/***********************************************************************//** * @brief Read GTIs from HDU. * * @param[in] hdu GTI table. * * Reads the Good Time Intervals from the GTI extension. Since the Fermi * LAT Science Tools do not set corrently the time reference for source * maps, the method automatically adds this missing information so that * the time reference is set correctly. The time reference that is assumed * for Fermi LAT is * * MJDREFI 51910 * MJDREFF 0.00074287037037037 * ***************************************************************************/ void GLATEventCube::read_gti(const GFitsTable& hdu) { // Work on a local copy of the HDU to make the kluge work GFitsTable* hdu_local = hdu.clone(); // Kluge: modify HDU table in case that the MJDREF header keyword is // blank. This happens for Fermi LAT source maps since the Science // Tools do not properly write out the time reference. We hard-code // here the Fermi LAT time reference to circumvent the problem. if (hdu.has_card("MJDREF")) { if (gammalib::strip_whitespace(hdu.string("MJDREF")).empty()) { hdu_local->header().remove("MJDREF"); hdu_local->card("MJDREFI", 51910, "Integer part of MJD reference"); hdu_local->card("MJDREFF", 0.00074287037037037, "Fractional part of MJD reference"); } } // Read Good Time Intervals m_gti.read(*hdu_local); // Set time set_times(); // Return return; }
/***********************************************************************//** * @brief Write response table into FITS table * * @param[in] hdu Fits table HDU. ***************************************************************************/ void GLATResponseTable::write(GFitsTable& hdu) const { // Allocate floating point vector columns GFitsTableFloatCol col_energy_lo = GFitsTableFloatCol("ENERG_LO", 1, m_energy_num); GFitsTableFloatCol col_energy_hi = GFitsTableFloatCol("ENERG_HI", 1, m_energy_num); GFitsTableFloatCol col_ctheta_lo = GFitsTableFloatCol("CTHETA_LO", 1, m_ctheta_num); GFitsTableFloatCol col_ctheta_hi = GFitsTableFloatCol("CTHETA_HI", 1, m_ctheta_num); // Set column values for (int i = 0; i < m_energy_num; ++i) { col_energy_lo(0,i) = m_energy_lo[i]; col_energy_hi(0,i) = m_energy_hi[i]; } for (int i = 0; i < m_ctheta_num; ++i) { col_ctheta_lo(0,i) = m_ctheta_lo[i]; col_ctheta_hi(0,i) = m_ctheta_hi[i]; } // Append columns to boundary table hdu.append(col_energy_lo); hdu.append(col_energy_hi); hdu.append(col_ctheta_lo); hdu.append(col_ctheta_hi); // Return return; }
/***********************************************************************//** * @brief Check input filename * * @param[in] filename File name. * * This method checks if the input FITS file is correct. ***************************************************************************/ std::string ctselect::check_infile(const std::string& filename) const { // Initialise message string std::string message = ""; // Open FITS file GFits file(filename); // Check for EVENTS HDU GFitsTable* table = NULL; try { // Get pointer to FITS table table = file.table("EVENTS"); // Initialise list of missing columns std::vector<std::string> missing; // Check for existence of TIME column if (!table->hascolumn("TIME")) { missing.push_back("TIME"); } // Check for existence of ENERGY column if (!table->hascolumn("ENERGY")) { missing.push_back("ENERGY"); } // Check for existence of RA column if (!table->hascolumn("RA")) { missing.push_back("RA"); } // Check for existence of DEC column if (!table->hascolumn("DEC")) { missing.push_back("DEC"); } // Set error message for missing columns if (missing.size() > 0) { message = "The following columns are missing in the" " \"EVENTS\" extension of input file \"" + m_outfile + "\": "; for (int i = 0; i < missing.size(); ++i) { message += "\"" + missing[i] + "\""; if (i < missing.size()-1) { message += ", "; } } } } catch (GException::fits_hdu_not_found& e) { message = "No \"EVENTS\" extension found in input file \"" + m_outfile + "\"."; } // Return return message; }
/***********************************************************************//** * @brief Read Good Time Intervals and time reference from FITS table * * @param[in] table FITS table. * * Reads the Good Time Intervals and time reference from a FITS table. ***************************************************************************/ void GGti::read(const GFitsTable& table) { // Free members free_members(); // Initialise attributes init_members(); // Read time reference m_reference.read(table); // Extract GTI information from FITS table m_num = table.integer("NAXIS2"); if (m_num > 0) { // Set GTIs m_start = new GTime[m_num]; m_stop = new GTime[m_num]; for (int i = 0; i < m_num; ++i) { m_start[i].set(table["START"]->real(i), m_reference); m_stop[i].set(table["STOP"]->real(i), m_reference); } // Set attributes set_attributes(); } // Return return; }
/***********************************************************************//** * @brief Load livetime cube from FITS file * * @param[in] table FITS table. ***************************************************************************/ void GLATLtCubeMap::read(const GFitsTable& table) { // Clear object clear(); // Load skymap m_map.read(table); // Set costheta binning scheme std::string scheme = gammalib::strip_whitespace(gammalib::toupper(table.string("THETABIN"))); m_sqrt_bin = (scheme == "SQRT(1-COSTHETA)"); // Read attributes m_num_ctheta = table.integer("NBRBINS"); m_num_phi = table.integer("PHIBINS"); m_min_ctheta = table.real("COSMIN"); // Return return; }
/***********************************************************************//** * @brief Read Pulse Height Analyzer spectrum * * @param[in] table FITS table. * * @exception GException::invalid_value * Mismatch between PHA file and energy boundaries. * * Reads the Pulse Height Analyzer spectrum from a FITS table. The channel * values are expected in the `COUNTS` column of the table. All other * columns are ignored. * * See * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/spectra/ogip_92_007/node5.html * for details about the Pulse Height Analyzer spectrum format. ***************************************************************************/ void GPha::read(const GFitsTable& table) { // Clear spectrum clear(); // Get data column const GFitsTableCol* col_data = table["COUNTS"]; // Extract number of channels in FITS file int length = col_data->length(); // Check whether column length is consistent with energy boundaries if (m_ebounds.size() > 0) { if (m_ebounds.size() != length) { std::string msg = "Mismatch between the "+gammalib::str(length)+ " channels in the PHA file and the "+ gammalib::str(m_ebounds.size())+" energy " "boundaries. Please correct either the energy " "boundaris or the PHA file."; throw GException::invalid_value(G_READ, msg); } } // Initialize spectrum m_counts.assign(length, 0.0); // Copy data for (int i = 0; i < length; ++i) { m_counts[i] = col_data->real(i); } // Read keywords m_exposure = (table.has_card("EXPOSURE")) ? table.real("EXPOSURE") : 0.0; // Return return; }
/***********************************************************************//** * @brief Read effective area from FITS table * * @param[in] hdu FITS table. * * @exception GLATException::inconsistent_response * Inconsistent response table encountered * * The effective area is converted into units of cm2. ***************************************************************************/ void GLATAeff::read_aeff(const GFitsTable& hdu) { // Clear array m_aeff.clear(); // Get energy and cos theta bins in response table m_aeff_bins.read(hdu); // Set minimum cos(theta) m_min_ctheta = m_aeff_bins.costheta_lo(0); // Continue only if there are effective area bins int size = m_aeff_bins.size(); if (size > 0) { // Allocate arrays m_aeff.reserve(size); // Get pointer to effective area column const GFitsTableCol* ptr = hdu["EFFAREA"]; // Check consistency of effective area table int num = ptr->number(); if (num != size) { throw GLATException::inconsistent_response(G_READ_AEFF, num, size); } // Copy data and convert from m2 into cm2 for (int i = 0; i < size; ++i) { m_aeff.push_back(ptr->real(0,i) * 1.0e4); } } // endif: there were effective area bins // Set detector section using the DETNAM keyword in the HDU std::string detnam = gammalib::strip_whitespace(hdu.string("DETNAM")); m_front = (detnam == "FRONT"); m_back = (detnam == "BACK"); // Return return; }
/***********************************************************************//** * @brief Read energy boundaries from FITS table * * @param[in] table FITS table. * * Reads the energy boundaries from a FITS table. The method interprets the * energy units provide in the FITS header. If no energy units are found it * is assumed that the energies are stored in units of keV. ***************************************************************************/ void GEbounds::read(const GFitsTable& table) { // Free members free_members(); // Initialise attributes init_members(); // Extract energy boundary information from FITS table m_num = table.integer("NAXIS2"); if (m_num > 0) { // Allocate memory m_min = new GEnergy[m_num]; m_max = new GEnergy[m_num]; // Get units std::string emin_unit = table["E_MIN"]->unit(); std::string emax_unit = table["E_MAX"]->unit(); if (emin_unit.empty()) { emin_unit = "keV"; } if (emax_unit.empty()) { emax_unit = "keV"; } // Copy information for (int i = 0; i < m_num; ++i) { m_min[i](table["E_MIN"]->real(i), emin_unit); m_max[i](table["E_MAX"]->real(i), emax_unit); } // Set attributes set_attributes(); } // endif: there were channels to read // Return return; }
/***********************************************************************//** * @brief Read spectrum from FITS file * * @param[in] table FITS table. * * @exception GMWLException::bad_file_format * Table has invalid format * * Read spectrum from FITS table. The table is expected to be in one of the * three following formats: * 2 columns: energy, flux * 3 columns: energy, flux, e_flux * 4 columns or more: energy, e_energy, flux, e_flux, ... * * @todo Investigate whether we can exploit UCDs for identifying the correct * columns or for determining the units. ***************************************************************************/ void GMWLSpectrum::read_fits(const GFitsTable& table) { // Reset spectrum m_data.clear(); // Initialise column pointers columns const GFitsTableCol* c_energy = NULL; const GFitsTableCol* c_energy_err = NULL; const GFitsTableCol* c_flux = NULL; const GFitsTableCol* c_flux_err = NULL; // Extract column pointers if (table.ncols() == 2) { c_energy = table[0]; c_flux = table[1]; } else if (table.ncols() == 3) { c_energy = table[0]; c_flux = table[1]; c_flux_err = table[2]; } else if (table.ncols() > 3) { c_energy = table[0]; c_energy_err = table[1]; c_flux = table[2]; c_flux_err = table[3]; } else { throw GMWLException::bad_file_format(G_READ_FITS, "At least 2 columns are expected is table \""+ table.extname()+"\"."); } // Read spectral points and add to spectrum for (int i = 0; i < table.nrows(); ++i) { GMWLDatum datum; if (c_energy != NULL) { datum.m_eng = conv_energy(c_energy->real(i), c_energy->unit()); } if (c_energy_err != NULL) { datum.m_eng_err = conv_energy(c_energy_err->real(i), c_energy->unit()); } if (c_flux != NULL) { datum.m_flux = conv_flux(datum.m_eng, c_flux->real(i), c_flux->unit()); } if (c_flux_err != NULL) { datum.m_flux_err = conv_flux(datum.m_eng, c_flux_err->real(i), c_flux_err->unit()); } m_data.push_back(datum); } // Get telescope name if (table.has_card("TELESCOP")) { m_telescope = table.string("TELESCOP"); } else { m_telescope = "unknown"; } // Get instrument name if (table.has_card("INSTRUME")) { m_instrument = table.string("INSTRUME"); } else { m_instrument = "unknown"; } // Set energy boundaries set_ebounds(); // Return return; }
/***********************************************************************//** * @brief Read Auxiliary Response File * * @param[in] table ARF FITS table. * * Reads the Auxiliary Response File from a FITS table. The true energy * boundaries are expected in the `ENERG_LO` and `ENERG_HI` columns, the * response information is expected in the `SPECRESP` column. * * The method will analyze the unit of the `SPECRESP` column, and if either * `m^2` or `m2` are encountered, multiply the values of the column by * \f$10^4\f$ to convert the response into units of \f$cm^2\f$. Units of the * `ENERG_LO` and `ENERG_HI` columns are also interpreted for conversion. * * See * http://heasarc.gsfc.nasa.gov/docs/heasarc/caldb/docs/memos/cal_gen_92_002/cal_gen_92_002.html#tth_sEc4 * for details about the Auxiliary Response File format. ***************************************************************************/ void GArf::read(const GFitsTable& table) { // Clear members clear(); // Get pointer to data columns const GFitsTableCol* energy_lo = table["ENERG_LO"]; const GFitsTableCol* energy_hi = table["ENERG_HI"]; const GFitsTableCol* specresp = table["SPECRESP"]; // Determine effective area conversion factor. Internal // units are cm^2 std::string u_specresp = gammalib::tolower(gammalib::strip_whitespace(specresp->unit())); double c_specresp = 1.0; if (u_specresp == "m^2" || u_specresp == "m2") { c_specresp = 10000.0; } // Extract number of energy bins int num = energy_lo->length(); // Set energy bins for (int i = 0; i < num; ++i) { // Append energy bin GEnergy emin(energy_lo->real(i), energy_lo->unit()); GEnergy emax(energy_hi->real(i), energy_hi->unit()); m_ebounds.append(emin, emax); // Append effective area value double aeff = specresp->real(i) * c_specresp; m_specresp.push_back(aeff); } // endfor: looped over energy bins // Read any additional columns for (int icol = 0; icol < table.ncols(); ++icol) { // Fall through if the column is a standard column std::string colname(table[icol]->name()); if ((colname == "ENERG_LO") || (colname == "ENERG_HI") || (colname == "SPECRESP")) { continue; } // Get pointer to column const GFitsTableCol* column = table[icol]; // Set column vector std::vector<double> coldata; for (int i = 0; i < num; ++i) { coldata.push_back(column->real(i)); } // Append column append(colname, coldata); } // endfor: looped over all additional columns // Return return; }
/***********************************************************************//** * @brief Read LAT events from FITS table. * * @param[in] table Event table. * * Read the LAT events from the event table. ***************************************************************************/ void GLATEventList::read_events(const GFitsTable& table) { // Clear existing events m_events.clear(); // Allocate space for keyword name char keyword[10]; // Extract number of events in FT1 file int num = table.integer("NAXIS2"); // If there are events then load them if (num > 0) { // Reserve data m_events.reserve(num); // Get column pointers const GFitsTableCol* ptr_time = table["TIME"]; const GFitsTableCol* ptr_energy = table["ENERGY"]; const GFitsTableCol* ptr_ra = table["RA"]; const GFitsTableCol* ptr_dec = table["DEC"]; const GFitsTableCol* ptr_theta = table["THETA"]; const GFitsTableCol* ptr_phi = table["PHI"]; const GFitsTableCol* ptr_zenith = table["ZENITH_ANGLE"]; const GFitsTableCol* ptr_azimuth = table["EARTH_AZIMUTH_ANGLE"]; const GFitsTableCol* ptr_eid = table["EVENT_ID"]; const GFitsTableCol* ptr_rid = table["RUN_ID"]; const GFitsTableCol* ptr_recon = table["RECON_VERSION"]; const GFitsTableCol* ptr_calib = table["CALIB_VERSION"]; const GFitsTableCol* ptr_class = table["EVENT_CLASS"]; const GFitsTableCol* ptr_conv = table["CONVERSION_TYPE"]; const GFitsTableCol* ptr_ltime = table["LIVETIME"]; // Copy data from columns into GLATEventAtom objects GLATEventAtom event; for (int i = 0; i < num; ++i) { event.m_time.set(ptr_time->real(i), m_gti.reference()); event.m_energy.MeV(ptr_energy->real(i)); event.m_dir.dir().radec_deg(ptr_ra->real(i), ptr_dec->real(i)); event.m_theta = ptr_theta->real(i); event.m_phi = ptr_phi->real(i); event.m_zenith_angle = ptr_zenith->real(i); event.m_earth_azimuth_angle = ptr_azimuth->real(i); event.m_event_id = ptr_eid->integer(i); event.m_run_id = ptr_rid->integer(i); event.m_recon_version = ptr_recon->integer(i); event.m_calib_version[0] = ptr_calib->integer(i,0); event.m_calib_version[1] = ptr_calib->integer(i,1); event.m_calib_version[2] = ptr_calib->integer(i,2); event.m_event_class = ptr_class->integer(i); event.m_conversion_type = ptr_conv->integer(i); event.m_livetime = ptr_ltime->real(i); m_events.push_back(event); } // Extract number of diffuse response labels int num_difrsp = table.integer("NDIFRSP"); // Allocate diffuse response components if (num_difrsp > 0) { // Reserve space m_difrsp_label.reserve(num_difrsp); // Allocate components for (int i = 0; i < num; ++i) { m_events[i].m_difrsp = new double[num_difrsp]; } // Load diffuse columns for (int k = 0; k < num_difrsp; ++k) { // Set keyword name std::sprintf(keyword, "DIFRSP%d", k); // Get DIFRSP label if (table.has_card(std::string(keyword))) { m_difrsp_label.push_back(table.string(std::string(keyword))); } else { m_difrsp_label.push_back("NONE"); } // Get column pointer const GFitsTableCol* ptr_dif = table[std::string(keyword)]; // Copy data from columns into GLATEventAtom objects for (int i = 0; i < num; ++i) { m_events[i].m_difrsp[k] = ptr_dif->real(i); } } // endfor: looped over diffuse columns } // endif: diffuse components found } // endif: events found // Return return; }
/***********************************************************************//** * @brief Read CTA PSF vector * * @param[in] table FITS table. * * This method reads a CTA PSF vector from the FITS HDU. Note that the * energies are converted to TeV. Conversion is done based on the units * provided for the energy columns. Units that are recognized are 'keV', * 'MeV', 'GeV', and 'TeV' (case independent). * * The Gaussian width parameter may be either given in 68% containment radius * (R68) or in sigma (ANGRES40; corresponding to a 38% containment radius). * The former format is used for CTA and H.E.S.S. data, the latter for * MAGIC data. All these things should be more uniform once we have a well * defined format. ***************************************************************************/ void GCTAPsfVector::read(const GFitsTable& table) { // Clear arrays m_logE.clear(); m_r68.clear(); m_sigma.clear(); // Set conversion factor from 68% containment radius to 1 sigma const double conv = 0.6624305 * gammalib::deg2rad; // Get pointers to table columns const GFitsTableCol* energy_lo = table["ENERG_LO"]; const GFitsTableCol* energy_hi = table["ENERG_HI"]; // Handle various data formats (H.E.S.S. and MAGIC) const GFitsTableCol* r68; double r68_scale = 1.0; if (table.contains("R68")) { r68 = table["R68"]; } else { r68 = table["ANGRES40"]; r68_scale = 1.0 / 0.6624305; // MAGIC PSF is already 1 sigma } // Determine unit conversion factors (default: TeV) std::string u_energy_lo = gammalib::tolower(gammalib::strip_whitespace(energy_lo->unit())); std::string u_energy_hi = gammalib::tolower(gammalib::strip_whitespace(energy_hi->unit())); double c_energy_lo = 1.0; double c_energy_hi = 1.0; if (u_energy_lo == "kev") { c_energy_lo = 1.0e-9; } else if (u_energy_lo == "mev") { c_energy_lo = 1.0e-6; } else if (u_energy_lo == "gev") { c_energy_lo = 1.0e-3; } if (u_energy_hi == "kev") { c_energy_hi = 1.0e-9; } else if (u_energy_hi == "mev") { c_energy_hi = 1.0e-6; } else if (u_energy_hi == "gev") { c_energy_hi = 1.0e-3; } // Extract number of energy bins int num = energy_lo->length(); // Set nodes for (int i = 0; i < num; ++i) { // Compute log10 mean energy in TeV double e_min = energy_lo->real(i) * c_energy_lo; double e_max = energy_hi->real(i) * c_energy_hi; double logE = 0.5 * (log10(e_min) + log10(e_max)); // Extract r68 value and scale as required double r68_value = r68->real(i) * r68_scale; // Store log10 mean energy and r68 value m_logE.append(logE); m_r68.push_back(r68_value); m_sigma.push_back(r68_value*conv); } // endfor: looped over nodes // Return return; }
/***********************************************************************//** * @brief Read column names from FITS HDU * * @param[in] hdu Response table HDU. * * @exception GCTAException::bad_rsp_table_format * Bad response table format encountered in FITS HDU. * * Read the response table column names from the HDU. Column names * terminating with "_LO" and "_HI" define the parameter space axes, while * all other column names define response parameter data cubes. It is * assumed that parameter space axes are given in subsequent order, with * the first column corresponding to the lower bin boundaries of the first * axis. Lower bin boundaries are indicated by the "_LO" termination. Upper * bin boundaries are assumed to follow immediately the lower bin boundaires * and are designated by the "_HI" termination. * * This method sets the following members: * m_colname_lo - Column names of lower boundaries * m_colname_hi - Column names of upper boundaries * m_colname_par - Column names of parameters * m_naxes - Number of axes * m_npars - Number of parameters * * In case that the HDU pointer is not valid (NULL), this method clears the * column names and does nothing else. * * @todo Implement exceptions for invalid HDU format ***************************************************************************/ void GCTAResponseTable::read_colnames(const GFitsTable& hdu) { // Clear column name arrays m_naxes = 0; m_npars = 0; m_colname_lo.clear(); m_colname_hi.clear(); m_colname_par.clear(); // Initialise search mode. There are three search modes: // 0 - we're looking for the next axis by searching for a column // terminating with "_LO" // 1 - we're looking for the upper boundary of an axis, terminating // with "_HI" // 2 - we're looking for a parameter column int mode = 0; std::string lo_column; std::string next_column; // Extract column names for all axes for (int i = 0; i < hdu.ncols(); ++i) { // Get column name std::string colname = hdu[i]->name(); // If we search for a "_LO" column, check if we have one. If one // is found, change the search mode to 1 and set the expected name // for the "_HI" column. If none is found, change the search // mode to 2 since from now on we should only have parameter // columns. if (mode == 0) { size_t pos = colname.rfind("_LO"); if (pos != std::string::npos) { mode = 1; lo_column = colname; next_column = colname.substr(0, pos) + "_HI"; } else { if (colname.rfind("_HI") != std::string::npos) { std::string message = "Column '" + colname + "' encountered without" " preceeding '_LO' column."; throw GCTAException::bad_rsp_table_format(G_READ_COLNAMES, message); } else { mode = 2; m_colname_par.push_back(colname); } } } // If we search for a "_HI" column, check if we have the // expected column name. If this is the case, switch back to // search mode 0 to get the next "_LO" column. Otherwise // throw an exception. else if (mode == 1) { if (colname == next_column) { mode = 0; m_colname_lo.push_back(lo_column); m_colname_hi.push_back(next_column); } else { std::string message = "Expected column '" + next_column + "' not found. '_HI' columns have" " to be placed immediately after" " corresponding '_LO' columns."; throw GCTAException::bad_rsp_table_format(G_READ_COLNAMES, message); } } // If we search for a parameter column, make sure that we have // neither a "_LO" nor a "_HI" column. else { if (colname.rfind("_LO") != std::string::npos) { std::string message = "Column '" + colname + "' found. All '_LO' columns have to" " be placed before the parameter" " columns."; throw GCTAException::bad_rsp_table_format(G_READ_COLNAMES, message); } else if (colname.rfind("_HI") != std::string::npos) { std::string message = "Column '" + colname + "' found. All '_HI' columns have to" " be placed before the parameter" " columns."; throw GCTAException::bad_rsp_table_format(G_READ_COLNAMES, message); } else { m_colname_par.push_back(colname); } } } // endfor: looped over all column names // Store number of axes m_naxes = m_colname_lo.size(); // Store number of parameters m_npars = m_colname_par.size(); // Return return; }