Catching imagemagick compare -metric AE result from .net c#

Questions and postings pertaining to the development of ImageMagick, feature enhancements, and ImageMagick internals. ImageMagick source code and algorithms are discussed here. Usage questions which are too arcane for the normal user list should also be posted here.
Post Reply
bmrowe

Catching imagemagick compare -metric AE result from .net c#

Post by bmrowe »

I am running the compare method from a .net c# app and want to grab the value it returns with the –metric AE command. I have attempted to do it the same way as I have done before with no luck, the standard out from the process call is always an empty string even though the comparison completes successfully and when I run the same command from the command line it returns a valid number. Is there a trick to getting this to work? The chunk of code shown below is my latest attempt.



Process process = new Process();

process.StartInfo.FileName = "compare";

process.StartInfo.CreateNoWindow = true;

process.StartInfo.UseShellExecute = false;

process.StartInfo.RedirectStandardOutput = true;

process.StartInfo.Arguments = " -metric AE " + "\"" + @Image1.Text + "\" \"" + @Image2.Text + "\"" +

" \"C:\\Documents and Settings\\someuser\\Desktop\\Differences.pdf\"";



process.Start();

output = process.StandardOutput.ReadToEnd();

process.WaitForExit();

output = process.StandardOutput.ReadToEnd();


Thanks!
bmrowe

Re: Catching imagemagick compare -metric AE result from .net c#

Post by bmrowe »

I figured out the solution, mainly due to the overwhelming amount of support from this forum. I thought I would post the solution to prevent this forum from being a total waste of time.

The main issue is that there needs to be a thread to execute the program and another thread to capture the data... all the while being thread safe. So the code basically registered an event to fire asynchronously when something is written to standardout or standarderror.

###CODE
//variables
public IList<string> strings = new List<string>();

//main function
ProcessCaller processCaller = new ProcessCaller(this);
processCaller.FileName = "compare";
processCaller.Arguments = " -metric AE " + "\"" + @Image1.Text + "\" \"" + @Image2.Text + "\"" +
" \"C:\\Documents and Settings\\someuser\\Desktop\\Differences.pdf\"";
processCaller.StdErrReceived += new DataReceivedHandler(writeStreamInfo);
processCaller.StdOutReceived += new DataReceivedHandler(writeStreamInfo);
processCaller.Completed += new EventHandler(processCompletedOrCanceled);
processCaller.Cancelled += new EventHandler(processCompletedOrCanceled);
processCaller.Start();

private void writeStreamInfo(object sender, DataReceivedEventArgs e)
{
strings.Add(e.Text);
}
private void processCompletedOrCanceled(object sender, EventArgs e)
{
}

//process caller class
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Collections;
using System.ComponentModel;
using AsyncOperation=TestProcessCaller.AsyncOperation;

namespace ImageMagickTest
{
/// <summary>
/// Delegate used by the events StdOutReceived and
/// StdErrReceived...
/// </summary>
public delegate void DataReceivedHandler(object sender,
DataReceivedEventArgs e);

/// <summary>
/// Event Args for above delegate
/// </summary>
public class DataReceivedEventArgs : EventArgs
{
/// <summary>
/// The text that was received
/// </summary>
public string Text;
/// <summary>
/// Constructor
/// </summary>
/// <param name="text">The text that was received for this event to be triggered.</param>
public DataReceivedEventArgs(string text)
{
Text = text;
}
}

/// <summary>
/// This class can launch a process (like a bat file, perl
/// script, etc) and return all of the StdOut and StdErr
/// to GUI app for display in textboxes, etc.
/// </summary>
/// <remarks>
/// This class (c) 2003 Michael Mayer
/// Use it as you like (public domain licensing).
/// Please post any bugs / fixes to the page where
/// you downloaded this code.
/// </remarks>
public class ProcessCaller : AsyncOperation
{

/// <summary>
/// The command to run (should be made into a property)
/// </summary>
public string FileName;
/// <summary>
/// The Arguments for the cmd (should be made into a property)
/// </summary>
public string Arguments;

/// <summary>
/// The WorkingDirectory (should be made into a property)
/// </summary>
public string WorkingDirectory;

/// <summary>
/// Fired for every line of stdOut received.
/// </summary>
public event DataReceivedHandler StdOutReceived;

/// <summary>
/// Fired for every line of stdErr received.
/// </summary>
public event DataReceivedHandler StdErrReceived;

/// <summary>
/// Amount of time to sleep on threads while waiting
/// for the process to finish.
/// </summary>
public int SleepTime = 500;

/// <summary>
/// The process used to run your task
/// </summary>
private Process process;

/// <summary>
/// Initialises a ProcessCaller with an association to the
/// supplied ISynchronizeInvoke. All events raised from this
/// object will be delivered via this target. (This might be a
/// Control object, so events would be delivered to that Control's
/// UI thread.)
/// </summary>
/// <param name="isi">An object implementing the
/// ISynchronizeInvoke interface. All events will be delivered
/// through this target, ensuring that they are delivered to the
/// correct thread.</param>
public ProcessCaller(ISynchronizeInvoke isi)
: base(isi)
{
}

// This constructor only works with changes to AsyncOperation...
// /// <summary>
// /// Initialises a ProcessCaller without an association to an
// /// ISynchronizeInvoke. All events raised from this object
// /// will be delievered via the worker thread.
// /// </summary>
// public ProcessCaller()
// {
// }

/// <summary>
/// Launch a process, but do not return until the process has exited.
/// That way we can kill the process if a cancel is requested.
/// </summary>
protected override void DoWork()
{
StartProcess();

// Wait for the process to end, or cancel it
while (!process.HasExited)
{
Thread.Sleep(SleepTime); // sleep
if (CancelRequested)
{
// Not a very nice way to end a process,
// but effective.
process.Kill();
AcknowledgeCancel();
}
}
}

/// <summary>
/// This method is generally called by DoWork()
/// which is called by the base classs Start()
/// </summary>
protected virtual void StartProcess()
{
// Start a new process for the cmd
process = new Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = FileName;
process.StartInfo.Arguments = Arguments;
process.StartInfo.WorkingDirectory = WorkingDirectory;
process.Start();


// Invoke stdOut and stdErr readers - each
// has its own thread to guarantee that they aren't
// blocked by, or cause a block to, the actual
// process running (or the gui).
new MethodInvoker(ReadStdOut).BeginInvoke(null, null);
new MethodInvoker(ReadStdErr).BeginInvoke(null, null);

}

/// <summary>
/// Handles reading of stdout and firing an event for
/// every line read
/// </summary>
protected virtual void ReadStdOut()
{
string str;
while ((str = process.StandardOutput.ReadLine()) != null)
{
FireAsync(StdOutReceived, this, new DataReceivedEventArgs(str));
}
}

/// <summary>
/// Handles reading of stdErr
/// </summary>
protected virtual void ReadStdErr()
{
string str;
while ((str = process.StandardError.ReadLine()) != null)
{
FireAsync(StdErrReceived, this, new DataReceivedEventArgs(str));
}
}

}
}

//ASyncOperation class
using System;
using System.Windows.Forms;
using System.Threading;
using System.ComponentModel;

namespace TestProcessCaller
{
/// <summary>
/// Exception thrown by AsyncUtils.AsyncOperation.Start when an
/// operation is already in progress.
/// </summary>
public class AlreadyRunningException : System.ApplicationException
{
/// <summary>
///
/// </summary>
public AlreadyRunningException() : base("Asynchronous operation already running")
{ }
}

/// <summary>
/// This base class is designed to be used by lengthy operations that wish to
/// support cancellation. It also allows those operations to invoke delegates
/// on the UI Thread of a hosting control.
/// </summary>
/// <remarks>
/// This class is from the MSDN article:
/// http://msdn.microsoft.com/msdnmag/issue ... fault.aspx
/// (C) 2001-2002 I D Griffiths
/// Please see the article for a complete description of the intentions and
/// operation of this class.
/// </remarks>
public abstract class AsyncOperation
{
/// <summary>
/// Initialises an AsyncOperation with an association to the
/// supplied ISynchronizeInvoke. All events raised from this
/// object will be delivered via this target. (This might be a
/// Control object, so events would be delivered to that Control's
/// UI thread.)
/// </summary>
/// <param name="target">An object implementing the
/// ISynchronizeInvoke interface. All events will be delivered
/// through this target, ensuring that they are delivered to the
/// correct thread.</param>
public AsyncOperation(ISynchronizeInvoke target)
{
isiTarget = target;
isRunning = false;
}

/// <summary>
/// Launch the operation on a worker thread. This method will
/// return immediately, and the operation will start asynchronously
/// on a worker thread.
/// </summary>
public void Start()
{
lock(this)
{
if (isRunning)
{
throw new AlreadyRunningException();
}
// Set this flag here, not inside InternalStart, to avoid
// race condition when Start called twice in quick
// succession.
isRunning = true;
}
new MethodInvoker(InternalStart).BeginInvoke(null, null);
}


/// <summary>
/// Attempt to cancel the current operation. This returns
/// immediately to the caller. No guarantee is made as to
/// whether the operation will be successfully cancelled. All
/// that can be known is that at some point, one of the
/// three events Completed, Cancelled, or Failed will be raised
/// at some point.
/// </summary>
public virtual void Cancel()
{
lock(this)
{
cancelledFlag = true;
}
}

/// <summary>
/// Attempt to cancel the current operation and block until either
/// the cancellation succeeds or the operation completes.
/// </summary>
/// <returns>true if the operation was successfully cancelled
/// or it failed, false if it ran to completion.</returns>
public bool CancelAndWait()
{
lock(this)
{
// Set the cancelled flag

cancelledFlag = true;


// Now sit and wait either for the operation to
// complete or the cancellation to be acknowledged.
// (Wake up and check every second - shouldn't be
// necessary, but it guarantees we won't deadlock
// if for some reason the Pulse gets lost - means
// we don't have to worry so much about bizarre
// race conditions.)
while(!IsDone)
{
Monitor.Wait(this, 1000);
}
}
return !HasCompleted;
}

/// <summary>
/// Blocks until the operation has either run to completion, or has
/// been successfully cancelled, or has failed with an internal
/// exception.
/// </summary>
/// <returns>true if the operation completed, false if it was
/// cancelled before completion or failed with an internal
/// exception.</returns>
public bool WaitUntilDone()
{
lock(this)
{
// Wait for either completion or cancellation. As with
// CancelAndWait, we don't sleep forever - to reduce the
// chances of deadlock in obscure race conditions, we wake
// up every second to check we didn't miss a Pulse.
while (!IsDone)
{
Monitor.Wait(this, 1000);
}
}
return HasCompleted;
}


/// <summary>
/// Returns false if the operation is still in progress, or true if
/// it has either completed successfully, been cancelled
/// successfully, or failed with an internal exception.
/// </summary>
public bool IsDone
{
get
{
lock(this)
{
return completeFlag || cancelAcknowledgedFlag || failedFlag;
}
}
}

/// <summary>
/// This event will be fired if the operation runs to completion
/// without being cancelled. This event will be raised through the
/// ISynchronizeTarget supplied at construction time. Note that
/// this event may still be received after a cancellation request
/// has been issued. (This would happen if the operation completed
/// at about the same time that cancellation was requested.) But
/// the event is not raised if the operation is cancelled
/// successfully.
/// </summary>
public event EventHandler Completed;


/// <summary>
/// This event will be fired when the operation is successfully
/// stoped through cancellation. This event will be raised through
/// the ISynchronizeTarget supplied at construction time.
/// </summary>
public event EventHandler Cancelled;


/// <summary>
/// This event will be fired if the operation throws an exception.
/// This event will be raised through the ISynchronizeTarget
/// supplied at construction time.
/// </summary>
public event System.Threading.ThreadExceptionEventHandler Failed;


/// <summary>
/// The ISynchronizeTarget supplied during construction - this can
/// be used by deriving classes which wish to add their own events.
/// </summary>
protected ISynchronizeInvoke Target
{
get { return isiTarget; }
}
private ISynchronizeInvoke isiTarget;


/// <summary>
/// To be overridden by the deriving class - this is where the work
/// will be done. The base class calls this method on a worker
/// thread when the Start method is called.
/// </summary>
protected abstract void DoWork();


/// <summary>
/// Flag indicating whether the request has been cancelled. Long-
/// running operations should check this flag regularly if they can
/// and cancel their operations as soon as they notice that it has
/// been set.
/// </summary>
protected bool CancelRequested
{
get
{
lock(this) { return cancelledFlag; }
}
}
private bool cancelledFlag;


/// <summary>
/// Flag indicating whether the request has run through to
/// completion. This will be false if the request has been
/// successfully cancelled, or if it failed.
/// </summary>
protected bool HasCompleted
{
get
{
lock(this) { return completeFlag; }
}
}
private bool completeFlag;


/// <summary>
/// This is called by the operation when it wants to indicate that
/// it saw the cancellation request and honoured it.
/// </summary>
protected void AcknowledgeCancel()
{
lock(this)
{
cancelAcknowledgedFlag = true;
isRunning = false;

// Pulse the event in case the main thread is blocked
// waiting for us to finish (e.g. in CancelAndWait or
// WaitUntilDone).
Monitor.Pulse(this);

// Using async invocation to avoid a potential deadlock
// - using Invoke would involve a cross-thread call
// whilst we still held the object lock. If the event
// handler on the UI thread tries to access this object
// it will block because we have the lock, but using
// async invocation here means that once we've fired
// the event, we'll run on and release the object lock,
// unblocking the UI thread.
FireAsync(Cancelled, this, EventArgs.Empty);
}
}
private bool cancelAcknowledgedFlag;


// Set to true if the operation fails with an exception.
private bool failedFlag;
// Set to true if the operation is running
private bool isRunning;


// This method is called on a worker thread (via asynchronous
// delegate invocation). This is where we call the operation (as
// defined in the deriving class's DoWork method).
private void InternalStart()
{
// Reset our state - we might be run more than once.
cancelledFlag = false;
completeFlag = false;
cancelAcknowledgedFlag = false;
failedFlag = false;
// isRunning is set during Start to avoid a race condition
try
{
DoWork();
}
catch (Exception e)
{
// Raise the Failed event. We're in a catch handler, so we
// had better try not to throw another exception.
try
{
FailOperation(e);
}
catch
{ }

// The documentation recommends not catching
// SystemExceptions, so having notified the caller we
// rethrow if it was one of them.
if (e is SystemException)
{
throw;
}
}

lock(this)
{
// If the operation wasn't cancelled (or if the UI thread
// tried to cancel it, but the method ran to completion
// anyway before noticing the cancellation) and it
// didn't fail with an exception, then we complete the
// operation - if the UI thread was blocked waiting for
// cancellation to complete it will be unblocked, and
// the Completion event will be raised.
if (!cancelAcknowledgedFlag && !failedFlag)
{
CompleteOperation();
}
}
}


// This is called when the operation runs to completion.
// (This is private because it is called automatically
// by this base class when the deriving class's DoWork
// method exits without having cancelled

private void CompleteOperation()
{
lock(this)
{
completeFlag = true;
isRunning = false;
Monitor.Pulse(this);
// See comments in AcknowledgeCancel re use of
// Async.
FireAsync(Completed, this, EventArgs.Empty);
}
}

/// <summary>
///
/// </summary>
/// <param name="e"></param>
private void FailOperation(Exception e)
{
lock(this)
{
failedFlag = true;
isRunning = false;
Monitor.Pulse(this);
FireAsync(Failed, this, new ThreadExceptionEventArgs(e));
}
}

/// <summary>
/// Utility function for firing an event through the target.
/// It uses C#'s variable length parameter list support
/// to build the parameter list.
/// This functions presumes that the caller holds the object lock.
/// (This is because the event list is typically modified on the UI
/// thread, but events are usually raised on the worker thread.)
/// </summary>
/// <param name="dlg"></param>
/// <param name="pList"></param>
protected void FireAsync(Delegate dlg, params object[] pList)
{
if (dlg != null)
{
Target.BeginInvoke(dlg, pList);
}
}
}
}
Post Reply