/* $Id: qfits_ext.c,v 1.7 2002/01/15 17:44:12 fors Exp $ * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * COPYRIGHT (c) 2001 European Southern Observatory * LICENSE: GNU General Public License version 2 or later * * PROJECT: VLT Data Flow System * AUTHOR: Ralf Palsa -- ESO/DMD/DPG * SUBSYSTEM: Instrument pipelines * * PURPOSE: * DESCRIPTION: * * $Name: fsmosaic-1_0 $ * $Revision: 1.7 $ * ---------------------------------------------------------------------------- */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "qfits_ext.h" /* * Define the FITS pixel data types */ typedef unsigned char byte; typedef short int16; typedef int int32; typedef float float32; typedef double float64; /* * @memo * Reverse the order of a sequence of bytes. * * @return Nothing. * * @param s Start address of the byte sequence. * @param n Number of successive bytes to swap. * * @doc * The function reverses the order of the bytes pointed to by \textbf{s} * and it's immediate \textbf{n} successors. The bytes are reversed in place. * * @author R. Palsa */ inline static void bswap(void *s, size_t n) { unsigned char *c0 = s; unsigned char *c1 = (unsigned char *)s + n; unsigned char t; while (c0 < c1) { t = *c0; *c0++ = *--c1; *c1 = t; } return; } /* * @memo * Get size of a pixel in bytes. * * @return The number of bytes available to store a pixel value. If the * pixel type is not supported 0 is returned. * * @param pixel_format FITS pixel format code. * * @doc * The function translates the FITS pixel format code into the number * of bytes needed to store a pixel value. * * @author R. Palsa */ inline static size_t fits_sizeof_pixel(int pixel_format) { size_t bytes; switch (pixel_format) { case BPP_8_UNSIGNED: bytes = 1; break; case BPP_16_SIGNED: bytes = 2; break; case BPP_32_SIGNED: bytes = 4; break; case BPP_IEEE_FLOAT: bytes = 4; break; case BPP_IEEE_DOUBLE: bytes = 8; break; default: bytes = 0; break; } return bytes; } /* * @memo * Convert pixel value data format from FITS to host. * * @return The function returns a pointer to the target pixel buffer if the * conversion was successful. In case of any error a #NULL# pointer is * returned. * * @param target Target pixel buffer. * @param source Source pixel buffer. * @param size Number of pixels to be converted. * @param fits_format FITS pixel format code. * @param bscale Data scaling factor. * @param bzero Data offset. * * @doc * The function converts the pixel data provided by the source pixel buffer * \textbf{source} from the FITS pixel format specified by the format code * \textbf{fits\_format} to the type \textbf{pixel\_t}. If the endianess * of the FITS format differs from the one of the host the byte order * is changed appropriately. * * The function read \textbf{size} pixels from the input buffer and writes * it, after the convversion to the output pixel buffer \textbf{target}. * The function does not check if the output pixel buffer is large enough * to accept all pixels from the input buffer. Also, there is no check * if the input buffer provides at least \textbf{size} pixels. This is * the caller's responsibility. * * Finally the converted pixel values are scaled and offset according to * the provided scaling factor \textbf{bscale} and zero point \textbf{bzero}. * * @author R. Palsa */ inline static pixel_t *fits_convert_to_host(pixel_t *target, byte *source, size_t size, int fits_format, double bscale, double bzero) { register size_t i; int16 *sval; int32 *ival; float32 *fval; float64 *dval; pixel_t *pixel = target; assert(target != 0); assert(source != 0); if (bscale < BSCALE_MIN) return 0; /* * Convert the pixel values to pixel_t. Apart from the byte pixel * format (bytes atmost need sacling and offset adjustment) the pixels * endianess is also changed here if necessary. */ switch (fits_format) { case BPP_8_UNSIGNED: for (i = 0; i < size; i++) *pixel++ = (pixel_t)(bscale * (double)(*source++) + bzero); break; case BPP_16_SIGNED: sval = (int16 *)source; for (i = 0; i < size; i++) { #ifndef WORDS_BIGENDIAN bswap((void *)sval, SIZEOF_SHORT); #endif *pixel++ = (pixel_t)(bscale * *sval++ + bzero); } break; case BPP_32_SIGNED: ival = (int32 *)source; for (i = 0; i < size; i++) { #ifndef WORDS_BIGENDIAN bswap((void *)ival, SIZEOF_INT); #endif *pixel++ = (pixel_t)(bscale * *ival++ + bzero); } break; case BPP_IEEE_FLOAT: fval = (float32 *)source; for (i = 0; i < size; i++) { #ifndef WORDS_BIGENDIAN bswap((void *)fval, SIZEOF_FLOAT); #endif *pixel++ = (pixel_t)(bscale * *fval++ + bzero); } break; case BPP_IEEE_DOUBLE: dval = (float64 *)source; for (i = 0; i < size; i++) { #ifndef WORDS_BIGENDIAN bswap((void *)dval, SIZEOF_DOUBLE); #endif *pixel++ = (pixel_t)(bscale * *dval++ + bzero); } break; default: return 0; break; } return target; } /* * @memo * Convert pixel value dataformat from host to FITS. * * @return The function returns a pointer to the target pixel buffer if the * conversion was successful. In case of any error a #NULL# pointer is * returned. * * @param target Target pixel buffer. * @param source Source pixel buffer. * @param size Number of pixels to be converted. * @param fits_format FITS pixel format code. * @param bscale Data scaling factor. * @param bzero Data offset. * * @doc * The function converts the pixel data provided by the source pixel buffer * \textbf{source} from the pixel type \textbf{pixel\_t} to the FITS pixel * format specified by the format code \textbf{fits\_format}. If the * endianess of the FITS format differs from the one of the host the byte * order is changed appropriately. * * The function read \textbf{size} pixels from the input buffer and writes * it, after the convversion to the output pixel buffer \textbf{target}. * The function does not check if the output pixel buffer is large enough * to accept all pixels from the input buffer. Also, there is no check * if the input buffer provides at least \textbf{size} pixels. This is * the caller's responsibility. * * @author R. Palsa */ inline static byte *fits_convert_to_fits(byte *target, pixel_t *source, size_t size, int fits_format, double bscale, double bzero) { register size_t i; pixel_t *s; double sscaled; assert(target != 0); assert(source != 0); if (bscale < BSCALE_MIN) return 0; /* * Convert the pixel values from pixel_t to FITS. The pixel values are * clipped if necessary. Apart from the byte pixel format the pixels * endianess is also changed here if needed. */ switch (fits_format) { case BPP_8_UNSIGNED: for (i = 0; i < size; i++) { byte *t; s = source + i; t = target + i; sscaled = ((double)*s - bzero) / bscale; if (sscaled > UCHAR_MAX) *t = (byte)UCHAR_MAX; else if (sscaled < (pixel_t)0) *t = (byte)0; else *t = (byte)floor(sscaled + 0.5); } break; case BPP_16_SIGNED: for (i = 0; i < size; i ++) { int16 *t; s = source + i; t = (int16 *)target + i; sscaled = ((double)*s - bzero) / bscale; if (sscaled > SHRT_MAX) *t = (int16)SHRT_MAX; else if (sscaled < SHRT_MIN) *t = (int16)SHRT_MIN; else *t = (int16)floor(sscaled + 0.5); #ifndef WORDS_BIGENDIAN bswap((void *)t, SIZEOF_SHORT); #endif } break; case BPP_32_SIGNED: for (i = 0; i < size; i ++) { int32 *t; s = source + i; t = (int32 *)target + i; sscaled = ((double)*s - bzero) / bscale; if (sscaled > INT_MAX) *t = (int32)INT_MAX; else if (sscaled < INT_MIN) *t = (int32)INT_MIN; else *t = (int32)floor(sscaled + 0.5); #ifndef WORDS_BIGENDIAN bswap((void *)t, SIZEOF_INT); #endif } break; case BPP_IEEE_FLOAT: for (i = 0; i < size; i ++) { float32 *t; s = source + i; t = (float32 *)target + i; sscaled = ((double)*s - bzero) / bscale; *t = (float32)sscaled; #ifndef WORDS_BIGENDIAN bswap((void *)t, SIZEOF_FLOAT); #endif } break; case BPP_IEEE_DOUBLE: for (i = 0; i < size; i ++) { float64 *t; s = source + i; t = (float64 *)target + i; sscaled = ((double)*s - bzero) / bscale; *t = (float64)sscaled; #ifndef WORDS_BIGENDIAN bswap((byte *)t, SIZEOF_DOUBLE); #endif } break; default: return 0; break; } return target; } /** * @memo * Create a new FITS file information object. * * @return The function returns a pointer to the allocated object if no error * occurred, otherwise a #NULL# pointer is returned. * * @doc * The function allocates the appropriate amount of memory for a * FITS file information object of type \textbf{fits\_info\_t} and * initializes the created object. * * The initial values for the data offset and the scale factor are * 0. and 1. respectively. * * @author R. Palsa */ inline fits_info_t *fits_info_new(void) { fits_info_t *info = (fits_info_t *)malloc(sizeof *info); if (info) { info->naxes = 0; info->naxis = 0; info->bits_per_pixel = 0; info->header_size = 0; info->bscale = 1.; info->bzero = 0.; info->time_flag = 0; } return info; } /** * @memo * Destroy a FITS information object. * * @return Nothing. * * @param info FITS file information object. * * @doc * The function deallocates the memory used by the FITS information * object \textbf{info}. If memory was allocated for the \textbf{naxis} * member this memory is released prior to the object destruction. * * @author R. Palsa */ inline void fits_info_del(fits_info_t *info) { if (!info) return; if (!info->naxis) free(info->naxis); free(info); return; } /** * @memo * Get basic FITS file information. * * @return The function returns a pointer to \textbf{fits\_info\_t} containing * the FITS file informations. In case of any error a #NULL# pointer is * returned * * @param filename Pathname of the file to be queried. * * @doc * The function retrieves basic information about the primary data array * from the primary FITS header of the file \textbf{filename}. * The retrieved data is * \begin{itemize} * \item the number of axes, * \item the number of pixels fore each axis, * \item the pixel data format, * \item the data scale factor, * \item the data offset and * \item the size of the header in bytes. * \end{itemize} * * @author R. Palsa */ fits_info_t *fits_get_file_info(const char *filename) { int ival; fits_info_t *info = fits_info_new(); if (info) { char *s; if (access(filename, R_OK)) { fits_info_del(info); return 0; } /* * Size of header in bytes */ if (qfits_get_hdrinfo((char *)filename, 0, 0, &ival)) { fits_info_del(info); return 0; } info->header_size = ival; /* * Determine the pixel format */ if (!(s = qfits_query_hdr((char *)filename, "BITPIX"))) { fits_info_del(info); return 0; } else info->bits_per_pixel = atoi(s); /* * Get the number of axes and the number of pixels for each * axis. */ if (!(s = qfits_query_hdr((char *)filename, "NAXIS"))) { fits_info_del(info); return 0; } info->naxes = atoi(s); if (info->naxes > 0) { register int i; char key[8]; if (!(info->naxis = (int *)calloc(info->naxes, sizeof(int)))) { fits_info_del(info); return 0; } for (i = 1; i <= info->naxes; i++) { sprintf(key, "NAXIS%-d", i); if (!(s = qfits_query_hdr((char *)filename, key))) { fits_info_del(info); return 0; } else info->naxis[i - 1] = atoi(s); } } /* * Get data offset and scale factor. If they are not given * the offset is set to 0 and the scale factor is set to 1. */ if ((s = qfits_query_hdr((char *)filename, "BZERO"))) info->bzero = atof(s); if ((s = qfits_query_hdr((char *)filename, "BSCALE"))) info->bscale = atof(s); } return info; } /** * @memo * Load pixel data from a FITS file. * * @return The function returns a pointer to the created pixel buffer if * no error occured, otherwise a #NULL# pointer is returned. * * @param filename Path to the input FITS file. * @param info FITS file information object. * * @doc * The function creates a pixel buffer according to the size information * of the FITS primary data array provided by the FITS information object * \textbf{info}. The buffer is then filled with the pixel data from the * input FITS file given by \textbf{filename}. * * Note that the amount of data which is loaded into the buffer is also * determined from \textbf{info}. It is the caller's responsibility to * ensure that the size information provided by \textbf{info} matches * the input file. * * When loading the pixel data into the buffer, the function converts * the FITS IEEE data format to the hosts native format. * * @author R. Palsa */ inline pixel_t *fits_load_pixel_data(const char *filename, fits_info_t *info) { byte *raw_data; int i; size_t sz = 1.; size_t nbytes; pixel_t *pixels; FILE *stream; assert(filename != 0); assert(info != 0); /* * Required buffer size in bytes. */ for (i = 0; i < info->naxes; i++) sz *= info->naxis[i]; nbytes = sz * fits_sizeof_pixel(info->bits_per_pixel); /* * Read pixel data into the pixel buffer */ if ((stream = fopen(filename, "r")) == 0) return 0; if ((raw_data = (byte *)malloc(nbytes * sizeof(byte))) == 0) { fclose(stream); return 0; } fseek(stream, info->header_size, SEEK_SET); if (fread(raw_data, 1, nbytes, stream) != nbytes) { free(raw_data); fclose(stream); return 0; } else fclose(stream); /* * Create the output pixel buffer and fill it with the pixel data * after conversion from FITS IEEE to pixel_t. */ if ((pixels = (pixel_t *)malloc(sz * sizeof(pixel_t))) == 0) { free(raw_data); return 0; } if (!fits_convert_to_host(pixels, raw_data, sz, info->bits_per_pixel, info->bscale, info->bzero)) { free(raw_data); free(pixels); return 0; } free(raw_data); return pixels; } /** * @memo * Save pixels to a FITS primary data array. * * @return The function returns #EXIT_SUCCESS# if the pixel data was * successfully written, otherwise the function returns #EXIT_FAILURE#. * * @param filename Pathname of the output FITS file. * @param pixels Pixel data to be written. * @param size Number of pixels. * @param fits_format Output FITS pixel format code. * @param bscale Data scaling factor. * @param bzero Data offset. * * @doc * The function appends the pixel data provided by \textbf{pixels} to the * file \textbf{filename}. The function expects \textbf{size} pixels * in the input pixel buffer. Before appending the pixel data to the * output file the pixel data is converted to the output FITS pixel * format given by \textbf{fits\_fotmat}. * * @author R. Palsa */ inline int fits_save_pixel_data(const char *filename, pixel_t *pixels, size_t size, int fits_format, double bscale, double bzero) { byte *raw_data; size_t nbytes = size * fits_sizeof_pixel(fits_format); FILE *stream; /* * Create output byte buffer. Fill it with the data converted to * FITS format. */ if (nbytes == 0) return EXIT_FAILURE; if ((raw_data = (byte *)malloc(nbytes * sizeof(byte))) == 0) return EXIT_FAILURE; else if (!fits_convert_to_fits(raw_data, pixels, size, fits_format, bscale, bzero)) { free(raw_data); return EXIT_FAILURE; } /* * Write the pixel data to disk. */ if (!(stream = fopen(filename, "a"))) { free(raw_data); return EXIT_FAILURE; } if (fwrite(raw_data, sizeof(byte), nbytes, stream) != nbytes) { free(raw_data); fclose(stream); return EXIT_FAILURE; } free(raw_data); fclose(stream); return EXIT_SUCCESS; } /** * @memo * Load pixels from a FITS primary data array. * * @return The function returns a pointer to the created image if no error * occurred, otherwise a #NULL# pointer is returned. * * @param filename Pathname of the input FITS file. * * @doc * The function creates an image object from the pixel data of the FITS * primary data array of \textbf{filename}. The function requires that * the number of axes of the primary data array is two and that the * number of pixels is less or equal #IMAGE_HSIZE_MAX# and #IMAGE_VSIZE_MAX# * respectively. * * @author R. Palsa */ inline image_t *fits_load_image(const char *filename) { image_t *image; pixel_t *data; fits_info_t *info; if (!(info = fits_get_file_info(filename))) return 0; if (info->naxes != 2) { fits_info_del(info); return 0; } if (info->naxis[0] < 1 || info->naxis[0] > IMAGE_HSIZE_MAX || info->naxis[1] < 1 || info->naxis[1] > IMAGE_VSIZE_MAX) { fits_info_del(info); return 0; } /* * Create the image object and load the pixel data from * the primary data array. */ if (!(image = image_new(info->naxis[0], info->naxis[1]))) { fits_info_del(info); return 0; } if (!(data = fits_load_pixel_data(filename, info))) { fits_info_del(info); image_del(image); return 0; } else image->data = data; return image; } /** * @memo * Save an image as FITS file. * * @return The function returns #EXIT_SUCCESS# if no error occurred, otherwise * the return value is #EXIT_FAILURE#. * * @param filename Pathname of the output FITS file. * @param header FITS header to be saved with the image data. * @param image Image to be saved. * @param bits_per_pixel FITS pixel format code. * * @doc * The function saves the image \textbf{image} together with the * header \textbf{header} as primary header and data array to the * FITS file \textbf{filename}. The output FITS pixel format is * provided by \textbf{bits_per_pixel} and may be one of the * following types: * \begin{itemize} * \item #BPP_8_UNSIGNED#, * \item #BPP_16_SIGNED#, * \item #BPP_32_SIGNED#, * \item #BPP_IEEE_FLOAT#, * \item #BPP_IEEE_DOUBLE#, or * \item #BPP_DEFAULT# * \end{itemize} * * The type code #BPP_DEFAULT# matches the internal pixel format, i.e. * using this type will guarantee no loss in presicion during output. * For all other output types a loss in precision might occur. * * @author */ inline int fits_save_image(const char *filename, qfits_header *header, image_t *image, int bits_per_pixel) { register int i; char card[FITS_CARD_SIZE + 1]; long naxis; double bscale, bzero; FILE *stream; assert(filename != 0); assert(header != 0 && image != 0); assert(fits_sizeof_pixel(bits_per_pixel) != 0); /* * Do necessary changes in the output header. */ sprintf(card, "%d", bits_per_pixel); qfits_header_mod(header, "BITPIX", card, "# bits per pix value"); if ((naxis = qfits_header_getint(header, "NAXIS", -1)) == -1) return EXIT_FAILURE; if (naxis > 2) { for (i = 3; i <= naxis; i++) { char keyname[FITS_CARD_SIZE + 1]; sprintf(keyname, "NAXIS%-d", i); qfits_header_del(header, keyname); } } /* * Just to be sure not to end up with a wrong MD5 signature in the * output header. */ qfits_header_del(header, "DATAMD5"); /* * Change dimensions, sizes and scaling factors. */ qfits_header_mod(header, "NAXIS", "2", "# of axes in data array"); sprintf(card, "%d", image->hsize); qfits_header_mod(header, "NAXIS1", card, "# of pixels"); sprintf(card, "%d", image->vsize); qfits_header_mod(header, "NAXIS2", card, "# of pixels"); /* * Get pixel value offset and scaling from the header. If the * keywords are not present use the defaults. */ bzero = qfits_header_getdouble(header, "BZERO", 0.); bscale = qfits_header_getdouble(header, "BSCALE", 1.); /* * Write the header to the output file. */ if ((stream = fopen(filename, "w")) == 0) return EXIT_FAILURE; else { qfits_header_dump(header, stream); fclose(stream); } /* * Save the pixel data */ if (fits_save_pixel_data(filename, image->data, image->hsize * image->vsize, bits_per_pixel, bscale, bzero) == EXIT_FAILURE) { remove(filename); return EXIT_FAILURE; } /* * Fill up the file with zeros up to the next FITS block boundary. */ qfits_zeropad((char *)filename); return EXIT_SUCCESS; } /** * @memo * Modify the value string of a header keyword. * * @return The function returns #EXIT_SUCCESS# if no error occured, otherwise * the return value is #EXIT_FAILURE#. * * @param header FITS header object. * @param keyword Keyword name. * @param value Value string. * * @doc * * @author R. Palsa */ void fits_header_set_value(qfits_header *header, const char *keyword, const char *value) { char comment[FITS_CARD_SIZE + 1]; char *s; if ((s = qfits_header_getcom(header, (char *)keyword))) strcpy(comment, s); if (s) qfits_header_mod(header, (char *)keyword, (char *)value, comment); else qfits_header_mod(header, (char *)keyword, (char *)value, 0); return; }