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

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #45 on: 2011 January 22 19:32:04 »
I think I just had a "duh" moment...

So let me make sure I am thinking correctly:

When my module is installed, the "InstallPixInsightModule" function is called.  This is called everytime PixInsight starts.   The function calls the CTORs of my processes contained in the module.  Those CTORs all initialize a variable like "TheExposeImageInterface" which is scoped to the module not the class...

Therefore, I can access any of the "The<...>Interface" or "The<...>Process" etc...

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 #46 on: 2011 January 23 01:46:04 »
Quote
When do destructors get invoked on the Process classes?  Is it only when PixInsight closes?  Is there anywhere describing the full lifecycle of the Module / Process?

Do you refer to descendants of the ProcessImplementation class? In such case, the destructor is invoked each time an instance of the corresponding class is destroyed, either within your module, or as a result of an action performed by the PixInsight Core application. In the latter case, the destructor function is called asynchronously from Core garbage collection routines via the low-level module communication API, you you'll be unable to track the calls using standard debugging techniques.

If you're referring to a reimplementation of the MetaProcess class, then this is a completely different case. In theory, the destructor of one of these reimplementations would be called in the event of the module being uninstalled by the user (via the Process > Manage Modules main menu item for example). However, for reasons beyond this post, the Core application will never unload a module's shared object (a .so, .dylib or .dll shared library). Modules are scheduled for uninstallation in PixInsight; they cannot be explicitly unloaded until the running instance of PI Core terminates. This means that your reimplementations of MetaProcess::~MetaProcess() will never be called, unless you explicitly destroy a MetaProcess descendant instance from your module, which is a nonstandard practice. Note that the same happens with MetaModule, MetaParameter, and the rest of abstract meta description objects in the PCL.

Note that your reimplementations of MetaModule::OnUnload(), in case you define them, will never be called for the same reasons. MetaModule::OnLoad() will always be called upon module installation.

Quote
How can I serialize data from a PixInsight Array<T, A>  Class?  I'm sure I can do it manually, but I figured there has to be something built in since you are marshaling objects with their instance data...

What do you refer by "serializing"? Do you mean persistently storing instances of Array<> in data streams, such as XML documents? Currently, there is no built-in XML support in PCL (this is going to change soon). You can write Array<> to binary files if you want, or you can represent Array<> as plain text. let me know if you are interested in doing this.
Juan Conejero
PixInsight Development Team
http://pixinsight.com/

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #47 on: 2011 January 23 01:49:33 »
Quote
So let me make sure I am thinking correctly:

When my module is installed, the "InstallPixInsightModule" function is called.  This is called everytime PixInsight starts.   The function calls the CTORs of my processes contained in the module.  Those CTORs all initialize a variable like "TheExposeImageInterface" which is scoped to the module not the class...

Therefore, I can access any of the "The<...>Interface" or "The<...>Process" etc...

That's correct. The "The<...>" objects are the standard mechanism in PixInsight/PCL to access module global objects. Note also that these global objects are necessary for low-level communication between the Core and your module, which is always happening behind the scenes.
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 #48 on: 2011 January 23 07:17:28 »
Do you refer to descendants of the ProcessImplementation class?

I think my question was too vague.  I was really trying to get clues as to which of my classes where instantiated and destroyed by PixInsight and which were persistent for the duration of the application.  I think my question about the "TheXXX..." classes is what I really cared about.  So I think I understand that.

Quote
What do you refer by "serializing"? Do you mean persistently storing instances of Array<> in data streams, such as XML documents? Currently, there is no built-in XML support in PCL (this is going to change soon). You can write Array<> to binary files if you want, or you can represent Array<> as plain text. let me know if you are interested in doing this.

Specifically, I want to store the user's camera settings without requiring them to load a process icon.  The Settings API will let me do this, but it doesn't have a convenient way for me to persist my data structures.  As a lazy Ruby developer I am spoiled :-P - so yes, I would like to serialize my Array<> to a char* that I can store using the settings API.  XML would be wasteful since it is just an internal mechanism.  I don't really care if it is human readable.  That's what the XPSM files are for ;-)


David Raphael

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #49 on: 2011 January 23 10:57:52 »
I have another simple question. 

I need to convert a pcl::String into an LPCSTR in Windows.

Is there a simple way to do this?
David Raphael

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #50 on: 2011 January 23 11:26:51 »
Is this it? -

Code: [Select]
IsoString theString = GUI->CamDlg.GetDriverFile().ToIsoString();
char * chars = theString.c_str();
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 #51 on: 2011 January 24 02:58:29 »
Quote
Specifically, I want to store the user's camera settings without requiring them to load a process icon.  The Settings API will let me do this, but it doesn't have a convenient way for me to persist my data structures.  As a lazy Ruby developer I am spoiled :-P - so yes, I would like to serialize my Array<> to a char* that I can store using the settings API.  XML would be wasteful since it is just an internal mechanism.  I don't really care if it is human readable.  That's what the XPSM files are for ;-)

Actually, that isn't difficult to do with PCL using the Settings class and a few container classes.

These static member functions of the pcl::Settings class allow you to do what you want:

bool Settings::Read( const IsoString& key, ByteArray& b )
void Settings::Write( const IsoString& key, const ByteArray& b )


A ByteArray object is a dynamic array of bytes implemented as the Array<uint8> template instantiation in PCL. ByteArray allows you to store basically anything, so it is ideal to store the contents of simple data structures.

Let's say that your camera settings are being represented with a CameraSettings class. Then all you need is adding two member functions that convert its data to/from raw binary representations:

class CameraSettings
{
public:
   ...
   void AddToRawData( ByteArray& ) const;
   ByteArray::const_iterator GetFromRawData( ByteArray::const_iterator );
};


Now you need to implement the actual export/import mechanisms. First, let's introduce a few utility routines implemented as template functions:

Code: [Select]
/*
 * Adds an object t to a ByteArray stream b.
 */
template <class T>
void AddToRawData( ByteArray& b, const T& t )
{
   const uint8* p = reinterpret_cast<const uint8*>( &t );
   b.Add( p, p+sizeof( t ) );
}

/*
 * Retrieves an object t from a ByteArray stream at the specified location i.
 * Returns an iterator located at the next position in the ByteArray stream.
 */
template <class T>
ByteArray::const_iterator GetFromRawData( T& t, ByteArray::const_iterator i )
{
   t = *reinterpret_cast<const T*>( i );
   return i + sizeof( T );
}

/*
 * Adds the contents of a string s to a ByteArray stream b.
 */
template <class S>
void AddStringToRawData( ByteArray& b, const S& s )
{
   AddToRawData( b, uint32( s.Length() ) );
   if ( !s.IsEmpty() )
      b.Add( reinterpret_cast<const uint8*>( s.Begin() ), reinterpret_cast<const uint8*>( s.End() ) );
}

/*
 * Loads a string's character contents from the specified location i on a ByteArray.
 * Returns an iterator located at the next position in the ByteArray stream.
 */
template <class S>
ByteArray::const_iterator GetStringFromRawData( S& s, ByteArray::const_iterator i )
{
   uint32 n;
   i = GetFromRawData( n, i );
   if ( n > 0 )
   {
      s.Assign( reinterpret_cast<const S::char_type*>( i ), 0, n );
      i += n * sizeof( S::char_type );
   }
   else
      s.Clear();
   return i;
}

/*
 * Template instantiations for the String type.
 */

void AddToRawData( ByteArray& b, const String& s )
{
   AddStringToRawData( b, s );
}

ByteArray::const_iterator GetFromRawData( String& s, ByteArray::const_iterator i )
{
   return GetStringFromRawData( s, i );
}

/*
 * Template instantiations for the IsoString type.
 */

void AddToRawData( ByteArray& b, const IsoString& s )
{
   AddStringToRawData( b, s );
}

ByteArray::const_iterator GetFromRawData( IsoString& s, ByteArray::const_iterator i )
{
   return GetStringFromRawData( s, i );
}

With these simple routines we have a rough implementation of ByteArray-based data streams. The implementation is indeed rough, but efficient and sufficient to our purposes. I have in mind adding more capable and elegant data stream classes to PCL but for now these simple routines should do the trick quite well.

In the above code, note that strings require specific template instantiations and specializations, as these are aggregate objects. Basically, we need to store the length in characters (not bytes!) and, if the string is not empty, the sequence of characters as a set of raw bytes. Other simple types, such as int and float, double, etc., can be stored directly.

As an example, let's suppose that your CameraSettings class only has a few data members similar to these:

class CameraSettings
{
private:
   String cameraName;
   int    imageWidth;   // in pixels
   int    imageHeight;  // ...
   float  readoutNoise; // in e-
   int    shutterSpeed; // in ms
};


Then your conversion routines would look like these:

void CameraSettings::AddToRawData( ByteArray& b ) const
{
   AddToRawData( b, cameraName );
   AddToRawData( b, imageWidth );
   AddToRawData( b, imageHeight );
   AddToRawData( b, readoutNoise );
   AddToRawData( b, shutterSpeed );
}

ByteArray::const_iterator CameraSettings::GetFromRawData( ByteArray::const_iterator i )
{
   return GetFromRawData( shutterSpeed,
             GetFromRawData( readoutNoise,
                GetFromRawData( imageHeight,
                   GetFromRawData( imageWidth,
                      GetFromRawData( cameraName, i ) ) ) ) );
}


Now all you need is storing and managing your private data streams with the Settings class. For example:

typedef Array<CameraSettings> camera_settings_container; // represents your set of supported cameras

void SaveCameras( const camera_settings_container& cameras )
{
   ByteArray data;
   for ( camera_settings_container::const_iterator i = cameras.Begin(); i != cameras.End(); ++i )
      i->AddToRawData( data );
   Settings::Write( "CameraData", data );
}

camera_settings_container LoadCameras()
{
   camera_settings_container cameras;
   ByteArray data;
   if ( Settings::Read( "CameraData", data ) )
   {
      CameraSettings camera;
      for ( ByteArray::const_iterator i = data.Begin(); i < data.End(); i = camera.GetFromRawData( i ) )
         cameras.Add( camera );
   }
   return cameras;
}


Disclaimer: code not tested, so it can contain some errors.

Hope this helps.
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 #52 on: 2011 January 24 06:46:34 »
Quote
Hope this helps.

Most definitely!
David Raphael

Offline Sean Houghton

  • Newcomer
  • Posts: 36
    • Cerebiggum
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #53 on: 2011 February 02 20:10:52 »
Hi Dave,

Any chance you can host your source code on GitHub so people like me can contribute?
Sean
Carlsbad, CA
cerebiggum.com

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #54 on: 2011 February 02 21:20:04 »
I will post it on github soon...I need to get all the licensing sorted out etc...but I definitely plan on making this a community project!

Cheers,
Dave
David Raphael

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #55 on: 2011 February 05 18:43:54 »
Ok - so I am probably the world's worst C++ coder :-)  And when you throw COM into the mix ... along with some templates ... my head starts to hurt!

I am struggling with this code.  It is a wrapper for an ASCOM call.  The ASCOM call, ImageArray() returns a SAFEARRAY ...

This code compiles now, but I think it is fundamentally wrong.  It is really expensive to be invoking all of these functions in these loops...

So - here is what I need help with.  I need to copy this 2d array of signed integers that are buried in this SAFEARRAY (theCameraPtr->ImageArray.parray) into an Image for PixInsight!

I haven't tested this yet...I am probably going to get to that tonight or tomorrow, but in the meantime - I'd love some advice on how to make this code suck a little less.

Code: [Select]

UInt32Image PixInsightASCOMDriver::ImageArray()
{
while(!theCameraPtr->ImageReady)
{
Sleep(1000);
}

UInt32Image theImageData;
theImageData.AllocateData(theCameraPtr->NumX, theCameraPtr->NumY);
CComSafeArray< long > safeArr;
safeArr.Attach(theCameraPtr->ImageArray.parray);
long idx[2];
long val;
for(int rowIdx = 0;rowIdx < theCameraPtr->NumY; rowIdx++)
{
UInt32Image::sample* v = theImageData.ScanLine(rowIdx);

for(int colIdx = 0;colIdx < theCameraPtr->NumX; colIdx++)
{
idx[0] = colIdx;
idx[1] = rowIdx;
safeArr.MultiDimGetAt(idx, val);
UInt32PixelTraits::FromSample((pcl::int16&)val, v[colIdx]);
}
}
safeArr.Detach();
return theImageData;
}

Thanks for any help...
David Raphael

Offline zvrastil

  • PixInsight Addict
  • ***
  • Posts: 179
    • Astrophotography
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #56 on: 2011 February 06 00:42:51 »
Hello David,

there is a function SafeArrayAccessData, which gives you pointer to the content of the array. It is not on the ATL wrapper CComSafeArray (I'm not sure why). Try following code:
Code: [Select]
       UInt32Image PixInsightASCOMDriver::ImageArray()
        {
                while(!theCameraPtr->ImageReady)
                {
                        Sleep(1000);
}

                UInt32Image theImageData;
                theImageData.AllocateData(theCameraPtr->NumX, theCameraPtr->NumY);
                
                long HUGEP *pData;
                SafeArrayAccessData( theCameraPtr->ImageArray.parray, (void HUGEP* FAR*)&pData );

                for(int rowIdx = 0;rowIdx < theCameraPtr->NumY; rowIdx++)
{
UInt32Image::sample* v = theImageData.ScanLine(rowIdx);
                        memcpy( sample, pData+rowIdx*theCameraPtr->NumX, theCameraPtr->NumX*sizeof(long) );
                }

                SafeArrayUnaccessData( theCameraPtr->ImageArray.parray );

                return theImageData;
         }

I'm writing it from the top of my head, so there may be some error. But that's generally the idea. In case of some problem, let know.

regards, Zbynek

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #57 on: 2011 February 06 07:19:36 »
Thanks Zbynek -question:
Is
Code: [Select]
long HUGEP *pData;

the same as

Code: [Select]
long *pData;

On 32 bit systems?


I think it is.  I think that
Code: [Select]
long *pData;

is actually not a long.  it should be more like
Code: [Select]
SAFEARRAY *pData;

But I think I can adapt this code accordingly. 

Now this next question may be for Juan - I picked UInt32Image.  I did this because the ASCOM driver always returns a 2D array of long .  However, I was using:

Code: [Select]
UInt32PixelTraits::FromSample((pcl::int16&)val, v[colIdx]);

Which now that I look at it, should probably be int32 not int16...but regardless, I wasn't sure if this is necessary?  I noticed there seems to be a lot of cool template magic with regards to types and the images...but there isn't a Int32Image -

Or if I am completely on the wrong track here, please let me know.

Cheers,
Dave
David Raphael

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #58 on: 2011 February 06 10:06:35 »
Ok - I've whipped up a newer version.  

I think I was thinking that I had to access the safearray as a multidimensional array, but instead I'm accessing it as a single dim array...so I am back to a long * instead of a multidim...

Also, I read that it is not dependable to use the reported size of the image - you need to check the bounds of the array (makes sense)...so I added that change as well.

Code: [Select]
UInt32Image PixInsightASCOMDriver::ImageArray()
{
//TODO:  This needs to have some sort of time out.
while(!theCameraPtr->ImageReady)
{
Sleep(1000);
}

long *imageData;
SafeArrayAccessData(theCameraPtr->ImageArray.parray, (void **)&imageData);
int dims = SafeArrayGetDim(theCameraPtr->ImageArray.parray);
long ubound1, ubound2, lbound1, lbound2;
SafeArrayGetUBound(theCameraPtr->ImageArray.parray,1,&ubound1);
SafeArrayGetUBound(theCameraPtr->ImageArray.parray,2,&ubound2);
SafeArrayGetLBound(theCameraPtr->ImageArray.parray,1,&lbound1);
SafeArrayGetLBound(theCameraPtr->ImageArray.parray,2,&lbound2);

int sizeX = ubound1 - lbound1;
int sizeY = ubound2 - lbound2;

UInt32Image theImageData;
theImageData.AllocateData(sizeX, sizeY);

for(int rowIdx = 0;rowIdx < sizeY; rowIdx*=sizeY)
{
UInt32Image::sample* v = theImageData.ScanLine(rowIdx);
memcpy(v, &imageData[rowIdx], sizeX * sizeof(long));
}

SafeArrayUnaccessData( theCameraPtr->ImageArray.parray );

return theImageData;
}

My next question is - do I need to use memcpy?  I think with C++ I can just use an assignment, and the memcpy is implicit...

Also - I just thought about something...I don't think I should be copying long data directly to a sample - I think that this is a bad idea...I'm going to change that...do I need to actually copy each sample?  Or is there a bulk way to load a set of data AND convert from signed to unsigned :-) ?

Thanks for the help...
« Last Edit: 2011 February 06 10:14:19 by David Raphael »
David Raphael

Offline David Raphael

  • PixInsight Addict
  • ***
  • Posts: 226
    • Astrofactors CCD Cameras
Re: Image Acquisition in PixInsight (Was: When will Pixinsight...)
« Reply #59 on: 2011 February 06 10:19:57 »
Ok - that last chunk of code was my brain on drugs I think :=)  I've been writing in Ruby too long...

I was trying to iterate over rows...but I was incorrectly incrementing the row count in trying to create the offset for each row in the other array...

Anyways - I will post a new chunk of code shortly.
David Raphael