2 INDI driver
for SBIG CCD
3 Copyright (C
) 2005 Chris
Curran (ccurran AT planetcurran DOT com
)
5 Based on Apogee PPI driver by Jasem
Mutlaq (mutlaqja AT ikarustech DOT com
)
7 This library is free software
; you can redistribute it
and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation
; either
10 version
2.1 of the License
, or (at your option
) any later version
.
12 This library is distributed in the hope that it will be useful
,
13 but WITHOUT ANY WARRANTY
; without even the implied warranty of
14 MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE
. See the GNU
15 Lesser General Public License
for more details
.
17 You should have received a copy of the GNU Lesser General Public
18 License along with
this library
; if not, write to the Free Software
19 Foundation
, Inc
., 59 Temple Place
, Suite
330, Boston
, MA
02111-1307 USA
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <netinet/in.h>
36 extern char* me
; /* argv[0] */
37 SBIGCam
*MainCam
= NULL
; /* Main and only camera */
39 /* send client definitions of all properties */
43 MainCam
= new SBIGCam();
46 void ISGetProperties (const char *dev
)
48 if (dev
&& strcmp (mydev
, dev
))
53 MainCam
->ISGetProperties(dev
);
57 void ISNewSwitch (const char *dev
, const char *name
, ISState
*states
, char *names
[], int n
)
60 /* ignore if not ours */
61 if (dev
&& strcmp (dev
, mydev
))
66 MainCam
->ISNewSwitch(dev
, name
, states
, names
, n
);
69 void ISNewText (const char *dev
, const char *name
, char *texts
[], char *names
[], int n
)
71 /* ignore if not ours */
72 if (dev
&& strcmp (mydev
, dev
))
77 MainCam
->ISNewText(dev
, name
, texts
, names
, n
);
81 void ISNewNumber (const char *dev
, const char *name
, double values
[], char *names
[], int n
)
84 /* ignore if not ours */
85 if (dev
&& strcmp (dev
, mydev
))
90 MainCam
->ISNewNumber(dev
, name
, values
, names
, n
);
93 void ISNewBLOB (const char */
*dev*/
, const char */
*name*/
, int */
*sizes
[]*/
, char **/
*blobs
[]*/
, char **/
*formats
[]*/
, char **/
*names
[]*/
, int /*n*/)
96 // We use this if we're receving binary data from the client. Most likely we won't for this driver.
103 IEAddTimer (POLLMS
, SBIGCam::ISStaticPoll
, this);
111 void SBIGCam::initProperties()
113 fillSwitch(&PowerS
[0], "CONNECT", "Connect", ISS_OFF
);
114 fillSwitch(&PowerS
[1], "DISCONNECT", "Disconnect", ISS_ON
);
115 fillSwitchVector(&PowerSP
, PowerS
, NARRAY(PowerS
), mydev
, "CONNECTION", "Connection", COMM_GROUP
, IP_RW
, ISR_1OFMANY
, 60, IPS_IDLE
);
117 fillSwitch(&FrameTypeS
[0], "FRAME_LIGHT", "Light", ISS_ON
);
118 fillSwitch(&FrameTypeS
[1], "FRAME_BIAS", "Bias", ISS_OFF
);
119 fillSwitch(&FrameTypeS
[2], "FRAME_DARK", "Dark", ISS_OFF
);
120 fillSwitch(&FrameTypeS
[3], "FRAME_FLAT", "Flat Field", ISS_OFF
);
121 fillSwitchVector(&FrameTypeSP
, FrameTypeS
, NARRAY(FrameTypeS
), mydev
, "FRAME_TYPE", "Frame Type", EXPOSE_GROUP
, IP_RW
, ISR_1OFMANY
, 60, IPS_IDLE
);
123 fillNumber(&FrameN
[0], "X", "", "%.0f", 0., MAX_PIXELS
, 1., 0.);
124 fillNumber(&FrameN
[1], "Y", "", "%.0f", 0., MAX_PIXELS
, 1., 0.);
125 fillNumber(&FrameN
[2], "Width", "", "%.0f", 0., MAX_PIXELS
, 1., 0.);
126 fillNumber(&FrameN
[3], "Height", "", "%.0f", 0., MAX_PIXELS
, 1., 0.);
127 fillNumberVector(&FrameNP
, FrameN
, NARRAY(FrameN
), mydev
, "FRAME", "Frame", IMAGE_GROUP
, IP_RW
, 60, IPS_IDLE
);
129 fillNumber(&BinningN
[0], "HOR_BIN", "X", "%0.f", 1., MAXHBIN
, 1., 1.);
130 fillNumber(&BinningN
[1], "VER_BIN", "Y", "%0.f", 1., MAXVBIN
, 1., 1.);
131 fillNumberVector(&BinningNP
, BinningN
, NARRAY(BinningN
), mydev
, "BINNING", "Binning", IMAGE_GROUP
, IP_RW
, 60, IPS_IDLE
);
133 fillNumber(&ExposeTimeN
[0], "EXPOSE_S", "Duration (s)", "%5.2f", 0., 36000., 0.5, 1.);
134 fillNumberVector(&ExposeTimeNP
, ExposeTimeN
, NARRAY(ExposeTimeN
), mydev
, "EXPOSE_DURATION", "Expose", EXPOSE_GROUP
, IP_RW
, 60, IPS_IDLE
);
136 fillNumber(&TemperatureN
[0], "TEMPERATURE", "Temperature", "%+06.2f", MIN_CCD_TEMP
, MAX_CCD_TEMP
, 0.2, 0.);
137 fillNumberVector(&TemperatureNP
, TemperatureN
, NARRAY(TemperatureN
), mydev
, "CCD_TEMPERATURE", "Expose", EXPOSE_GROUP
, IP_RW
, 60, IPS_IDLE
);
139 // We need to setup the BLOB (Binary Large Object) below. Using this property, we can send FITS to our client
140 strcpy(imageB
.name
, "CCD1");
141 strcpy(imageB
.label
, "Feed");
142 strcpy(imageB
.format
, "");
151 strcpy(imageBP
.device
, mydev
);
152 strcpy(imageBP
.name
, "Video");
153 strcpy(imageBP
.label
, "Video");
154 strcpy(imageBP
.group
, COMM_GROUP
);
155 strcpy(imageBP
.timestamp
, "");
158 imageBP
.s
= IPS_IDLE
;
159 imageBP
.bp
= &imageB
;
165 void SBIGCam::ISGetProperties(const char */
*dev*/
)
169 IDDefSwitch(&PowerSP
, NULL
);
170 IDDefBLOB(&imageBP
, NULL
);
173 IDDefSwitch(&FrameTypeSP
, NULL
);
174 IDDefNumber(&ExposeTimeNP
, NULL
);
175 IDDefNumber(&TemperatureNP
, NULL
);
178 IDDefNumber(&FrameNP
, NULL
);
179 IDDefNumber(&BinningNP
, NULL
);
183 void SBIGCam::ISNewSwitch (const char */
*dev*/
, const char *name
, ISState
*states
, char *names
[], int n
)
187 if (!strcmp (name
, PowerSP
.name
))
189 IUResetSwitches(&PowerSP
);
190 IUUpdateSwitches(&PowerSP
, states
, names
, n
);
196 if (!strcmp(FrameTypeSP
.name
, name
))
198 if (checkPowerS(&FrameTypeSP
))
201 IUResetSwitches(&FrameTypeSP
);
202 IUUpdateSwitches(&FrameTypeSP
, states
, names
, n
);
203 FrameTypeSP
.s
= IPS_OK
;
204 IDSetSwitch(&FrameTypeSP
, NULL
);
211 void SBIGCam::ISNewText (const char */
*dev*/
, const char */
*name*/
, char **/
*texts
[]*/
, char **/
*names
[]*/
, int /*n*/)
216 void SBIGCam::ISNewNumber (const char */
*dev*/
, const char *name
, double values
[], char *names
[], int n
)
219 if (!strcmp (ExposeTimeNP
.name
, name
))
221 if (checkPowerN(&ExposeTimeNP
))
224 if (ExposeTimeNP
.s
== IPS_BUSY
)
226 ExposeTimeNP
.s
= IPS_IDLE
;
227 ExposeTimeN
[0].value
= 0;
229 IDSetNumber(&ExposeTimeNP
, "Exposure cancelled.");
230 IDLog("Exposure Cancelled.\n");
234 ExposeTimeNP
.s
= IPS_IDLE
;
236 IUUpdateNumbers(&ExposeTimeNP
, values
, names
, n
);
238 IDLog("Exposure Time (ms) is: %g\n", ExposeTimeN
[0].value
);
240 handleExposure(NULL
);
244 if (!strcmp(TemperatureNP
.name
, name
))
246 if (checkPowerN(&TemperatureNP
))
249 TemperatureNP
.s
= IPS_IDLE
;
251 if (values
[0] < MIN_CCD_TEMP
|| values
[0] > MAX_CCD_TEMP
)
253 IDSetNumber(&TemperatureNP
, "Error: valid range of temperature is from %d to %d", MIN_CCD_TEMP
, MAX_CCD_TEMP
);
257 targetTemp
= values
[0];
259 // JM: Below, tell SBIG to update to temperature to targetTemp
260 // e.g. cam->setTemp(targetTemp);
263 // Now we set property to busy and poll in ISPoll for CCD temp
264 TemperatureNP
.s
= IPS_BUSY
;
266 IDSetNumber(&TemperatureNP
, "Setting CCD temperature to %+06.2f C", values
[0]);
267 IDLog("Setting CCD temperature to %+06.2f C\n", values
[0]);
272 if (!strcmp(FrameNP
.name
, name
))
274 if (checkPowerN(&FrameNP
))
278 IUUpdateNumbers(&FrameNP
, values
, names
, n
);
280 // JM: Below, we setup the frame size we want to take exposure of
281 // The way it is done depends on the camera API. Example below
282 //cam->m_StartX = (int) FrameN[0].value;
283 //cam->m_StartY = (int) FrameN[1].value;
284 //cam->m_Width = (int) FrameN[2].value;
285 //cam->m_Height = (int) FrameN[3].value;
287 IDSetNumber(&FrameNP
, NULL
);
293 if (!strcmp(BinningNP
.name
, name
))
295 if (checkPowerN(&BinningNP
))
299 BinningNP
.s
= IPS_OK
;
300 IUUpdateNumbers(&BinningNP
, values
, names
, n
);
302 // JM: Below we set the camera binning.
304 //cam->m_BinX = (int) BinningN[0].value;
305 //cam->m_BinY = (int) BinningN[1].value;
307 IDLog("Binning is: %.0f x %.0f\n", BinningN
[0].value
, BinningN
[1].value
);
313 void SBIGCam::ISStaticPoll(void *p
)
315 if (!((SBIGCam
*)p
)->isCCDConnected())
317 IEAddTimer (POLLMS
, SBIGCam::ISStaticPoll
, p
);
321 ((SBIGCam
*) p
)->ISPoll();
323 IEAddTimer (POLLMS
, SBIGCam::ISStaticPoll
, p
);
326 void SBIGCam::ISPoll()
332 switch (ExposeTimeNP
.s
)
340 // JM: Here we check the status of the camera (whether it's still capturing an image or has finished that)
341 // ISPoll is called once per second. e.g. below for how we do this for Apogee Cameras
343 readStatus = cam->read_Status();
346 IDLog("Error in exposure!\n");
347 ExposeTimeNP.s = IPS_IDLE;
348 ExposeTimeN[0].value = 0;
349 IDSetNumber(&ExposeTimeNP, "Error in exposure procedure.");
352 else if (readStatus == Camera_Status_ImageReady)
354 ExposeTimeN[0].value = 0;
355 ExposeTimeNP.s = IPS_OK;
356 IDSetNumber(&ExposeTimeNP, "Exposure done, downloading image...");
357 IDLog("Exposure done, downloading image...\n");
358 // grab and save image. Don't forget to call this!
363 ExposeTimeN
[0].value
--;
364 IDSetNumber(&ExposeTimeNP
, NULL
);
372 switch (TemperatureNP
.s
)
374 // JM: If we're not setting a new temperature (i.e. state is either ok or idle)
375 // We simply check for the temp every 5 seconds (this is why mtc = 5 and then decremented).
382 //TemperatureN[0].value = cam->read_Temperature();
383 IDSetNumber(&TemperatureNP
, NULL
);
388 // JM: If we're setting a new temperature, we continously check for it here untill we get
389 // a "close enough" value. This close enough value as seen below is the TEMP_THRESHOLD (0.25 C)
392 // Read in current CCD temp
393 //ccdTemp = cam->read_Temperature();
395 if (fabs(targetTemp
- ccdTemp
) <= TEMP_THRESHOLD
)
396 TemperatureNP
.s
= IPS_OK
;
399 TemperatureN
[0].value
= ccdTemp
;
400 IDSetNumber(&TemperatureNP
, NULL
);
409 /* Downloads the image from the CCD row by row and store them
411 N.B. No processing is done on the image */
412 void SBIGCam::grabImage()
418 char filename
[] = "/tmp/fitsXXXXXX";
420 if ((fd
= mkstemp(filename
)) < 0)
422 IDMessage(mydev
, "Error making temporary filename.");
423 IDLog("Error making temporary filename.\n");
428 // JM: allocate memory for buffer. In most cameras, the bit depth is 16 bit and so we use unsigned short
429 // But change if the bit depth is different
430 img_size
= SBIGFrame
.width
* SBIGFrame
.height
* sizeof(unsigned short);
432 SBIGFrame
.img
= (unsigned short *) malloc (img_size
);
434 if (SBIGFrame
.img
== NULL
)
436 IDMessage(mydev
, "Not enough memory to store image.");
437 IDLog("Not enough memory to store image.\n");
441 // JM: Here we fetch the image from the camera
443 /*if (!cam->GetImage( SBIGFrame.img , SBIGFrame.width, SBIGFrame.height ))
446 IDMessage(mydev, "GetImage() failed.");
447 IDLog("GetImage() failed.");
451 err
= writeFITS(filename
, errmsg
);
456 IDMessage(mydev
, errmsg
, NULL
);
464 int SBIGCam::writeFITS(char *filename
, char errmsg
[])
468 int bpp
, bpsl
, width
, height
;
472 ofp
= fits_open (filename
, "w");
475 sprintf(errmsg
, "Error: cannot open file for writing.");
479 // We're assuming bit depth is 16 (unsigned short). Change as desired.
480 width
= SBIGFrame
.width
;
481 height
= SBIGFrame
.height
;
482 bpp
= sizeof(unsigned short); /* Bytes per Pixel */
483 bpsl
= bpp
* SBIGFrame
.width
; /* Bytes per Line */
486 hdu
= create_fits_header (ofp
, width
, height
, bpp
);
489 sprintf(errmsg
, "Error: creating FITS header failed.");
492 if (fits_write_header (ofp
, hdu
) < 0)
494 sprintf(errmsg
, "Error: writing to FITS header failed.");
498 // JM: Here we're performing little endian to big endian (network order) conversion
499 // Since FITS is stored in big endian. If the camera provides the image buffer in big endian
500 // then there is no need for this conversion
501 for (int i
=0; i
< height
; i
++)
502 for (int j
=0 ; j
< width
; j
++)
503 SBIGFrame
.img
[width
* i
+ j
] = getBigEndian( (SBIGFrame
.img
[width
* i
+ j
]) );
505 // The "2" below is for two bytes (16 bit or unsigned short). Again, change as desired.
506 for (int i
= 0; i
< height
; i
++)
508 fwrite(SBIGFrame
.img
+ (i
* width
), 2, width
, ofp
->fp
);
512 nbytes
= nbytes
% FITS_RECORD_SIZE
;
515 while (nbytes
++ < FITS_RECORD_SIZE
)
519 if (ferror (ofp
->fp
))
521 sprintf(errmsg
, "Error: write error occured");
528 ExposeTimeNP
.s
= IPS_OK
;
529 IDSetNumber(&ExposeTimeNP
, NULL
);
530 IDLog("Loading FITS image...\n");
532 uploadFile(filename
);
538 void SBIGCam::uploadFile(char * filename
)
542 unsigned char *fitsData
, *compressedData
;
544 unsigned int i
=0, nr
= 0;
545 uLongf compressedBytes
=0;
549 if ( -1 == stat (filename
, &stat_p
))
551 IDLog(" Error occoured attempting to stat %s\n", filename
);
555 totalBytes
= stat_p
.st_size
;
556 fitsData
= (unsigned char *) malloc (sizeof(unsigned char) * totalBytes
);
557 compressedData
= (unsigned char *) malloc (sizeof(unsigned char) * totalBytes
+ totalBytes
/ 64 + 16 + 3);
559 if (fitsData
== NULL
|| compressedData
== NULL
)
561 IDLog("Error! low memory. Unable to initialize fits buffers.\n");
565 fitsFile
= fopen(filename
, "r");
567 if (fitsFile
== NULL
)
570 /* #1 Read file from disk */
571 for (i
=0; i
< totalBytes
; i
+= nr
)
573 nr
= fread(fitsData
+ i
, 1, totalBytes
- i
, fitsFile
);
577 IDLog("Error reading temporary FITS file.\n");
582 compressedBytes
= sizeof(char) * totalBytes
+ totalBytes
/ 64 + 16 + 3;
585 r
= compress2(compressedData
, &compressedBytes
, fitsData
, totalBytes
, 9);
588 /* this should NEVER happen */
589 IDLog("internal error - compression failed: %d\n", r
);
594 imageB
.blob
= compressedData
;
595 imageB
.bloblen
= compressedBytes
;
596 imageB
.size
= totalBytes
;
597 strcpy(imageB
.format
, ".fits.z");
599 IDSetBLOB (&imageBP
, NULL
);
602 free (compressedData
);
606 /* Initiates the exposure procedure */
607 void SBIGCam::handleExposure(void */
*p*/
)
610 int curFrame
= getOnSwitch(&FrameTypeSP
);
617 /*if (!cam->Expose( (int) ExposeTimeN[0].value, true ))
619 ExposeTimeNP.s = IPS_IDLE;
620 IDSetNumber(&ExposeTimeNP, "Light Camera exposure failed.");
621 IDLog("Light Camera exposure failed.\n");
626 /* BIAS frame is the same as DARK but with minimum period. i.e. readout from camera electronics.
630 /*if (!cam->Expose( 0.05 , false ))
632 ExposeTimeNP.s = IPS_IDLE;
633 IDSetNumber(&ExposeTimeNP, "Bias Camera exposure failed.");
634 IDLog("Bias Camera exposure failed.\n");
642 /*if (!cam->Expose( (int) ExposeTimeN[0].value , false ))
644 ExposeTimeNP.s = IPS_IDLE;
645 IDSetNumber(&ExposeTimeNP, "Dark Camera exposure failed.");
646 IDLog("Dark Camera exposure failed.\n");
653 /*if (!cam->Expose( (int) ExposeTimeN[0].value , true ))
655 ExposeTimeNP.s = IPS_IDLE;
656 IDSetNumber(&ExposeTimeNP, "Flat Camera exposure failed.");
657 IDLog("Flat Camera exposure failed.\n");
663 SBIGFrame
.frameType
= curFrame
;
664 SBIGFrame
.width
= (int) FrameN
[2].value
;
665 SBIGFrame
.height
= (int) FrameN
[3].value
;
666 SBIGFrame
.expose
= (int) ExposeTimeN
[0].value
;
667 SBIGFrame
.temperature
= TemperatureN
[0].value
;
668 SBIGFrame
.binX
= (int) BinningN
[0].value
;
669 SBIGFrame
.binY
= (int) BinningN
[1].value
;
671 ExposeTimeNP
.s
= IPS_BUSY
;
673 IDSetNumber(&ExposeTimeNP
, "Taking a %g seconds frame...", ExposeTimeN
[0].value
);
674 IDLog("Taking a frame...\n");
678 /* Retrieves basic data from the CCD upon connection like temperature, array size, firmware..etc */
679 void SBIGCam::getBasicData()
683 // Maximum resolution
685 // JM: Update here basic variables upon connection.
688 FrameN[2].max = cam->m_NumX;
689 FrameN[3].max = cam->m_NumY;
690 IUUpdateMinMax(&FrameNP);
693 BinningN[0].max = cam->m_MaxBinX;
694 BinningN[1].max = cam->m_MaxBinX;
695 IUUpdateMinMax(&BinningNP);
697 // Current Temperature
698 TemperatureN[0].value = cam->read_Temperature();
699 IDSetNumber(&TemperatureNP, NULL);*/
703 int SBIGCam::getOnSwitch(ISwitchVectorProperty
*sp
)
705 for (int i
=0; i
< sp
->nsp
; i
++)
707 if (sp
->sp
[i
].s
== ISS_ON
)
714 int SBIGCam::checkPowerS(ISwitchVectorProperty
*sp
)
716 if (PowerSP
.s
!= IPS_OK
)
718 if (!strcmp(sp
->label
, ""))
719 IDMessage (mydev
, "Cannot change property %s while the CCD is offline.", sp
->name
);
721 IDMessage (mydev
, "Cannot change property %s while the CCD is offline.", sp
->label
);
724 IDSetSwitch(sp
, NULL
);
731 int SBIGCam::checkPowerN(INumberVectorProperty
*np
)
733 if (PowerSP
.s
!= IPS_OK
)
735 if (!strcmp(np
->label
, ""))
736 IDMessage (mydev
, "Cannot change property %s while the CCD is offline.", np
->name
);
738 IDMessage (mydev
, "Cannot change property %s while the CCD is offline.", np
->label
);
741 IDSetNumber(np
, NULL
);
748 int SBIGCam::checkPowerT(ITextVectorProperty
*tp
)
751 if (PowerSP
.s
!= IPS_OK
)
753 if (!strcmp(tp
->label
, ""))
754 IDMessage (mydev
, "Cannot change property %s while the CCD is offline.", tp
->name
);
756 IDMessage (mydev
, "Cannot change property %s while the CCD is offline.", tp
->label
);
767 void SBIGCam::connectCCD()
776 PowerS
[0].s
= ISS_ON
;
777 PowerS
[1].s
= ISS_OFF
;
779 IDSetSwitch(&PowerSP
, "CCD is online. Retrieving basic data.");
780 IDLog("CCD is online. Retrieving basic data.\n");
786 PowerSP
.s
= IPS_IDLE
;
787 PowerS
[0].s
= ISS_OFF
;
788 PowerS
[1].s
= ISS_ON
;
789 IDSetSwitch(&PowerSP
, "Error: no cameras were detected.");
790 IDLog("Error: no cameras were detected.\n");
797 PowerS
[0].s
= ISS_OFF
;
798 PowerS
[1].s
= ISS_ON
;
799 PowerSP
.s
= IPS_IDLE
;
800 IDSetSwitch(&PowerSP
, "CCD is offline.");
802 // JM: Disconnect cam and clean up
803 // e.g. disConnectSBIG();
809 bool SBIGCam::initCamera()
811 // JM: Here, we do all camera initilization stuff and we establish a connection to the camera
812 // If everything goes well, we return true, otherwise false
813 // Please use IDLog(...) to report errors.
818 /* isCCDConnected: return 1 if we have a connection, 0 otherwise */
819 int SBIGCam::isCCDConnected(void)
821 return ((PowerS
[0].s
== ISS_ON
) ? 1 : 0);
824 FITS_HDU_LIST
* SBIGCam::create_fits_header (FITS_FILE
*ofp
, uint width
, uint height
, uint bpp
)
827 FITS_HDU_LIST
*hdulist
;
829 char temp_s
[FITS_CARD_SIZE
], expose_s
[FITS_CARD_SIZE
], binning_s
[FITS_CARD_SIZE
], pixel_s
[FITS_CARD_SIZE
], frame_s
[FITS_CARD_SIZE
];
830 char obsDate
[FITS_CARD_SIZE
];
832 snprintf(obsDate
, FITS_CARD_SIZE
, "DATE-OBS= '%s' /Observation Date UTC", timestamp());
834 hdulist
= fits_add_hdu (ofp
);
835 if (hdulist
== NULL
) return (NULL
);
837 hdulist
->used
.simple
= 1;
838 hdulist
->bitpix
= 16;
840 hdulist
->naxisn
[0] = width
;
841 hdulist
->naxisn
[1] = height
;
842 hdulist
->naxisn
[2] = bpp
;
843 // JM: Record here the minimum and maximum pixel values
844 /*hdulist->used.datamin = min();
845 hdulist->datamin = min();
846 hdulist->used.datamax = max();
847 hdulist->datamax = max();*/
848 hdulist
->used
.bzero
= 1;
849 hdulist
->bzero
= 0.0;
850 hdulist
->used
.bscale
= 1;
851 hdulist
->bscale
= 1.0;
853 snprintf(temp_s
, FITS_CARD_SIZE
, "CCD-TEMP= %g / degrees celcius", SBIGFrame
.temperature
);
854 snprintf(expose_s
, FITS_CARD_SIZE
, "EXPOSURE= %d / milliseconds", SBIGFrame
.expose
);
855 snprintf(binning_s
, FITS_CARD_SIZE
, "BINNING = '(%d x %d)'", SBIGFrame
.binX
, SBIGFrame
.binY
);
856 //sprintf(pixel_s, "PIX-SIZ = '%.0f microns square'", PixelSizeN[0].value);
857 switch (SBIGFrame
.frameType
)
860 strcpy(frame_s
, "FRAME = 'Light'");
863 strcpy(frame_s
, "FRAME = 'Bias'");
866 strcpy(frame_s
, "FRAME = 'Flat Field'");
869 strcpy(frame_s
, "FRAME = 'Dark'");
873 fits_add_card (hdulist
, frame_s
);
874 fits_add_card (hdulist
, temp_s
);
875 fits_add_card (hdulist
, expose_s
);
876 //fits_add_card (hdulist, pixel_s);
878 // JM: If there is a way to get the specific model of the camera, use that.
879 fits_add_card (hdulist
, "INSTRUME= 'SBIG CCD'");
880 fits_add_card (hdulist
, obsDate
);