Author Topic: Image Acquisition in PixInsight (Was: When will Pixinsight...)  (Read 78824 times)

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #75 on: 2011 February 07 10:52:26 »
When I get home I will try that...


Thanks!

-Dave
David Raphael

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #76 on: 2011 February 07 20:30:13 »
uint32 worked like a champ.  No more negative values.  Screenshot attached ;-)

BTW - The lines were from the driver before.  I disabled the "Line Noise Reduction" option in the driver.  Hopefully I can get the simulator working so I don't always have a camera hooked up ;-)

Screenshot attached...
David Raphael

Offline Carlos Milovic

  • PTeam Member
  • PixInsight Jedi Master
  • ******
  • Posts: 2172
  • Join the dark side... we have cookies
    • http://www.astrophoto.cl
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #77 on: 2011 February 07 20:49:43 »
Excellent :D
Regards,

Carlos Milovic F.
--------------------------------
PixInsight Project Developer
http://www.pixinsight.com

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #78 on: 2011 February 08 08:28:18 »
And just to show that I am testing more than one platform...here is a screen from Mac OS X -

I've written a test driver for Mac OS X - it is basically a simulator.  I will probably port the simulator to each platform so that people that want to write native PixInsight drivers will be able to see some sample code...

Cheers,
Dave
David Raphael

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #79 on: 2011 February 12 09:51:27 »
I am running into a weird behavior.  I am trying to throw an exception when a user tries to create 2 cameras with the same name.  However, when I throw the exception it somehow corrupts the button that was last clicked..

Code: [Select]

  void ImageAcquisitionSettingsInterface::__CameraListButtons_Click( Button& sender, bool checked )
  {
    if(sender == GUI->AddCamera_PushButton)
{
try
{
Console() << "AddCamera clicked\n";
AddCamera();
UpdateCameraList();
} catch( ... )
{
throw Error("Please use unique name for each camera.");
}
}
...

So when this code throws, the GUI->AddCamera_PushButton no longer sends events (at least the click event no longer seems to reach the function I have registered as the OnClick(..))  The other buttons still send events to the same event handler...


Let me know if you have any ideas!


Is this a bad way of handling errors with PCL?  Originally, I was throwing from the AddCamera method and not catching in the event handler.  But that doesn't make a difference either. 
David Raphael

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #80 on: 2011 February 12 10:17:55 »
Hi David,

As a general rule, no exception should propagate from an event handler. You must catch and process all exceptions thrown within the event handler function. For example:

#include <pcl/ErrorHandler.h>

...

if ( sender == GUI->AddCamera_PushButton )
{
   try
   {
      Console() << "AddCamera clicked\n";
      AddCamera();
      UpdateCameraList();
   }

   ERROR_HANDLER
}

...


The ERROR_HANDLER macro (declared in pcl/ErrorHandler.h) will do the necessary work for you. However, you should throw the correct exceptions from the appropriate locations in your code. In this case, the following exception:

      throw Error("Please use unique name for each camera.");

should probably be thrown from your AddCamera function. The ERROR_HANDLER macro will automatically generate the appropriate error messages for all Error exceptions, and also for other types of exceptions, including allocation errors and even unknown exceptions. ERROR_HANDLER used as above guarantees that no exception will be thrown by your event handler.
Juan Conejero
PixInsight Development Team
http://pixinsight.com/

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #81 on: 2011 February 12 10:46:45 »
Perfect - that fixed my problem...

David Raphael

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #82 on: 2011 February 17 22:04:36 »
This may be an obvious C++ thing, but the following code doesn't compile on OS X:

Code: [Select]
Console() << "foo";

This works though:

Code: [Select]
Console c = Console();
c << "foo";

I am not too worried about it either way...but I was just wondering if this is just one of those MSVC things that let's non-standard C++ slip by ;-)
David Raphael

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #83 on: 2011 February 18 00:19:51 »
Hi David,

That's a very strange error; it shouldn't happen. The code you're using:

Console() << "foo"; // *1*

is perfectly valid. It isn't the recommended way unless you only have to write something to the console sporadically, but it is legal. If you have to generate more output, the following code is more efficient:

Console console;
...
console << "foo";
console.Write( "foo" ); // equivalent to the previous sentence
console.WriteLn( "foo" ); // more efficient than 'console << "foo\n"'


because each time you call Console's constructor you're generating a new low-level handle in the core application, which is destroyed when the implicitly generated high-level object in your module gets out of scope (that is, in the same sentence if you use *1* above).

Anyway, as I've said your original intent should be compiled without any issue. What version of GCC are you using on your Mac? Open a terminal window and enter this command:

gcc -dumpversion

It should be 4.2.1. It is the default compiler in OS X Snow Leopard, which I assume you are using. PCL is not compatible with GCC < 4.2 on Mac OS X.
Juan Conejero
PixInsight Development Team
http://pixinsight.com/

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #84 on: 2011 February 18 06:29:08 »
Here is my compiler:
i686-apple-darwin10-gcc-4.2.1

Here is the compiler output:

Code: [Select]

../../ImageAcquisitionSettingsInterface.cpp: In member function ‘void pcl::ImageAcquisitionSettingsInterface::AddCamera()’:
../../ImageAcquisitionSettingsInterface.cpp:181: error: no match for ‘operator<<’ in ‘pcl::Console() << "AddCamera()\012"’
/Users/draphael/PCL/include/pcl/String.h:4692: note: candidates are: pcl::IsoString& pcl::operator<<(pcl::IsoString&, const pcl::GenericString<char, pcl::IsoCharTraits, pcl::StandardAllocator>&)


I definitely agree that it is inefficient to make the call that way each time, but I was still intrigued by the behavior - I agree with you that it should be perfectly legal. 

Thanks,
Dave
David Raphael

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #85 on: 2011 February 22 13:14:32 »
I am working on the threading code now.  I have some concerns:

I am using something like this code in my ExecuteGlobal() function:
Code: [Select]
...
exposeThread = new ExposeImageThread( TheImageAcquisitionSettingsInterface->activeCamera, exposureDuration, exposureCount);
exposeThread->Start( );

while( exposeThread->IsExposing() )
{
    //console << "exposing .... \n";
    pcl::Sleep(.01);
    ProcessInterface::ProcessEvents();
}

return true;

But this of course leaves the rest of the UIs locked - I think we will want the rest of PixInsight to keep running as normal while capturing.  Is this just an option?  Do I need to enable parallel processing on this instance or something like that?

Alternatively, I tried spinning up the thread and exiting the ExecuteGlobal() before the thread is finished...this definitely didn't make PixInsight happy.

Also, I guess it is a no-no for a thread to make calls that create ImageWindows :-)  <- I guess this is part of your design?  This is easy enough to process on the main ExposeImageInstance object anyways...but I was just curious.

Let me know what you think.

Regards,
Dave
David Raphael

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #86 on: 2011 February 22 13:38:44 »
And to continue this a bit...

Should I even use ExecuteGlobal()?  I guess this isn't really operating on any kind of images...so I guess I'll just implement my own method? 
David Raphael

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #87 on: 2011 February 22 16:40:18 »
Hi David,

Before continuing, at this point you must decide how you want to implement your process:

(1) As a synchronous process. Think of your tool as if it was a special version of the NewImage process. NewImage creates new images from a set of user-defined parameters (image geometry, color space, etc.). Your process would create new images as a result of an acquisition task initiated by the user (we'll see how later). Pros: Easy to implement, relatively simple and rock-solid implementation. Cons: as this is a synchronous task, the whole PixInsight platform has to wait until the image acquisition process is either completed or aborted.

(2) As an asynchronous process. Think of it as a special variation of the Statistics tool. Statistics is an observer. Your process isn't an observer, but it shares with Statistics the fact that it cannot be executed, neither in an image context nor in the global context. In this case your interface governs your process completely, something that Statistics also does. It has no standard execution button in its control bar (neither a triangle nor a sphere) and cannot generate instances (no process icons). You just have a button that reads 'Start' or something like that. The user presses 'Start' and your task is launched by the button's event handler. Your process must be implemented as an asynchronous thread that works in the background. Pros: Versatile implementation. PixInsight continues working normally while your process runs in the background. Cons: Complex and risky implementation. There are many potential stability issues. For example, what happens if the user terminates the application while your background thread is running? There are currently no provisions to assist you in this case (this may change in a future version though). There are possible interactions with other processes that you cannot know in advance. Basically, you have no control once your task has been launched because your interface returns to the main GUI thread immediately.

Option 2 may seem attractive, and it is doable, although it requires a lot of work that would distract you from your main development work. Personally I think option 2 is a bad option for other reasons. My recommendation is that you implement option 1. Then once you have your acquisition process well tested and debugged, converting it to option 2 isn't too difficult, although I doubt it is really interesting. I understand that what you don't like of option 1 is the fact that PI gets 'frozen' while your task is running. Well, that isn't as bad as it sounds; it is actually very good! After all, PI gets frozen with any regular process. For example, think of a long ImageIntegration process. Along with the fact that there is actually no freeze, remember that you can execute up to 256 simultaneous instances of the PI Core application, so you can have one instance acquiring images and another one working normally.

So let's assume that you think it ... ... ... OK, and you choose option 1 ;D

Several important considerations:

- Forget about ExecuteGlobal(). You don't want your process executed in the global context. The reason is that when a process is executed, the whole PI GUI —including your interface— are disabled and no user interaction, besides moving windows, is allowed. You definitely don't want this.

- As happens with option 2, you have a Start button (or an 'Acquire' button or whatever you want to call it). What happens when the user clicks Start? Easy: you open a modal dialog, launch your process as a thread, and enter a waiting loop very similar to your snippet. A modal dialog allows you: (a) ensure that you have a working GUI to provide feedback to the user, and (b) ensure that only your GUI can be used during the whole acquisition process. In other words, you have full control.

- Your process interface allows you to perform maintenance tasks such as defining cameras, working parameters, etc. But once your Start button gets pressed, your modal dialog does the job. This is a typical divide and conquer strategy.

- You cannot create image windows from a thread. Only the GUI thread (the main PixInsight Core thread, from which your Start button's OnClick() event handler is called) can perform GUI operations. A thread cannot communicate using GUI resources in any way.

So your next question is how can I create an image window during the acquisition process? Well, the following code is an example of how the whole thing would look like:

Code: [Select]
struct ExposeImageData
{
   ExposeImageData() :
   mutex(), image(), imageProgress( 0 ), imageReady( false ),
   abort( false ), error( false ), errorMessage()
   {
   }

   Mutex       mutex;         // To protect data from concurrent accesses
   UInt16Image image;         // The image being acquired
   int         imageProgress; // Progress indicator, e.g. from 0 to 100
   bool        imageReady;    // Flag true if a new image is now ready
   bool        abort;         // Flag true if the user wants to abort
   bool        error;         // Flag true if an error occurs
   String      errorMessage;  // Error information
   ... // more stuff
};

ExposeImageData data;

class ExposeImageThread : public Thread
{
public:
...
   virtual void Run()
   {
      while ( true )
      {
         // Check if the user has aborted the process
         if ( data.abort )
         {
            ... possibly perform some cleanup here
            break;
         }
      
         ... do your acquisition stuff here

         // Check possible errors
         if ( error )
         {
            data.mutex.Lock();
            data.error = true;
            data.errorMessage = "Hmm, something went wrong...";
            data.mutex.Unlock();
            break;
         }

         // Update the progress indicator
         data.mutex.Lock();
         data.imageProgress++;
         data.mutex.Unlock();

         // Do we have acquired a new image?
         if ( acquisitionComplete )
         {
            data.mutex.Lock();
            data.imageReady = true;
            data.imageProgress = 100;
            data.mutex.Unlock();
         }

         // Job done?
         if ( finishedAcquiringImages )
            break;
      }
   }
};

class ExposeImageDialog : public Dialog
{
public:

   ExposeImageDialog() : Dialog()
   {
      ... build your dialog here

      // Handle Abort button click events
      Abort_PushButton.OnClick( (Button::click_event_handler)&ExposeImageDialog::__Button_Click, *this );

      // Attach your own dialog execution handler, so you gain control
      // as soon as your dialog is executed modally.
      OnExecute( (Dialog::execute_event_handler)&ExposeImageDialog::__Dialog_Execute, *this );
   }

   void __Dialog_Execute( Dialog& sender )
   {
      // Here we are. Now we are the *only* element of PixInsight
      // that can be used interactively by the user. Since this has
      // been called by the GUI thread, we can do anything we want
      // with GUI resources.
  
      // This is your original snippet
      
      exposeThread = new ExposeImageThread( TheImageAcquisitionSettingsInterface->activeCamera, exposureDuration, exposureCount );
      exposeThread->Start();

      while ( exposeThread->IsExposing() )
      {
         //console << "exposing .... \n"; <-- ups, forget about this; there is no console available at this point!
         pcl::Sleep( .01 );
         ProcessInterface::ProcessEvents();

         // Now let's see if we have an image ready
         data.mutex.Lock();
         bool myImageReady = data.imageReady;
         data.mutex.Unlock();
         if ( myImageReady )
         {
            ... code to create a new image window
         }
      }

      if ( data.error ) // note that the thread is not running at this point
      {
         MessageBox( data.errorMessage, ... ).Execute();
      }

      ...
   }

   void __Button_Click( Button& sender, bool checked )
   {
      // Handle the Abort button
      if ( sender == Abort_PushButton )
      {
         data.mutex.Lock();
         data.abort = true;
         data.mutex.Unlock();
      }
      else ... // handle other buttons
   }
  
};

// This is your interface class

class ExposeImageInterface : public ProcessInterface
{
public:
   ...

private:

   void __Button_Click( Button& sender, bool checked )
   {
      // Handle the Start button
      if ( sender == Start_PushButton )
      {
         ExposeImageDialog dialog;
         dialog.Execute();
      }
   }
  
};

This is just an example to show you how the different parts would work. Of course, the acquisition task could be implemented without a thread. however, a thread leads to a more modular implementation and allows for an easier adaptation of the whole engine to another paradigm in the future, such as option 2 above.

As always, code not tested at all. Hope this helps you to start running in the right direction :)
« Last Edit: 2011 February 22 16:46:49 by Juan Conejero »
Juan Conejero
PixInsight Development Team
http://pixinsight.com/

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #88 on: 2011 February 22 20:06:01 »
Thank you for the response...

So this makes me a little sad :-(

The reason I say that is because I was imaging a process container that I could stuff a bunch of different ExposeImage by dragging the triangle to the process container...using this I could do the same for Autofocus, etc...

But I guess that the process container isn't really sufficient for the camera control paradigm...however, I was trying to take advantage of the infrastructure you've laid out!!!

I will ponder this for a day or 2...maybe there is another option that I am not considering...

Cheers,
Dave
David Raphael

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #89 on: 2011 February 23 00:45:11 »
David,

Quote
I was imaging a process container that I could stuff a bunch of different ExposeImage by dragging the triangle to the process container...

Nothing stops you from doing that. You can implement both an instantiable global process and a modal dialog as I have described above. Instead of relying on a Start button on your interface, make your process a regular global process and write your ExecuteGlobal() as follows:

Code: [Select]
class ExposeImageInstance : public ProcessImplementation
{
public:
   ...
   virtual bool CanExecuteGlobal( String& whyNot ) const
   {
      return true;
   }

   virtual bool ExecuteGlobal()
   {
      ExposeImageDialog dialog;
      dialog.Execute(); // this is the image acquisition dialog
      return !thereAreErrors;
   }

   ...
};

The ExposeImageDialog that you open on your ExecuteGlobal routine is a modal dialog that works independently on your ExposeImageInterface; both GUI elements are unrelated and independent.

If you want to implement a process in the 'Pure PixInsight Way'TM then you're done. But if you wish, you can also implement a Start button in your interface. In this case, things are really simple because you can execute your own process globally from your Start button event handler:

Code: [Select]
class ExposeImageInterface : public ProcessInterface
{
public:
   ...

private:

   // This is the instance that you are editing on your interface.
   // It has the current definitions of cameras, working settings, etc.
   ExposeImageInstance instance;

   void __Button_Click( Button& sender, bool checked )
   {
      // Handle the Start button
     
      if ( sender == Start_PushButton )
      {
         instance.LaunchGlobal(); // execute our instance globally
      }
   }
   
};

Personally I like this way, because it adheres much better to the object oriented design in PixInsight. Let me know if this helps you further.

This is a relatively 'atypical' process in PixInsight so we have to devise new ways of doing what we want. I think the platform is flexible enough to accommodate your acquisition process without any problem.
Juan Conejero
PixInsight Development Team
http://pixinsight.com/