/* $Id: cpl_dfs.c,v 1.78 2008/01/29 10:50:47 llundin Exp $ * * This file is part of the ESO Common Pipeline Library * Copyright (C) 2001-2004 European Southern Observatory * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * $Author: llundin $ * $Date: 2008/01/29 10:50:47 $ * $Revision: 1.78 $ * $Name: $ */ #ifdef HAVE_CONFIG_H #include #endif #include /* Needed for sprintf() */ #include #include #include #include #include #include #include #include #include "cpl_dfs.h" /** * @defgroup cpl_dfs DFS related functions * */ /*----------------------------------------------------------------------------- * Private function prototypes *----------------------------------------------------------------------------- */ /**@{*/ /* Declarations of static MD5 functions */ #include "md5.h" static cpl_error_code cpl_dfs_update_key_string(fitsfile *, const char *, const char *, const char *); static cpl_error_code cpl_dfs_find_md5sum(fitsfile *, char *); static int cpl_is_fits(const char *); static const char *cpl_get_base_name(const char *); /*----------------------------------------------------------------------------- Private function prototypes -----------------------------------------------------------------------------*/ static FILE * cpl_dfs_paf_init(const char *, const char *, const char *) #if defined __GNUC__ && __GNUC__ >= 4 __attribute__((nonnull)) #endif ; static cpl_error_code cpl_dfs_paf_dump(const cpl_propertylist *, FILE *) #if defined __GNUC__ && __GNUC__ >= 4 __attribute__((nonnull)) #endif ; static cpl_error_code cpl_dfs_paf_dump_string(const char *, const char *, const char *, FILE *); static cpl_error_code cpl_dfs_paf_dump_double(const char *, double, const char *, FILE *); static cpl_error_code cpl_dfs_paf_dump_int(const char *, int, const char *, FILE *); static cpl_error_code cpl_dfs_product_save(cpl_frameset *, const cpl_parameterlist *, const cpl_frameset *, const cpl_imagelist *, const cpl_image *, cpl_type_bpp, const cpl_table *, const cpl_propertylist *, const char *, const char *, const cpl_propertylist *, const char *, const char *, const char *); /*----------------------------------------------------------------------------- Defines and Static variables -----------------------------------------------------------------------------*/ /* * Product keywords aliases */ #define DATAMD5 "DATAMD5" #define PIPEFILE "PIPEFILE" #define PRO_DID "ESO PRO DID" #define DPR_CATG "ESO DPR CATG" #define PRO_CATG "ESO PRO CATG" #define PRO_TYPE "ESO PRO TYPE" #define DPR_TECH "ESO DPR TECH" #define PRO_TECH "ESO PRO TECH" #define PRO_SCIENCE "ESO PRO SCIENCE" #define PRO_DATE "ESO PRO DATE" #define PRO_DATANCOM "ESO PRO DATANCOM" #define PRO_REC_ID "ESO PRO REC1 ID" #define PRO_REC_DRS_ID "ESO PRO REC1 DRS ID" #define PRO_REC_PIPE_ID "ESO PRO REC1 PIPE ID" #define PRO_REC_RAWi_NAME "ESO PRO REC1 RAW%d NAME" #define PRO_REC_RAWi_CATG "ESO PRO REC1 RAW%d CATG" #define PRO_REC_CALi_NAME "ESO PRO REC1 CAL%d NAME" #define PRO_REC_CALi_CATG "ESO PRO REC1 CAL%d CATG" #define PRO_REC_CALi_DATAMD5 "ESO PRO REC1 CAL%d " DATAMD5 #define PRO_REC_PARAMi_NAME "ESO PRO REC1 PARAM%d NAME" #define PRO_REC_PARAMi_VALUE "ESO PRO REC1 PARAM%d VALUE" #define MAX_PLENGTH (64) /* Size of a 128-bit MD5 hash in bytes */ #define MD5HASHSZ 32 /* Use at least this many places when printing the PAF key */ #define PAF_KEY_LEN 21 /* Right justify the PAF key and separate with an extra space */ #define PAF_KEY_FORMAT "%-21s " static const char cpl_dfs_pro_did[] = "PRO-1.15"; /*----------------------------------------------------------------------------- * Function code *----------------------------------------------------------------------------- */ /*----------------------------------------------------------------------------*/ /** @brief Save an image as a DFS-compliant pipeline product @param allframes The list of input frames for the recipe @param parlist The list of input parameters @param usedframes The list of raw/calibration frames used for this product @param image The image to be saved or NULL @param bpp Bits per pixel @param recipe The recipe name @param procat The product category tag @param applist Optional propertylist to append to product or NULL @param remregexp Optional regexp of properties not to put in main header @param pipe_id PACKAGE "/" PACKAGE_VERSION @param filename Filename of created product @note The image may be NULL in which case only the header information is saved @note remregexp may be NULL @return CPL_ERROR_NONE or the relevant CPL error code on error @see cpl_dfs_setup_product_header(), cpl_image_save(). The FITS header of the created product is created from the provided applist and the cards copied by cpl_dfs_setup_product_header(), with exception of the cards whose keys match the provided remregexp. */ /*----------------------------------------------------------------------------*/ cpl_error_code cpl_dfs_save_image(cpl_frameset * allframes, const cpl_parameterlist * parlist, const cpl_frameset * usedframes, const cpl_image * image, cpl_type_bpp bpp, const char * recipe, const char * procat, const cpl_propertylist * applist, const char * remregexp, const char * pipe_id, const char * filename) { const cpl_error_code error = cpl_dfs_product_save(allframes, parlist, usedframes, NULL, image, bpp, NULL, NULL, recipe, procat, applist, remregexp, pipe_id, filename); cpl_ensure_code(!error, error); return CPL_ERROR_NONE; } /*----------------------------------------------------------------------------*/ /** @brief Save an imagelist as a DFS-compliant pipeline product @param allframes The list of input frames for the recipe @param parlist The list of input parameters @param usedframes The list of raw/calibration frames used for this product @param imagelist The imagelist to be saved @param bpp Bits per pixel @param recipe The recipe name @param procat The product category tag @param applist Optional propertylist to append to product or NULL @param remregexp Optional regexp of properties not to put in main header @param pipe_id PACKAGE "/" PACKAGE_VERSION @param filename Filename of created product @note remregexp may be NULL @return CPL_ERROR_NONE or the relevant CPL error code on error @see cpl_dfs_image_save(), cpl_imagelist_save(). */ /*----------------------------------------------------------------------------*/ cpl_error_code cpl_dfs_save_imagelist(cpl_frameset * allframes, const cpl_parameterlist * parlist, const cpl_frameset * usedframes, const cpl_imagelist * imagelist, cpl_type_bpp bpp, const char * recipe, const char * procat, const cpl_propertylist * applist, const char * remregexp, const char * pipe_id, const char * filename) { cpl_error_code error; cpl_ensure_code(imagelist != NULL, CPL_ERROR_NULL_INPUT); error = cpl_dfs_product_save(allframes, parlist, usedframes, imagelist, NULL, bpp, NULL, NULL, recipe, procat, applist, remregexp, pipe_id, filename); cpl_ensure_code(!error, error); return CPL_ERROR_NONE; } /*----------------------------------------------------------------------------*/ /** @brief Save a table as a DFS-compliant pipeline product @param allframes The list of input frames for the recipe @param parlist The list of input parameters @param usedframes The list of raw/calibration frames used for this product @param table The table to be saved @param tablelist Optional propertylist to append to table extension or NULL @param recipe The recipe name @param procat The product category tag @param applist Optional propertylist to append to product or NULL @param remregexp Optional regexp of properties not to put in main header @param pipe_id PACKAGE "/" PACKAGE_VERSION @param filename Filename of created product @return CPL_ERROR_NONE or the relevant CPL error code on error @see cpl_dfs_save_image(), cpl_table_save(). */ /*----------------------------------------------------------------------------*/ cpl_error_code cpl_dfs_save_table(cpl_frameset * allframes, const cpl_parameterlist * parlist, const cpl_frameset * usedframes, const cpl_table * table, const cpl_propertylist * tablelist, const char * recipe, const char * procat, const cpl_propertylist * applist, const char * remregexp, const char * pipe_id, const char * filename) { cpl_error_code error; cpl_ensure_code(table != NULL, CPL_ERROR_NULL_INPUT); error = cpl_dfs_product_save(allframes, parlist, usedframes, NULL, NULL, CPL_BPP_IEEE_FLOAT, table, tablelist, recipe, procat, applist, remregexp, pipe_id, filename); cpl_ensure_code(!error, error); return CPL_ERROR_NONE; } /*----------------------------------------------------------------------------*/ /** @brief Create a new PAF file @param instrume Name of instrument in capitals (NACO, VISIR, etc.) @param recipe Name of recipe @param paflist Propertylist to save @param filename Filename of created PArameter File @return CPL_ERROR_NONE or the relevant CPL error code on error @see cpl_dfs_save_image(). The example below shows how to create a PAF from some FITS cards from the file ref_file and QC parameters in a propertylist qclist. Please note that qclist can be used also in calls to cpl_dfs_save_image() and cpl_dfs_save_table(). Error handling is omitted for brevity: @code const char pafcopy[] = "^(DATE-OBS|ARCFILE|ESO TPL ID|ESO DET DIT|MJD-OBS)$"; cpl_propertylist * paflist = cpl_propertylist_load_regexp(ref_file, 0, pafcopy, 0); cpl_propertylist_append(paflist, qclist); cpl_dfs_save_paf("IIINSTRUMENT", "rrrecipe", paflist, "rrrecipe.paf"); cpl_propertylist_delete(paflist); @endcode */ /*----------------------------------------------------------------------------*/ cpl_error_code cpl_dfs_save_paf(const char * instrume, const char *recipe, const cpl_propertylist * paflist, const char * filename) { FILE * paf; cpl_error_code status; cpl_ensure_code(instrume != NULL, CPL_ERROR_NULL_INPUT); cpl_ensure_code(recipe != NULL, CPL_ERROR_NULL_INPUT); cpl_ensure_code(paflist != NULL, CPL_ERROR_NULL_INPUT); cpl_ensure_code(filename != NULL, CPL_ERROR_NULL_INPUT); paf = cpl_dfs_paf_init(instrume, recipe, filename); cpl_ensure_code(paf != NULL, cpl_error_get_code()); status = cpl_dfs_paf_dump(paflist, paf); if (status == CPL_ERROR_NONE && fprintf(paf, "\n") != 1) status = CPL_ERROR_FILE_IO; if (status == CPL_ERROR_NONE) { cpl_ensure_code(fclose(paf) == 0, CPL_ERROR_FILE_IO); } else { (void)fclose(paf); } return status; } /** * @brief Add product keywords to a pipeline product property list. * * @param header Property list where keywords must be written * @param product_frame Frame describing the product * @param framelist List of frames including all raw input frames * @param parlist Recipe parameter list * @param recid Recipe name * @param pipeline_id Pipeline unique identifier * @param dictionary_id PRO dictionary identifier * * @return @c CPL_ERROR_NONE on success. * * @error * * * * * * * * * * * * * * * * * * * * * *
CPL_ERROR_NULL_INPUT * An input pointer is NULL. *
CPL_ERROR_DATA_NOT_FOUND * The input framelist contains no input frames or * a frame in the input framelist does not specify a file. * In the former case the string "set-of-frames" would be appended * to the error message returned by cpl_error_get_message(). *
CPL_ERROR_ILLEGAL_INPUT * The product frame is not tagged or not grouped as CPL_FRAME_GROUP_PRODUCT. *
CPL_ERROR_FILE_NOT_FOUND * A frame in the input framelist specifies a non-existing file. *
CPL_ERROR_BAD_FILE_FORMAT * A frame in the input framelist specifies an invalid file. *
* @enderror * * This function checks the @em header associated to a pipeline product, * to ensure that it is DICB compliant. In particular, this function does * the following: * * -# Copy to @em header, if they are present, the following primary * FITS keywords from the first input frame in the @em framelist: * ORIGIN, TELESCOPE, INSTRUME, OBJECT, RA, DEC, EPOCH, EQUINOX, * RADECSYS, DATE-OBS, MJD-OBS, UTC, LST, PI-COI, OBSERVER. If those * keywords are already present in the @em header property list, they * are overwritten only in case they have the same type. If any of * these keywords are present with an unexpected type, a warning is * issued, but the keywords are copied anyway (provided that the * above conditions are fulfilled), and no error is set. * * -# Copy all the HIERARCH.ESO._ keywords from the primary FITS header * of the first input frame in @em framelist, with the exception of * the HIERARCH.ESO.DPR._, and of the .PRO._ and .DRS._ keywords if * the first input frame is a calibration. If those keywords are * already present in @em header, they are overwritten. * * -# If found, remove the HIERARCH.ESO.DPR._ keywords from @em header. * * -# If found, remove the ARCFILE and ORIGFILE keywords from @em header. * * -# Add to @em header the following mandatory keywords from the PRO * dictionary: PIPEFILE, PRO.DID, PRO.REC1.ID, PRO.REC1.DRS.ID, * PRO.REC1.PIPE.ID, and PRO.CATG. If those keywords are already * present in @em header, they are overwritten. The keyword * PRO.CATG is always set identical to the tag in @em product_frame. * * -# Only if missing, add to @em header the following mandatory keywords * from the PRO dictionary: PRO.TYPE, PRO.TECH, and PRO.SCIENCE. * The keyword PRO.TYPE will be set to "REDUCED". If the keyword * DPR.TECH is found in the header of the first frame, PRO.TECH is * given its value, alternatively if the keyword PRO.TECH is found * it is copied instead, and if all fails the value "UNDEFINED" is * set. Finally, if the keyword DPR.CATG is found in the header of * the first frame and is set to "SCIENCE", the boolean keyword * PRO.SCIENCE will be set to "true", otherwise it will be copied * from an existing PRO.SCIENCE jeyword, while it will be set to * "false" in all other cases. * * -# Check the existence of the keyword PRO.DATANCOM in @em header. If * this keyword is missing, one is added, with the value of the total * number of raw input frames. * * -# Add to @em header the keywords PRO.REC1.RAW1.NAME, PRO.REC1.RAW1.CATG, * PRO.REC1.CAL1.NAME, PRO.REC1.CAL1.CATG, to describe the content of * the input set-of-frames. * * See the DICB PRO dictionary to have details on the mentioned PRO keywords. * * @note * The first input frame in @em framelist is the first raw frame in * @em framelist. If there are no raw frames in input, then the first * calibration frame is taken. Non-FITS files are handled as files * with an empty FITS header. */ cpl_error_code cpl_dfs_setup_product_header(cpl_propertylist *header, const cpl_frame *product_frame, const cpl_frameset *framelist, const cpl_parameterlist *parlist, const char *recid, const char *pipeline_id, const char *dictionary_id) { char cval[FLEN_VALUE]; const char *datamd5; const cpl_frame *frame; const cpl_frame *first_frame = NULL; cpl_propertylist *plist; const cpl_parameter *param; int nraw; int ncal; int npar; int i; const char *kname; /* * Here is the list of mandatory keywords, first in input header, * second in output _image_ header: */ typedef struct { const char *key; cpl_type type; } klist; klist mandatory[] = {{"ORIGIN", CPL_TYPE_STRING}, {"TELESCOP", CPL_TYPE_STRING}, {"INSTRUME", CPL_TYPE_STRING}, {"OBJECT", CPL_TYPE_STRING}, {"RA", CPL_TYPE_DOUBLE}, {"DEC", CPL_TYPE_DOUBLE}, {"EPOCH", CPL_TYPE_STRING}, {"EQUINOX", CPL_TYPE_DOUBLE}, {"RADECSYS", CPL_TYPE_STRING}, {"DATE-OBS", CPL_TYPE_STRING}, {"MJD-OBS", CPL_TYPE_DOUBLE}, {"UTC", CPL_TYPE_DOUBLE}, {"LST", CPL_TYPE_DOUBLE}, {"PI-COI", CPL_TYPE_STRING}, {"OBSERVER", CPL_TYPE_STRING}}; const int count_mandatory = (int)(sizeof(mandatory) / sizeof(klist)); int pro_science = 0; /* Here are the same keywords - as a regular expression */ #define REGMANDATORY "^(ORIGIN|TELESCOP|INSTRUME|OBJECT|RA|DEC|EPOCH|EQUINOX" \ "|RADECSYS|DATE-OBS|MJD-OBS|UTC|LST|PI-COI|OBSERVER)$" cpl_ensure_code(header != NULL, CPL_ERROR_NULL_INPUT); cpl_ensure_code(product_frame != NULL, CPL_ERROR_NULL_INPUT); cpl_ensure_code(framelist != NULL, CPL_ERROR_NULL_INPUT); cpl_ensure_code(recid != NULL, CPL_ERROR_NULL_INPUT); /* * Forbidden keywords are removed from product header. */ cpl_propertylist_erase_regexp(header, "^ESO DPR |^ARCFILE$|^ORIGFILE$", 0); /* * Get the first input frame in the input set-of-frames. */ frame = cpl_frameset_get_first_const(framelist); while (frame != NULL) { if (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_RAW) { first_frame = frame; break; } frame = cpl_frameset_get_next_const(framelist); } if (first_frame == NULL) { frame = cpl_frameset_get_first_const(framelist); while (frame != NULL) { if (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_CALIB) { first_frame = frame; break; } frame = cpl_frameset_get_next_const(framelist); } } if (first_frame == NULL) { return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND, "set-of-frames"); } /* * Now copy all the required entries, if present, from input to * product header. */ switch (cpl_is_fits(cpl_frame_get_filename(first_frame))) { case 0: plist = cpl_propertylist_new(); break; case 1: /* Load only the mandatory keywords and the HIERACH ESO keywords */ plist = cpl_propertylist_load_regexp( cpl_frame_get_filename(first_frame), 0, REGMANDATORY "|^ESO ", 0); cpl_ensure_code(plist != NULL, cpl_error_get_code()); break; default: cpl_ensure_code(0, cpl_error_get_code() == CPL_ERROR_NULL_INPUT ? CPL_ERROR_DATA_NOT_FOUND : cpl_error_get_code()); } for (i = 0; i < count_mandatory; i++) { if (cpl_propertylist_has(plist, mandatory[i].key)) { if (cpl_propertylist_get_type(plist, mandatory[i].key) != mandatory[i].type) { cpl_msg_warning(cpl_func, "Unexpected type for keyword %s in " "input header", mandatory[i].key); } if (cpl_propertylist_has(header, mandatory[i].key)) { if (cpl_propertylist_get_type(header, mandatory[i].key) != mandatory[i].type) { cpl_msg_warning(cpl_func, "Unexpected type for keyword %s " "in output header", mandatory[i].key); } if (cpl_propertylist_get_type(plist, mandatory[i].key) != cpl_propertylist_get_type(header, mandatory[i].key)) { /* Do not copy this keyword */ cpl_propertylist_erase(plist, mandatory[i].key); } } } } /* * Here copy all the HIERARCH.ESO._ keywords, excluding the * HIERARCH.ESO.DPR._ keywords, and of the .PRO._ and .DRS._ * keywords. */ /* Also copy the mandatory keywords, if any */ cpl_propertylist_copy_property_regexp(header, plist, "^ESO (DPR|DRS|PRO) ", 1); cpl_propertylist_delete(plist); /* * DATAMD5 (placeholder: will be computed by esorex) */ cpl_propertylist_update_string(header, DATAMD5, "Not computed"); cpl_propertylist_set_comment(header, DATAMD5, "MD5 checksum"); /* * PIPEFILE */ kname = cpl_frame_get_filename(product_frame); cpl_ensure_code(kname != NULL, cpl_error_get_code()); cpl_propertylist_update_string(header, PIPEFILE, kname); cpl_propertylist_set_comment(header, PIPEFILE, "Filename of data product"); /* * PRO DID */ cpl_propertylist_update_string(header, PRO_DID, dictionary_id); cpl_propertylist_set_comment(header, PRO_DID, "Data dictionary for PRO"); /* * PRO CATG */ kname = cpl_frame_get_tag(product_frame); if (kname == NULL) { return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT, "Product frame has no tag"); } cpl_propertylist_update_string(header, PRO_CATG, kname); cpl_propertylist_set_comment(header, PRO_CATG, "Category of pipeline product frame"); /* The product frame must be grouped correctly */ cpl_ensure_code(cpl_frame_get_group(product_frame) == CPL_FRAME_GROUP_PRODUCT, CPL_ERROR_ILLEGAL_INPUT); /* * Load only the HIERACH ESO DPR keywords */ if (cpl_is_fits(cpl_frame_get_filename(first_frame))) { plist = cpl_propertylist_load_regexp( cpl_frame_get_filename(first_frame), 0, "^ESO DPR ", 0); cpl_ensure_code(plist != NULL, cpl_error_get_code()); } else plist = NULL; /* * PRO TYPE * Check presence of PRO.TYPE already provided in the input header: * leave it alone if present, provide a default if missing. */ if (!cpl_propertylist_has(header, PRO_TYPE)) { cpl_propertylist_update_string(header, PRO_TYPE, "REDUCED"); cpl_propertylist_set_comment(header, PRO_TYPE, "Product type"); } /* * PRO TECH * Check presence of PRO.TECH already provided in the input header: * leave it alone if present, copy it from the first frame DPR.TECH * if present there, copy from the first frame PRO.TECH if present * there, set a default "UNDEFINED" if missing even there. */ if (!cpl_propertylist_has(header, PRO_TECH)) { if (plist && cpl_propertylist_has(plist, DPR_TECH)) { cpl_propertylist_update_string(header, PRO_TECH, cpl_propertylist_get_string(plist, DPR_TECH)); } else if (plist && cpl_propertylist_has(plist, PRO_TECH)) { cpl_propertylist_update_string(header, PRO_TECH, cpl_propertylist_get_string(plist, PRO_TECH)); } else { cpl_propertylist_update_string(header, PRO_TECH, "UNDEFINED"); } cpl_propertylist_set_comment(header, PRO_TECH, "Observation technique"); } /* * PRO SCIENCE * Check presence of PRO.SCIENCE already provided in the input header: * leave it alone if present, set it to "true" if DPR.CATG from the * first frame is "SCIENCE", set it to the value of PRO.SCIENCE if * present, set it to "false" in all other cases. */ if (!cpl_propertylist_has(header, PRO_SCIENCE)) { if (plist && cpl_propertylist_has(plist, DPR_CATG)) { pro_science = !strncmp(cpl_propertylist_get_string(plist, DPR_CATG), "SCIENCE", 7); } else if (plist && cpl_propertylist_has(plist, PRO_SCIENCE)) { pro_science = cpl_propertylist_get_bool(plist, PRO_SCIENCE); } cpl_propertylist_update_bool(header, PRO_SCIENCE, pro_science); cpl_propertylist_set_comment(header, PRO_SCIENCE, "Scientific product if T"); } cpl_propertylist_delete(plist); /* * PRO REC1 ID */ cpl_propertylist_update_string(header, PRO_REC_ID, recid); cpl_propertylist_set_comment(header, PRO_REC_ID, "Pipeline recipe (unique) identifier"); /* * PRO REC1 DRS ID */ /* cpl_propertylist_update_string(header, PRO_REC_DRS_ID, cpl_get_version()); */ cpl_propertylist_update_string(header, PRO_REC_DRS_ID, PACKAGE "-" PACKAGE_VERSION); cpl_propertylist_set_comment(header, PRO_REC_DRS_ID, "Data Reduction System identifier"); /* * PRO REC1 PIPE ID */ /* snprintf(cval, FLEN_VALUE, "%s/%s", PACKAGE, PACKAGE_VERSION); */ cpl_propertylist_update_string(header, PRO_REC_PIPE_ID, pipeline_id); cpl_propertylist_set_comment(header, PRO_REC_PIPE_ID, "Pipeline (unique) identifier"); /* * PRO REC1 RAWi NAME and PRO REC1 RAWi CATG */ nraw = 0; frame = cpl_frameset_get_first_const(framelist); while (frame != NULL) { if (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_RAW) { ++nraw; snprintf(cval, FLEN_VALUE, PRO_REC_RAWi_NAME, nraw); cpl_propertylist_update_string(header, cval, cpl_get_base_name(cpl_frame_get_filename(frame))); cpl_propertylist_set_comment(header, cval, "File name of raw frame"); snprintf(cval, FLEN_VALUE, PRO_REC_RAWi_CATG, nraw); cpl_propertylist_update_string(header, cval, cpl_frame_get_tag(frame)); cpl_propertylist_set_comment(header, cval, "Category of raw frame"); } frame = cpl_frameset_get_next_const(framelist); } /* * PRO DATANCOM */ if (!cpl_propertylist_has(header, PRO_DATANCOM)) { cpl_propertylist_update_int(header, PRO_DATANCOM, nraw); cpl_propertylist_set_comment(header, PRO_DATANCOM, "Number of combined frames"); } /* * PRO REC1 CALi NAME, PRO REC1 CALi CATG, and PRO REC1 CALi DATAMD5 */ ncal = 0; frame = cpl_frameset_get_first_const(framelist); while (frame != NULL) { if (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_CALIB) { ++ncal; snprintf(cval, FLEN_VALUE, PRO_REC_CALi_NAME, ncal); cpl_propertylist_update_string(header, cval, cpl_get_base_name(cpl_frame_get_filename(frame))); cpl_propertylist_set_comment(header, cval, "File name of calibration frame"); snprintf(cval, FLEN_VALUE, PRO_REC_CALi_CATG, ncal); cpl_propertylist_update_string(header, cval, cpl_frame_get_tag(frame)); cpl_propertylist_set_comment(header, cval, "Category of calibration frame"); snprintf(cval, FLEN_VALUE, PRO_REC_CALi_DATAMD5, ncal); switch (cpl_is_fits(cpl_frame_get_filename(frame))) { case 0: plist = cpl_propertylist_new(); break; case 1: plist = cpl_propertylist_load_regexp( cpl_frame_get_filename(frame), 0, "^" DATAMD5 "$", 0); cpl_ensure_code(plist != NULL, cpl_error_get_code()); break; default: cpl_ensure_code(0, cpl_error_get_code() == CPL_ERROR_NULL_INPUT ? CPL_ERROR_DATA_NOT_FOUND : cpl_error_get_code()); } if (cpl_propertylist_has(plist, DATAMD5)) { if (cpl_propertylist_get_type(plist, DATAMD5) != CPL_TYPE_STRING) { cpl_msg_warning(cpl_func, "Unexpected type for keyword " DATAMD5 " in input header"); } else { datamd5 = cpl_propertylist_get_string(plist, DATAMD5); cpl_propertylist_update_string(header, cval, datamd5); cpl_propertylist_set_comment(header, cval, "MD5 signature of calib frame"); } } cpl_propertylist_delete(plist); } frame = cpl_frameset_get_next_const(framelist); } npar = 0; /* The cast is OK, since param is not modified */ param = cpl_parameterlist_get_first((cpl_parameterlist*)parlist); while (param != NULL) { char pval[MAX_PLENGTH + 1]; ++npar; kname = cpl_parameter_get_alias(param, CPL_PARAMETER_MODE_CLI); switch (cpl_parameter_get_type(param)) { case CPL_TYPE_BOOL: if (cpl_parameter_get_bool(param) == 1) snprintf(pval, MAX_PLENGTH, "%s", "true"); else snprintf(pval, MAX_PLENGTH, "%s", "false"); break; case CPL_TYPE_INT: snprintf(pval, MAX_PLENGTH, "%d", cpl_parameter_get_int(param)); break; case CPL_TYPE_DOUBLE: snprintf(pval, MAX_PLENGTH, "%g", cpl_parameter_get_double(param)); break; case CPL_TYPE_STRING: snprintf(pval, MAX_PLENGTH, "%s", cpl_parameter_get_string(param)); break; default: /* * Theoretically impossible to get here */ cpl_ensure_code(0, CPL_ERROR_UNSPECIFIED); } snprintf(cval, FLEN_VALUE, PRO_REC_PARAMi_NAME, npar); cpl_propertylist_update_string(header, cval, kname); snprintf(cval, FLEN_VALUE, PRO_REC_PARAMi_VALUE, npar); cpl_propertylist_update_string(header, cval, pval); /* The cast is OK, since param is not modified */ param = cpl_parameterlist_get_next((cpl_parameterlist*)parlist); } return CPL_ERROR_NONE; } /*----------------------------------------------------------------------------*/ /** @brief Perform any DFS-compliancy required actions (DATAMD5/PIPEFILE update) @param self The list of frames with FITS products created by the recipe @return CPL_ERROR_NONE or the relevant CPL error code on error @note Each product frame must correspond to a FITS file created with a CPL FITS saving function. @error
CPL_ERROR_NULL_INPUT An input pointer is NULL.
CPL_ERROR_DATA_NOT_FOUND The input framelist contains a frame of type product with a missing filename.
CPL_ERROR_BAD_FILE_FORMAT The input framelist contains a frame of type product without a FITS card with key 'DATAMD5'.
CPL_ERROR_FILE_IO The input framelist contains a frame of type product for which the FITS card with key 'DATAMD5' could not be updated.
@enderror */ /*----------------------------------------------------------------------------*/ cpl_error_code cpl_dfs_update_product_header(cpl_frameset * self) { const char * md5key = DATAMD5; const cpl_frame * frame; int nupdate = 0; cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT); for (frame = cpl_frameset_get_first_const(self); frame != NULL; frame = cpl_frameset_get_next_const(self)) { if (cpl_frame_get_group(frame) == CPL_FRAME_GROUP_PRODUCT) { fitsfile * fproduct = NULL; const char * filename = cpl_frame_get_filename(frame); char card[FLEN_CARD]; char md5sum[1+MD5HASHSZ]; int error = 0; /* CFITSIO requires this to be zero */ int error2 = 0; /* CFITSIO requires this to be zero */ cpl_ensure_code(filename != NULL, CPL_ERROR_DATA_NOT_FOUND); if (fits_open_diskfile(&fproduct, filename, READWRITE, &error)) { return cpl_error_set_message_macro(cpl_func, CPL_ERROR_BAD_FILE_FORMAT, __FILE__, __LINE__, "fits_open_diskfile('%s') " "returned %d: %s", filename, error, cpl_tools_get_cfitsio_msg( error)); } /* fits_read_card() is missing const modifiers on the string arguments! fits_read_card() is trusted not to actually modify these strings - if that fails the behavior is undefined (e.g. segmentation violation). :-(((((((((((((((((((((((((((((((((((((((((((((( */ if (fits_read_card(fproduct, (char*)md5key, card, &error)) { (void)cpl_error_set_message_macro(cpl_func, CPL_ERROR_BAD_FILE_FORMAT, __FILE__, __LINE__, "fits_read_card('%s','%s') " "returned %d: %s", filename, md5key, error, cpl_tools_get_cfitsio_msg( error)); } else { /* assert( !error ); */ /* The fits header has the MD5-card, update it */ error = cpl_dfs_find_md5sum(fproduct, md5sum); if (!error) { error = cpl_dfs_update_key_string(fproduct, md5key, md5sum, "MD5 checksum"); if (!error) nupdate++; } if (!error) error = cpl_dfs_update_key_string(fproduct, PIPEFILE, filename, "Filename of data " "product"); } if (fits_close_file(fproduct, &error2)) { return cpl_error_set_message_macro(cpl_func, CPL_ERROR_FILE_IO, __FILE__, __LINE__, "fits_close_file('%s') " "returned %d: %s", filename, error2, cpl_tools_get_cfitsio_msg( error2)); } cpl_ensure_code(!error, error); } } cpl_msg_debug(cpl_func, "Updated the FITS card with key '%s' in the primary " "FITS header of %d product(s)", md5key, nupdate); return CPL_ERROR_NONE; } /**@}*/ /*----------------------------------------------------------------------------*/ /** @internal @brief Compute the MD5 hash of the data units in a FITS file. @param fproduct A CFITSIO FITS file structure @param md5sum Buffer with at least 33 (1+MD5HASHSZ) bytes of memory @return CPL_ERROR_NONE or the relevant CPL error code on error @note Upon success the CFITSIO FITS file structure is reset to the 1st HDU */ /*----------------------------------------------------------------------------*/ static cpl_error_code cpl_dfs_find_md5sum(fitsfile * fproduct, char * md5sum) { /* The MD5sum needs to be updated with calls to MD5Update(). For performance reasons the number of bytes read and passed to this function should be: 1) A multiple of a FITS block, 2880 (64 * 45) bytes 2) A multiple of file-system block size, likely a power of two. 3) Not exceed the size of the CPU cache (assuming that ffgbyt() will cause the loaded data to be placed in the cache where it will be readily available for MD5Update(). If this does not happen, nothing is gained - and no harm is done. 4) On cache-less architectures, the block size should simply prevent significant amounts of memory to be allocated. Oops: Disable blocking, because it causes the wrong MD5 sum for some blocksizes. For r.1.8 of tests/cpl_dfs-test.c blocking works for sizes greater or equal to the maximum data unit size, 362*2280, and for 2880 and 2 * 2880, but not for 181 * 2880 and not for 64 * 2880. This may be due to undocumented behaviour of ffgbyt() or MD5Update(). */ struct MD5Context ctx; unsigned char digest[(MD5HASHSZ)>>1]; int error = 0; const int hdutype = ANY_HDU; int next = 0; cpl_ensure_code(fproduct, CPL_ERROR_NULL_INPUT); cpl_ensure_code(md5sum, CPL_ERROR_NULL_INPUT); MD5Init(&ctx); /* Iterate through main HDU and all extensions */ do { long datastart, dataend; /* FIXME: fits_get_hduaddr() supports filesizes less than 2GiB, fits_get_hduaddrll() supports sizes less than 2^63 B, but is introduced after CFITSIO version 2.510 */ if (fits_get_hduaddr(fproduct, NULL, &datastart, &dataend, &error)) { return cpl_error_set_message_macro(cpl_func, CPL_ERROR_BAD_FILE_FORMAT, __FILE__, __LINE__, "fits_get_hduaddr() returned " "%d: %s", error, cpl_tools_get_cfitsio_msg(error)); } if (dataend > datastart) { void * buffer; const long datasize = dataend - datastart; /* Seek to beginning of Data Unit */ if (ffmbyt(fproduct, (OFF_T)datastart, 0, &error)) { return cpl_error_set_message_macro(cpl_func, CPL_ERROR_BAD_FILE_FORMAT, __FILE__, __LINE__, "ffmbyt(%lu) returned %d: " "%s", (unsigned long)datastart, error, cpl_tools_get_cfitsio_msg( error)); } buffer = cpl_malloc((size_t)datasize); /* Try to read datasize bytes */ if (ffgbyt(fproduct, datasize, buffer, &error)) { cpl_free(buffer); return cpl_error_set_message_macro(cpl_func, CPL_ERROR_BAD_FILE_FORMAT, __FILE__, __LINE__, "ffgbyt() returned %d: %s", error, cpl_tools_get_cfitsio_msg( error)); } /* Compute MD5 sum here */ MD5Update(&ctx, (const unsigned char *)buffer, (unsigned)datasize); cpl_free(buffer); } else { cpl_ensure_code(dataend == datastart, CPL_ERROR_ILLEGAL_OUTPUT); } next++; } while (!fits_movrel_hdu(fproduct, 1, (int*)&hdutype, &error)); if (error == END_OF_FILE) { error = 0; /* Reset CFITSIO error */ } else { return cpl_error_set_message_macro(cpl_func, CPL_ERROR_BAD_FILE_FORMAT, __FILE__, __LINE__, "fits_movrel_hdu() returned %d: %s", error, cpl_tools_get_cfitsio_msg(error)); } /* Move back to beginning */ if (fits_movabs_hdu(fproduct, 1, (int*)&hdutype, &error)) { return cpl_error_set_message_macro(cpl_func, CPL_ERROR_FILE_IO, __FILE__, __LINE__, "fits_movabs_hdu() returned %d: %s", error, cpl_tools_get_cfitsio_msg(error)); } MD5Final(digest, &ctx); /* Write digest into a string */ /* (static) caller quarantees that md5sum is long enough! */ sprintf(md5sum, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", digest[ 0], digest[ 1], digest[ 2], digest[ 3], digest[ 4], digest[ 5], digest[ 6], digest[ 7], digest[ 8], digest[ 9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]); return CPL_ERROR_NONE; } /*----------------------------------------------------------------------------*/ /** @internal @brief Update a FITS key in an CFITSIO open file @param fproduct The CFITSIO FITS file structure @param key The key of the card to update @param value The value to set @param value The (optional) comment to set @return CPL_ERROR_NONE or the relevant CPL error code on error */ /*----------------------------------------------------------------------------*/ static cpl_error_code cpl_dfs_update_key_string(fitsfile * fproduct, const char * key, const char * value, const char * comment) { int error = 0; /* CFITSIO requires this to be zero */ cpl_ensure_code(fproduct, CPL_ERROR_NULL_INPUT); cpl_ensure_code(key, CPL_ERROR_NULL_INPUT); cpl_ensure_code(value, CPL_ERROR_NULL_INPUT); /* fits_update_key() is missing const modifiers on the string arguments! fits_update_key() is trusted not to actually modify these strings - if that fails the behavior is undefined (e.g. segmentation violation). :-(((((((((((((((((((((((((((((((((((((((((((((( */ return fits_update_key(fproduct, TSTRING, (char*)key, (char*)value, (char*)comment, &error) ? cpl_error_set_message_macro(cpl_func, CPL_ERROR_FILE_IO, __FILE__, __LINE__, "fits_update_key" "('%s','%s') returned %d: %s", key, value, error, cpl_tools_get_cfitsio_msg(error)) : CPL_ERROR_NONE; } /*----------------------------------------------------------------------------*/ /** @internal @brief Determine whether a given file is a FITS file @param self The name of the fits @return 1 if the file is FITS, 0 if not, negative on error */ /*----------------------------------------------------------------------------*/ static int cpl_is_fits(const char * self) { int error = 0; int is_fits = 1; fitsfile * fptr; const char * errorfunc = NULL; cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, -1); if (fits_open_diskfile(&fptr, self, READONLY, &error)) { errorfunc = "fits_open_diskfile"; } else if (fits_read_imghdr(fptr, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &error)) { errorfunc = "fits_read_imghdr"; } if (error) { is_fits = error == FILE_NOT_OPENED ? -2 : 0; if (is_fits < 0) (void)cpl_error_set_message_macro(cpl_func, CPL_ERROR_FILE_NOT_FOUND, __FILE__, __LINE__, "%s('%s') returned %d: %s", errorfunc, self, error, cpl_tools_get_cfitsio_msg(error)); error = 0; } if (fptr != NULL && fits_close_file(fptr, &error) != 0) { /* FIXME: Is this even possible ? */ (void)cpl_error_set_message_macro(cpl_func, CPL_ERROR_BAD_FILE_FORMAT, __FILE__, __LINE__, "fits_close_file('%s') returned " "%d: %s", self, error, cpl_tools_get_cfitsio_msg(error)); } return is_fits; } /*----------------------------------------------------------------------------*/ /** @internal @brief Return a pointer to the basename of a full-path filename @param self The filename @return The pointer (possibly self, which may be NULL) @note The pointer returned by this function may not be used after self is de/re-allocated, nor after a call of the function with another string. */ /*----------------------------------------------------------------------------*/ static const char * cpl_get_base_name(const char * self) { const char * p = self ? strrchr(self, '/') : NULL; return p ? p + 1 : self; } /*----------------------------------------------------------------------------*/ /** @brief Open a new PAF file, output a default header. @param instrume Name of instrument in capitals (NACO, VISIR, etc.) @param recipe Name of recipe @param filename Filename of created product @return PAF stream or NULL on error This function creates a new PAF file with the requested file name. If another file already exists with the same name, it will be overwritten (if the file access rights allow it). This function returns an opened file pointer, ready to receive more data through fprintf()'s. The caller is responsible for fclose()ing the file. Possible _cpl_error_code_ set in this function: - CPL_ERROR_NULL_INPUT if an input pointer is NULL - CPL_ERROR_FILE_IO if fopen(), or fprintf() fails */ /*----------------------------------------------------------------------------*/ static FILE * cpl_dfs_paf_init(const char * instrume, const char * recipe, const char * filename) { FILE * paf = NULL; char * paf_id = NULL; const char paf_desc[] = "QC file"; int nlen; cpl_error_code error; cpl_ensure(instrume != NULL, CPL_ERROR_NULL_INPUT, NULL); cpl_ensure(recipe != NULL, CPL_ERROR_NULL_INPUT, NULL); cpl_ensure(filename != NULL, CPL_ERROR_NULL_INPUT, NULL); cpl_msg_info(cpl_func, "Writing PAF: %s" , filename); paf_id = cpl_sprintf("%s/%s", instrume, recipe); assert( paf_id != NULL); paf = fopen(filename, "w"); if (paf == NULL) { cpl_free(paf_id); cpl_ensure(0, CPL_ERROR_FILE_IO, NULL); } /* Some ugly, traditional error handling that obscures the actual functionality (i.e. to fprintf() some strings) */ error = CPL_ERROR_NONE; if (!error) { nlen = fprintf(paf, "PAF.HDR.START ;# start of header\n"); if (nlen <= PAF_KEY_LEN) error = CPL_ERROR_FILE_IO; } if (!error) { nlen = fprintf(paf, "PAF.TYPE \"pipeline product\" ;\n"); if (nlen <= PAF_KEY_LEN) error = CPL_ERROR_FILE_IO; } if (!error) { nlen = fprintf(paf, "PAF.ID \"%s\"\n", paf_id); if (nlen <= PAF_KEY_LEN) error = CPL_ERROR_FILE_IO; } if (!error) { nlen = fprintf(paf, "PAF.NAME \"%s\"\n", filename); if (nlen <= PAF_KEY_LEN) error = CPL_ERROR_FILE_IO; } if (!error) { nlen = fprintf(paf, "PAF.DESC \"%s\"\n", paf_desc); if (nlen <= PAF_KEY_LEN) error = CPL_ERROR_FILE_IO; } if (!error) { nlen = fprintf(paf, "PAF.CHCK.CHECKSUM \"\"\n"); if (nlen <= PAF_KEY_LEN) error = CPL_ERROR_FILE_IO; } if (!error) { nlen = fprintf(paf, "PAF.HDR.END ;# end of header\n"); if (nlen <= PAF_KEY_LEN) error = CPL_ERROR_FILE_IO; } if (!error) { nlen = fprintf(paf, "\n"); if (nlen != 1) error = CPL_ERROR_FILE_IO; } cpl_free(paf_id); if (error) { (void)fclose(paf); cpl_msg_error(cpl_func, "Could not write PAF: %s", filename); cpl_ensure(0, error, NULL); } return paf; } /*----------------------------------------------------------------------------*/ /** @brief Print a propertylist as PAF @param self Propertylist to be printed @param paf PAF stream @return CPL_ERROR_NONE or the relevant _cpl_error_code_ on error The property names are printed with these modifications: 1) The prefix "ESO " is dropped. 2) Any space-character is replaced by a . (dot). Thus a property name ESO QC PART1 PART2 PART3 will cause the printing of a line with QC.PART1.PART2.PART3 as key. Supported property-types: CPL_TYPE_CHAR (cast to int) CPL_TYPE_INT CPL_TYPE_LONG (only if sizeof(long) == sizeof(int), cast to int) CPL_TYPE_FLOAT (cast to double) CPL_TYPE_DOUBLE CPL_TYPE_STRING Possible _cpl_error_code_ set in this function: - CPL_ERROR_NULL_INPUT if an input pointer is NULL - CPL_ERROR_FILE_IO if fprintf() fails - CPL_ERROR_UNSUPPORTED_MODE if the list contains unsupported property-types */ /*----------------------------------------------------------------------------*/ static cpl_error_code cpl_dfs_paf_dump(const cpl_propertylist * self, FILE * paf) { int i; cpl_ensure_code(self, CPL_ERROR_NULL_INPUT); cpl_ensure_code(paf, CPL_ERROR_NULL_INPUT); for (i=0; i < cpl_propertylist_get_size(self); i++) { const cpl_property * prop = cpl_propertylist_get((cpl_propertylist *) self, i); const char * name = cpl_property_get_name(prop); const char * comment = cpl_property_get_comment(prop); char * qckey; /* Initialization needed in absence of #if for CPL_TYPE_LONG */ cpl_error_code err = CPL_ERROR_UNSUPPORTED_MODE; if (strstr(name, "ESO ") == name) { /* Drop prefix "ESO " */ qckey = cpl_malloc(strlen(name) - 3); memcpy(qckey, name+4, strlen(name) - 3); } else if (strstr(name, " ")) qckey = cpl_strdup(name); else qckey = (char *)name; /* Don't need to modify the key */ if (qckey != name) { /* Replace with . */ char * p; for (p = qckey; *p != '\0'; p++) if (*p == ' ') *p = '.'; } /* Print the property as PAF */ switch (cpl_property_get_type(prop)) { case CPL_TYPE_CHAR: err = cpl_dfs_paf_dump_int(qckey, cpl_property_get_char(prop), comment, paf); case CPL_TYPE_INT: err = cpl_dfs_paf_dump_int(qckey, cpl_property_get_int(prop), comment, paf); break; case CPL_TYPE_LONG: if (sizeof(long) == sizeof(int)) /* FIXME: Change to #if */ err = cpl_dfs_paf_dump_int(qckey, cpl_property_get_long(prop), comment, paf); break; case CPL_TYPE_FLOAT: err = cpl_dfs_paf_dump_double(qckey, cpl_property_get_float(prop), comment, paf); break; case CPL_TYPE_DOUBLE: err = cpl_dfs_paf_dump_double(qckey, cpl_property_get_double(prop), comment, paf); break; case CPL_TYPE_STRING: err = cpl_dfs_paf_dump_string(qckey, cpl_property_get_string(prop), comment, paf); break; default: err = CPL_ERROR_UNSUPPORTED_MODE; } if (qckey != name) cpl_free(qckey); cpl_ensure_code(!err, err); } return CPL_ERROR_NONE; } /*----------------------------------------------------------------------------*/ /** @brief Print a string-property as PAF @param key Property key @param value Property value @param comment Optional property comment @param paf PAF stream @return CPL_ERROR_NONE or the relevant _cpl_error_code_ on error Possible _cpl_error_code_ set in this function: - CPL_ERROR_NULL_INPUT if a non-optional input pointer is NULL - CPL_ERROR_FILE_IO if fprintf() fails */ /*----------------------------------------------------------------------------*/ static cpl_error_code cpl_dfs_paf_dump_string(const char * key, const char * value, const char * comment, FILE * paf) { cpl_ensure_code(paf, CPL_ERROR_NULL_INPUT); cpl_ensure_code(key, CPL_ERROR_NULL_INPUT); cpl_ensure_code(value, CPL_ERROR_NULL_INPUT); if (comment == NULL) cpl_ensure_code(fprintf(paf, PAF_KEY_FORMAT "\"%s\"\n", key, value) > PAF_KEY_LEN, CPL_ERROR_FILE_IO); else cpl_ensure_code(fprintf(paf, PAF_KEY_FORMAT "\"%s\" ; # %s\n", key, value, comment) > PAF_KEY_LEN, CPL_ERROR_FILE_IO); return CPL_ERROR_NONE; } /*----------------------------------------------------------------------------*/ /** @brief Print a double-property as PAF @param key Property key @param value Property value @param comment Optional property comment @param paf PAF stream @return CPL_ERROR_NONE or the relevant _cpl_error_code_ on error @see cpl_dfs_paf_dump_string */ /*----------------------------------------------------------------------------*/ static cpl_error_code cpl_dfs_paf_dump_double(const char * key, double value, const char * comment, FILE * paf) { cpl_ensure_code(paf, CPL_ERROR_NULL_INPUT); cpl_ensure_code(key, CPL_ERROR_NULL_INPUT); if (comment == NULL) cpl_ensure_code(fprintf(paf, PAF_KEY_FORMAT "%.10g\n", key, value) > PAF_KEY_LEN, CPL_ERROR_FILE_IO); else cpl_ensure_code(fprintf(paf, PAF_KEY_FORMAT "%.10g ; # %s\n", key, value, comment) > PAF_KEY_LEN, CPL_ERROR_FILE_IO); return CPL_ERROR_NONE; } /*----------------------------------------------------------------------------*/ /** @brief Print an int-property as PAF @param key Property key @param value Property value @param comment Optional property comment @param paf PAF stream @return CPL_ERROR_NONE or the relevant _cpl_error_code_ on error @see cpl_dfs_paf_dump_string */ /*----------------------------------------------------------------------------*/ static cpl_error_code cpl_dfs_paf_dump_int(const char * key, int value, const char * comment, FILE * paf) { cpl_ensure_code(paf, CPL_ERROR_NULL_INPUT); cpl_ensure_code(key, CPL_ERROR_NULL_INPUT); if (comment == NULL) cpl_ensure_code(fprintf(paf, PAF_KEY_FORMAT "%d\n", key, value) > PAF_KEY_LEN, CPL_ERROR_FILE_IO); else cpl_ensure_code(fprintf(paf, PAF_KEY_FORMAT "%d ; # %s\n", key, value, comment) > PAF_KEY_LEN, CPL_ERROR_FILE_IO); return CPL_ERROR_NONE; } /*----------------------------------------------------------------------------*/ /** @brief Save either an image or table as a pipeline product @param allframes The list of input frames for the recipe @param parlist The list of input parameters @param usedframes The list of raw/calibration frames used for this product @param imagelist The imagelist to be saved or NULL @param image The image to be saved or NULL @param bpp Bits per pixel @param table The table to be saved or NULL @param tablelist Optional propertylist to append to table extension or NULL @param recipe The recipe name @param procat The product category tag @param applist Optional propertylist to append to main header or NULL @param remregexp Optional regexp of properties not to put in main header @param pipe_id PACKAGE "/" PACKAGE_VERSION @param filename Filename of created product @return CPL_ERROR_NONE or the relevant CPL error code on error @note At most one of imagelist, image and table may be non-NULL, if all are NULL the product frame type will be that of an image @see cpl_dfs_save_image() */ /*----------------------------------------------------------------------------*/ static cpl_error_code cpl_dfs_product_save(cpl_frameset * allframes, const cpl_parameterlist * parlist, const cpl_frameset * usedframes, const cpl_imagelist * imagelist, const cpl_image * image, cpl_type_bpp bpp, const cpl_table * table, const cpl_propertylist * tablelist, const char * recipe, const char * procat, const cpl_propertylist * applist, const char * remregexp, const char * pipe_id, const char * filename) { cpl_propertylist * plist; cpl_frame * product_frame; /* Inside this function the product-types are numbered: 0: imagelist 1: table 2: image */ const unsigned pronum = imagelist != NULL ? 0 : table != NULL ? 1 : 2; const char * proname[] = {"imagelist", "table", "image"}; /* FIXME: Define a frame type for an imagelist */ const int protype[] = {CPL_FRAME_TYPE_ANY, CPL_FRAME_TYPE_TABLE, CPL_FRAME_TYPE_IMAGE}; cpl_error_code error = CPL_ERROR_NONE; /* No more than one of imagelist, table and image may be non-NULL */ /* tablelist may only be non-NULL when table is non-NULL */ if (imagelist != NULL) { assert(pronum == 0); assert(image == NULL); assert(table == NULL); assert(tablelist == NULL); } else if (table != NULL) { assert(pronum == 1); assert(imagelist == NULL); assert(image == NULL); } else { /* image may be NULL, in which case all three are NULL */ assert(pronum == 2); assert(imagelist == NULL); assert(table == NULL); assert(tablelist == NULL); } cpl_ensure_code(allframes != NULL, CPL_ERROR_NULL_INPUT); cpl_ensure_code(parlist != NULL, CPL_ERROR_NULL_INPUT); cpl_ensure_code(usedframes != NULL, CPL_ERROR_NULL_INPUT); cpl_ensure_code(recipe != NULL, CPL_ERROR_NULL_INPUT); cpl_ensure_code(procat != NULL, CPL_ERROR_NULL_INPUT); cpl_ensure_code(pipe_id != NULL, CPL_ERROR_NULL_INPUT); cpl_ensure_code(filename != NULL, CPL_ERROR_NULL_INPUT); cpl_msg_info(cpl_func, "Writing FITS %s product(%s): %s", proname[pronum], procat, filename); product_frame = cpl_frame_new(); /* Create product frame */ error |= cpl_frame_set_filename(product_frame, filename); error |= cpl_frame_set_tag(product_frame, procat); error |= cpl_frame_set_type(product_frame, protype[pronum]); error |= cpl_frame_set_group(product_frame, CPL_FRAME_GROUP_PRODUCT); error |= cpl_frame_set_level(product_frame, CPL_FRAME_LEVEL_FINAL); if (error) { cpl_frame_delete(product_frame); cpl_ensure_code(0, cpl_error_get_code()); } plist = cpl_propertylist_new(); /* Add any QC parameters here */ if (applist != NULL) error = cpl_propertylist_append(plist, applist); /* Add DataFlow keywords */ if (!error) error = cpl_dfs_setup_product_header(plist, product_frame, usedframes, parlist, recipe, pipe_id, cpl_dfs_pro_did); if (remregexp != NULL && !error) { cpl_errorstate prestate = cpl_errorstate_get(); (void)cpl_propertylist_erase_regexp(plist, remregexp, 0); if (!cpl_errorstate_is_equal(prestate)) error = cpl_error_get_code(); } if (!error) { switch (pronum) { case 0: error = cpl_imagelist_save(imagelist, filename, bpp, plist, CPL_IO_DEFAULT); break; case 1: error = cpl_table_save(table, plist, tablelist, filename, CPL_IO_DEFAULT); break; default: /* case 2: */ error = cpl_image_save(image, filename, bpp, plist, CPL_IO_DEFAULT); } } if (!error) { /* Insert the frame of the saved file in the input frameset */ error = cpl_frameset_insert(allframes, product_frame); } else { cpl_frame_delete(product_frame); } cpl_propertylist_delete(plist); cpl_ensure_code(!error, error); return CPL_ERROR_NONE; } /* Definitions of static MD5 functions */ #include "md5.c"