/*************************************************************************** * GFitsImage.cpp - Abstract FITS image base class * * ----------------------------------------------------------------------- * * copyright (C) 2008-2013 by Juergen Knoedlseder * * ----------------------------------------------------------------------- * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ /** * @file GFitsImage.cpp * @brief Abstract FITS image base class implementation * @author Juergen Knoedlseder */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include "GException.hpp" #include "GFitsCfitsio.hpp" #include "GFits.hpp" #include "GFitsImage.hpp" #include "GTools.hpp" /* __ Method name definitions ____________________________________________ */ #define G_NAXES "GFitsImage::naxes(int)" #define G_OPEN_IMAGE "GFitsImage::open(void*)" #define G_LOAD_IMAGE "GFitsImage::load_image(int,void*,void*,int*)" #define G_SAVE_IMAGE "GFitsImage::save_image(int,void*)" #define G_OFFSET_1D "GFitsImage::offset(int&)" #define G_OFFSET_2D "GFitsImage::offset(int&,int&)" #define G_OFFSET_3D "GFitsImage::offset(int&,int&,int&)" #define G_OFFSET_4D "GFitsImage::offset(int&,int&,int&,int&)" /* __ Macros _____________________________________________________________ */ /* __ Coding definitions _________________________________________________ */ /* __ Debug definitions __________________________________________________ */ /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void constructor * * Construct instance of an empty image. No header cards are present in an * empty image. ***************************************************************************/ GFitsImage::GFitsImage(void) : GFitsHDU() { // Initialise class members for clean destruction init_members(); // Return return; } /***********************************************************************//** * @brief 1D image constructor * * @param[in] bitpix Number of Bits per pixel (negative is floating point). * @param[in] nx Number of pixels. * * Construct 1D instance of GFitsImage by specifying the number of pixels. * This method also adds the relevant header cards. ***************************************************************************/ GFitsImage::GFitsImage(const int& bitpix, const int& nx) : GFitsHDU() { // Initialise class members for clean destruction init_members(); // Store number of Bits per pixel m_bitpix = bitpix; // Set number of axes m_naxis = 1; // Set image dimensions m_naxes = new long[m_naxis]; m_naxes[0] = nx; m_num_pixels = nx; // Initialise header init_image_header(); // Return return; } /***********************************************************************//** * @brief 2D image constructor * * @param[in] bitpix Number of Bits per pixel (negative is floating point). * @param[in] nx Number of pixels in first dimension. * @param[in] ny Number of pixels in second dimension. * * Construct 2D instance of GFitsImage by specifying the number of pixels * in each dimension. This method also adds the relevant header cards. ***************************************************************************/ GFitsImage::GFitsImage(const int& bitpix, const int& nx, const int& ny) : GFitsHDU() { // Initialise class members for clean destruction init_members(); // Store number of Bits per pixel m_bitpix = bitpix; // Set number of axes m_naxis = 2; // Set image dimensions m_naxes = new long[m_naxis]; m_naxes[0] = nx; m_naxes[1] = ny; m_num_pixels = nx * ny; // Initialise header init_image_header(); // Return return; } /***********************************************************************//** * @brief 3D image constructor * * @param[in] bitpix Number of Bits per pixel (negative is floating point). * @param[in] nx Number of pixels in first dimension. * @param[in] ny Number of pixels in second dimension. * @param[in] nz Number of pixels in third dimension. * * Construct 3D instance of GFitsImage by specifying the number of pixels * in each dimension. This method also adds the relevant header cards. ***************************************************************************/ GFitsImage::GFitsImage(const int& bitpix, const int& nx, const int& ny, const int& nz) : GFitsHDU() { // Initialise class members for clean destruction init_members(); // Store number of Bits per pixel m_bitpix = bitpix; // Set number of axes m_naxis = 3; // Set image dimensions m_naxes = new long[m_naxis]; m_naxes[0] = nx; m_naxes[1] = ny; m_naxes[2] = nz; m_num_pixels = nx * ny * nz; // Initialise header init_image_header(); // Return return; } /***********************************************************************//** * @brief 4D image constructor * * @param[in] bitpix Number of Bits per pixel (negative is floating point). * @param[in] nx Number of pixels in first dimension. * @param[in] ny Number of pixels in second dimension. * @param[in] nz Number of pixels in third dimension. * @param[in] nt Number of pixels in forth dimension. * * Construct 4D instance of GFitsImage by specifying the number of pixels * in each dimension. This method also adds the relevant header cards. ***************************************************************************/ GFitsImage::GFitsImage(const int& bitpix, const int& nx, const int& ny, const int& nz, const int& nt) : GFitsHDU() { // Initialise class members for clean destruction init_members(); // Store number of Bits per pixel m_bitpix = bitpix; // Set number of axes m_naxis = 4; // Set image dimensions m_naxes = new long[m_naxis]; m_naxes[0] = nx; m_naxes[1] = ny; m_naxes[2] = nz; m_naxes[3] = nt; m_num_pixels = nx * ny * nz * nt; // Initialise header init_image_header(); // Return return; } /***********************************************************************//** * @brief Constructor * * @param[in] bitpix Number of Bits per pixel (negative is floating point). * @param[in] naxis Image dimensions [0,1,2,3,4]. * @param[in] naxes Pointer to array giving number of pixels in each dimension. * * Construct instance of GFitsImage by specifying the image dimension and * the number of pixels in each dimension. This method also adds the relevant * header cards. ***************************************************************************/ GFitsImage::GFitsImage(const int& bitpix, const int& naxis, const int* naxes) : GFitsHDU() { // Initialise class members for clean destruction init_members(); // Store number of Bits per pixel m_bitpix = bitpix; // Store number of axes m_naxis = naxis; // Copy number of pixels in each dimension and calculate the total // number of pixels if (m_naxis > 0) { m_naxes = new long[m_naxis]; m_num_pixels = 1; for (int i = 0; i < m_naxis; ++i) { m_naxes[i] = naxes[i]; m_num_pixels *= naxes[i]; } } // Initialise header init_image_header(); // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] image FITS image. ***************************************************************************/ GFitsImage::GFitsImage(const GFitsImage& image) : GFitsHDU(image) { // Initialise class members for clean destruction init_members(); // Copy members copy_members(image); // Return return; } /***********************************************************************//** * @brief Destructor ***************************************************************************/ GFitsImage::~GFitsImage(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] image FITS image. * @return FITS image. ***************************************************************************/ GFitsImage& GFitsImage::operator=(const GFitsImage& image) { // Execute only if object is not identical if (this != &image) { // Copy base class members this->GFitsHDU::operator=(image); // Free members free_members(); // Initialise private members for clean destruction init_members(); // Copy members copy_members(image); } // endif: object was not identical // Return this object return *this; } /*========================================================================== = = = Public methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Return dimension of an image axis * * @param[in] axis Image axis [0,...,naxis()-1]. * * @exception GException::out_of_range * Image axis not valid. ***************************************************************************/ int GFitsImage::naxes(const int& axis) const { // Check if axis is within the range #if defined(G_RANGE_CHECK) if (axis < 0 || axis >= naxis()) { throw GException::out_of_range(G_NAXES, "Image axis", axis, naxis()); } #endif // Get axis dimension int dim = m_naxes[axis]; // Return axis dimension return dim; } /***********************************************************************//** * @brief Set nul value * * @param[in] value Nul value. * * @todo To correctly reflect the nul value in the data, the image should * be reloaded. However, the image may have been changed, so in principle * saving is needed. However, we may not want to store the image, hence saving * is also not desired. We thus have to develop a method to update the * image information for a new nul value in place ... ***************************************************************************/ void GFitsImage::nulval(const void* value) { // Allocate nul value alloc_nulval(value); // Update image //TODO // Return return; } /***********************************************************************//** * @brief Print column information * * @param[in] chatter Chattiness (defaults to NORMAL). * @return String containing column information. * * @todo Format and cfitsio information is mainly for debugging. This could * be vanish in a more stable version of the code, or it could be compiled * in conditionally using a debug option. ***************************************************************************/ std::string GFitsImage::print(const GChatter& chatter) const { // Initialise result string std::string result; // Continue only if chatter is not silent if (chatter != SILENT) { // Append header result.append("=== GFitsImage ==="); // Append HDU information result.append("\n"+print_hdu(chatter)); // Append image dimensions result.append("\n"+gammalib::parformat("Image type")); result.append(typecode(type())); result.append("\n"+gammalib::parformat("Number of dimensions")); result.append(gammalib::str(naxis())); result.append("\n"+gammalib::parformat("Number of image pixels")); result.append(gammalib::str(size())); for (int i = 0; i < naxis(); ++i) { result.append("\n"+gammalib::parformat("Number of bins in "+gammalib::str(i))); result.append(gammalib::str(naxes(i))); } // NORMAL: Append header information if (chatter >= NORMAL) { result.append(+"\n"+m_header.print(gammalib::reduce(chatter))); } } // endif: chatter was not silent // Return result return result; } /*========================================================================== = = = Protected methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class members ***************************************************************************/ void GFitsImage::init_members(void) { // Initialise members m_bitpix = 8; m_naxis = 0; m_naxes = NULL; m_num_pixels = 0; m_anynul = 0; // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] image FITS image to copy ***************************************************************************/ void GFitsImage::copy_members(const GFitsImage& image) { // Copy attributes m_bitpix = image.m_bitpix; m_naxis = image.m_naxis; m_num_pixels = image.m_num_pixels; m_anynul = image.m_anynul; // Copy axes m_naxes = NULL; if (image.m_naxes != NULL && m_naxis > 0) { m_naxes = new long[m_naxis]; for (int i = 0; i < m_naxis; ++i) { m_naxes[i] = image.m_naxes[i]; } } // Return return; } /***********************************************************************//** * @brief Delete class members ***************************************************************************/ void GFitsImage::free_members(void) { // Free memory if (m_naxes != NULL) delete [] m_naxes; // Mark memory as free m_naxes = NULL; // Return return; } /***********************************************************************//** * @brief Open FITS image * * @param[in] vptr FITS file pointer * * Open FITS image in FITS file. Opening means connecting the FITS file * pointer to the image and reading the image and axes dimensions. ***************************************************************************/ void GFitsImage::data_open(void* vptr) { // Open image open_image(vptr); // Return return; } /***********************************************************************//** * @brief Save FITS image * * Saves the image into the FITS file. ***************************************************************************/ void GFitsImage::data_save(void) { // Save image save_image(type(), pixels()); // Return return; } /***********************************************************************//** * @brief Close FITS image * * Closing a FITS image resets the object into its initial state. Closing * does NOT save the image into the FITS file. Use the save method for this * purpose. * * @todo Not sure that this is efficient at this level since the pixel array * will not be deallocated!!! ***************************************************************************/ void GFitsImage::data_close(void) { // Free members free_members(); // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief Connect FITS image * * @param[in] vptr FITS file pointer * * Connects a FITS file pointer to an image. This method actually does * nothing since any GFitsImage can directly access the FITS file pointer * that is stored in the GFitsHDU base class. ***************************************************************************/ void GFitsImage::data_connect(void* vptr) { // Return return; } /***********************************************************************//** * @brief Initialise image header * * Initialises the image header by setting the default header cards. This * method requires the members m_bitpix, m_naxis, and m_naxes to be set * previously. ***************************************************************************/ void GFitsImage::init_image_header(void) { // Set image header keywords m_header.append(GFitsHeaderCard("XTENSION", "IMAGE ", "IMAGE extension")); m_header.append(GFitsHeaderCard("BITPIX", bitpix(), "number of bits per data pixel")); m_header.append(GFitsHeaderCard("NAXIS", naxis(), "number of data axes")); for (int i = 0; i < naxis(); ++i) { std::ostringstream s_key; std::ostringstream s_comment; s_key << "NAXIS" << (i+1); s_comment << "length of data axis " << (i+1); m_header.append(GFitsHeaderCard(s_key.str(), naxes(i), s_comment.str())); } m_header.append(GFitsHeaderCard("PCOUNT", 0, "required keyword; must = 0")); m_header.append(GFitsHeaderCard("GCOUNT", 1, "required keyword; must = 1")); // Return return; } /***********************************************************************//** * @brief Open Image * * @param[in] vptr FITS file void pointer. * * @exception GException::fits_error * FITS error. * * Open FITS image in FITS file. Opening means connecting the FITS file * pointer to the image and reading the image and axes dimensions. ***************************************************************************/ void GFitsImage::open_image(void* vptr) { // Move to HDU gammalib::fits_move_to_hdu(G_OPEN_IMAGE, vptr); // Save the FITS file pointer and the HDU number FPTR_COPY(m_fitsfile, vptr); m_hdunum = FPTR(vptr)->HDUposition; // Get the image dimensions int status = 0; status = __ffgidm(FPTR(m_fitsfile), &m_naxis, &status); if (status != 0) { throw GException::fits_error(G_OPEN_IMAGE, status); } // Reset number of image pixels m_num_pixels = 0; // Get the axes dimensions if (m_naxis > 0) { // Allocate memory for axes dimensions if (m_naxes != NULL) delete [] m_naxes; m_naxes = new long[m_naxis]; // Get the axes dimensions status = __ffgisz(FPTR(m_fitsfile), m_naxis, m_naxes, &status); if (status != 0) { throw GException::fits_error(G_OPEN_IMAGE, status); } // Calculate number of image pixels m_num_pixels = 1; for (int i = 0; i < m_naxis; ++i) { m_num_pixels *= m_naxes[i]; } } // endif: there is an image // Return return; } /***********************************************************************//** * @brief Load FITS image * * @param[in] datatype Datatype of pixels to be saved. * @param[in] pixels Pixel array to be saved. * @param[in] nulval Pointer to pixel nul value. * @param[out] anynul Number of nul values encountered during loading. * * @exception GException::fits_error * FITS error. * * Load image pixels from FITS file. ***************************************************************************/ void GFitsImage::load_image(int datatype, const void* pixels, const void* nulval, int* anynul) { // Move to HDU move_to_hdu(); // Load the image pixels (if there are some ...) if (m_naxis > 0) { long* fpixel = new long[m_naxis]; long* lpixel = new long[m_naxis]; long* inc = new long[m_naxis]; for (int i = 0; i < m_naxis; ++i) { fpixel[i] = 1; lpixel[i] = m_naxes[i]; inc[i] = 1; } int status = 0; status = __ffgsv(FPTR(m_fitsfile), datatype, fpixel, lpixel, inc, (void*)nulval, (void*)pixels, anynul, &status); delete [] fpixel; delete [] lpixel; delete [] inc; if (status != 0) { throw GException::fits_error(G_LOAD_IMAGE, status); } } // Return return; } /***********************************************************************//** * @brief Save FITS image * * @param[in] datatype Datatype of pixels to be saved * @param[in] pixels Pixel array to be saved * * @exception GException::fits_file_not_open * No FITS file has been opened. * @exception GException::fits_error * FITS error. * * Save image pixels into FITS file. In case that the HDU does not exist it * is created. In case that the pixel array is empty no data are saved; all * image pixels will be empty in this case. ***************************************************************************/ void GFitsImage::save_image(int datatype, const void* pixels) { // Throw an exception if FITS file is not open if (FPTR(m_fitsfile)->Fptr == NULL) { throw GException::fits_file_not_open(G_SAVE_IMAGE, "Open file before saving the image."); } // Move to HDU. We use here an explicit cfitsio moveto function since we // want to recover the error code ... int status = 0; int type = 0; status = __ffmahd(FPTR(m_fitsfile), m_hdunum+1, &type, &status); // If move was successful but HDU type in file differs from HDU type // of object then replace the HDU in the file if (status == 0 && type != exttype()) { status = __ffdhdu(FPTR(m_fitsfile), NULL, &status); if (status != 0) { throw GException::fits_error(G_SAVE_IMAGE, status); } status = __ffiimg(FPTR(m_fitsfile), m_bitpix, m_naxis, m_naxes, &status); //status = __ffiimgll(FPTR(m_fitsfile), m_bitpix, m_naxis, m_naxes, &status); if (status != 0) { throw GException::fits_error(G_SAVE_IMAGE, status); } } // If HDU does not yet exist in file then create it now if (status == 107) { status = 0; status = __ffcrim(FPTR(m_fitsfile), m_bitpix, m_naxis, m_naxes, &status); if (status != 0) { throw GException::fits_error(G_SAVE_IMAGE, status); } } else if (status != 0) { throw GException::fits_error(G_SAVE_IMAGE, status); } // If HDU seems to be empty then create it now. This is only needed for the // primary HDU, since __ffmahd gives no error if the primary HDU is empty. // By checking the number of keywords in the HDU we detect an empty HDU ... int num = 0; status = __ffghsp(FPTR(m_fitsfile), &num, NULL, &status); if (status != 0) { throw GException::fits_error(G_SAVE_IMAGE, status); } if (num == 0) { status = __ffcrim(FPTR(m_fitsfile), m_bitpix, m_naxis, m_naxes, &status); if (status != 0) { throw GException::fits_error(G_SAVE_IMAGE, status); } } // Save the image pixels (if there are some ...) if (m_naxis > 0 && pixels != NULL) { long* fpixel = new long[m_naxis]; long* lpixel = new long[m_naxis]; for (int i = 0; i < m_naxis; ++i) { fpixel[i] = 1; lpixel[i] = m_naxes[i]; } status = __ffpss(FPTR(m_fitsfile), datatype, fpixel, lpixel, (void*)pixels, &status); delete [] fpixel; delete [] lpixel; if (status != 0) { throw GException::fits_error(G_SAVE_IMAGE, status); } } // Return return; } /***********************************************************************//** * @brief Fetch image pixels * * Fetch the image pixels. This function is in general called if pixel * values should be read or written yet no pixel array is allocated. In case * that pixels existed already before they will be deleted before fetching * new ones. * There are two possibilities to fetch the pixels: * (1) In case that a FITS file is attached to the image, the pixel array * will be loaded from the FITS file using the load_image() method. * (2) In case that no FITS file is attached, a new pixel array will be * allocated that is initalised to zero. ***************************************************************************/ void GFitsImage::fetch_data(void) { // Fetch only if there are pixels in image if (m_num_pixels > 0) { // Allocate and initialise fresh memory alloc_data(); init_data(); // If a FITS file is attached then load pixels from FITS file. if (FPTR(m_fitsfile)->Fptr != NULL) { load_image(type(), ptr_data(), ptr_nulval(), &m_anynul); } } // endif: there were pixels available // Return return; } /***********************************************************************//** * @brief Return pixel offset * * @param[in] ix Pixel index (starting from 0). * * @exception GException::out_of_range * Pixel index is outside valid range. ***************************************************************************/ int GFitsImage::offset(const int& ix) const { // Check if axis is within the range #if defined(G_RANGE_CHECK) if (ix < 0 || ix >= m_num_pixels) { throw GException::out_of_range(G_OFFSET_1D, ix, 0, m_num_pixels-1); } #endif // Return index return ix; } /***********************************************************************//** * @brief Return 2D pixel offset * * @param[in] ix Pixel index in first dimension (starting from 0). * @param[in] iy Pixel index in second dimension (starting from 0). * * @exception GException::fits_wrong_image_operator * Pixel array has less than 2 dimensions. * @exception GException::out_of_range * Image axis not valid. * * Computes of offset in the pixel array of a 2D coordinate. This method is * only applicable to pixels arrays that have at least 2 dimenions. ***************************************************************************/ int GFitsImage::offset(const int& ix, const int& iy) const { // Operator is only valid for 2D images if (m_naxis < 2) { throw GException::fits_wrong_image_operator(G_OFFSET_2D, m_naxis, 2); } // Check if axis is within the range #if defined(G_RANGE_CHECK) if (ix < 0 || ix >= m_naxes[0]) { throw GException::out_of_range(G_OFFSET_2D, ix, 0, m_naxes[0]-1); } if (iy < 0 || iy >= m_naxes[1]) { throw GException::out_of_range(G_OFFSET_2D, iy, 0, m_naxes[1]-1); } #endif // Return offset return (ix + iy * m_naxes[0]); } /***********************************************************************//** * @brief Return 3D pixel offset * * @param[in] ix Pixel index in first dimension (starting from 0). * @param[in] iy Pixel index in second dimension (starting from 0). * @param[in] iz Pixel index in third dimension (starting from 0). * * @exception GException::fits_wrong_image_operator * Pixel array has less than 3 dimensions. * @exception GException::out_of_range * Image axis not valid. * * Computes of offset in the pixel array of a 3D coordinate. This method is * only applicable to pixels arrays that have at least 3 dimenions. ***************************************************************************/ int GFitsImage::offset(const int& ix, const int& iy, const int& iz) const { // Operator is only valid for 3D images if (m_naxis < 3) { throw GException::fits_wrong_image_operator(G_OFFSET_3D, m_naxis, 3); } // Check if axis is within the range #if defined(G_RANGE_CHECK) if (ix < 0 || ix >= m_naxes[0]) { throw GException::out_of_range(G_OFFSET_3D, ix, 0, m_naxes[0]-1); } if (iy < 0 || iy >= m_naxes[1]) { throw GException::out_of_range(G_OFFSET_3D, iy, 0, m_naxes[1]-1); } if (iz < 0 || iz >= m_naxes[2]) { throw GException::out_of_range(G_OFFSET_3D, iz, 0, m_naxes[2]-1); } #endif // Return offset return (ix + m_naxes[0] * (iy + iz * m_naxes[1])); } /***********************************************************************//** * @brief Return 4D pixel offset * * @param[in] ix Pixel index in first dimension (starting from 0). * @param[in] iy Pixel index in second dimension (starting from 0). * @param[in] iz Pixel index in third dimension (starting from 0). * @param[in] it Pixel index in forth dimension (starting from 0). * * @exception GException::fits_wrong_image_operator * Pixel array has less than 4 dimensions. * @exception GException::out_of_range * Image axis not valid. * * Computes of offset in the pixel array of a 4D coordinate. This method is * only applicable to pixels arrays that have at least 4 dimenions. ***************************************************************************/ int GFitsImage::offset(const int& ix, const int& iy, const int& iz, const int& it) const { // Operator is only valid for 4D images if (m_naxis < 4) { throw GException::fits_wrong_image_operator(G_OFFSET_4D, m_naxis, 4); } // Check if axis is within the range #if defined(G_RANGE_CHECK) if (ix < 0 || ix >= m_naxes[0]) { throw GException::out_of_range(G_OFFSET_4D, ix, 0, m_naxes[0]-1); } if (iy < 0 || iy >= m_naxes[1]) { throw GException::out_of_range(G_OFFSET_4D, iy, 0, m_naxes[1]-1); } if (iz < 0 || iz >= m_naxes[2]) { throw GException::out_of_range(G_OFFSET_4D, iz, 0, m_naxes[2]-1); } if (it < 0 || it >= m_naxes[3]) { throw GException::out_of_range(G_OFFSET_4D, it, 0, m_naxes[3]-1); } #endif // Return offset return (ix + m_naxes[0] * (iy + m_naxes[1] * (iz + it * m_naxes[2]))); }