IPP Software Navigation Tools IPP Links Communication Pan-STARRS Links

Ignore:
Timestamp:
Dec 13, 2005, 5:47:35 PM (21 years ago)
Author:
Paul Price
Message:

Upgrading for psLib-0.9.0

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/archive/scripts/src/phase2/pmFPAConceptsGet.c

    r5633 r5786  
    1414
    1515psMetadataItem *p_pmFPAConceptGetFromCamera(pmCell *cell, // The cell
    16                                             const char *concept // Name of concept
     16                                            const char *concept // Name of concept
    1717    )
    1818{
    1919    if (cell) {
    20         psMetadata *camera = cell->camera;      // Camera data
    21         // Need the CELL.NAME first, which should be in the cell->concepts since creation of the cell
    22         bool mdStatus = true;           // Status of MD lookup
    23         // Finally, the info that we want
    24         psMetadataItem *item = psMetadataLookup(camera, concept);
    25         return item;
     20        psMetadata *camera = cell->camera;      // Camera data
     21        // Need the CELL.NAME first, which should be in the cell->concepts since creation of the cell
     22        bool mdStatus = true;           // Status of MD lookup
     23        // Finally, the info that we want
     24        psMetadataItem *item = psMetadataLookup(camera, concept);
     25        return item;
    2626    }
    2727    return NULL;
    28 }   
     28}
    2929
    3030
    3131psMetadataItem *p_pmFPAConceptGetFromHeader(pmFPA *fpa, // The FPA that contains the chip
    32                                             pmChip *chip, // The chip that contains the cell
    33                                             pmCell *cell, // The cell
    34                                             const char *concept // Name of concept
    35     )
    36 {
    37     bool mdStatus = true;               // Status of MD lookup
     32                                            pmChip *chip, // The chip that contains the cell
     33                                            pmCell *cell, // The cell
     34                                            const char *concept // Name of concept
     35    )
     36{
     37    bool mdStatus = true;               // Status of MD lookup
    3838    psMetadata *translation = psMetadataLookupMD(&mdStatus, fpa->camera, "TRANSLATION"); // FITS translation
    3939    if (! mdStatus) {
    40         psError(PS_ERR_IO, false, "Unable to find TRANSLATION in camera configuration.\n");
    41         return NULL;
     40        psError(PS_ERR_IO, false, "Unable to find TRANSLATION in camera configuration.\n");
     41        return NULL;
    4242    }
    4343
     
    4545    const char *keyword = psMetadataLookupString(&mdStatus, translation, concept);
    4646    if (mdStatus && strlen(keyword) > 0) {
    47         // We have a FITS header to look up --- search each level
    48         if (cell && cell->hdu) {
    49             psMetadataItem *cellItem = psMetadataLookup(cell->hdu->header, keyword);
    50             if (cellItem) {
    51                 // XXX: Need to clean up before returning
    52                 return cellItem;
    53             }
    54         }
    55 
    56         if (chip && chip->hdu) {
    57             psMetadataItem *chipItem = psMetadataLookup(chip->hdu->header, keyword);
    58             if (chipItem) {
    59                 // XXX: Need to clean up before returning
    60                 return chipItem;
    61             }
    62         }
    63 
    64         if (fpa->hdu) {
    65             psMetadataItem *fpaItem = psMetadataLookup(fpa->hdu->header, keyword);
    66             if (fpaItem) {
    67                 // XXX: Need to clean up before returning
    68                 return fpaItem;
    69             }
    70         }
    71 
    72         if (fpa->phu) {
    73             psMetadataItem *fpaItem = psMetadataLookup(fpa->phu, keyword);
    74             if (fpaItem) {
    75                 // XXX: Need to clean up before returning
    76                 return fpaItem;
    77             }
    78         }
     47        // We have a FITS header to look up --- search each level
     48        if (cell && cell->hdu) {
     49            psMetadataItem *cellItem = psMetadataLookup(cell->hdu->header, keyword);
     50            if (cellItem) {
     51                // XXX: Need to clean up before returning
     52                return cellItem;
     53            }
     54        }
     55
     56        if (chip && chip->hdu) {
     57            psMetadataItem *chipItem = psMetadataLookup(chip->hdu->header, keyword);
     58            if (chipItem) {
     59                // XXX: Need to clean up before returning
     60                return chipItem;
     61            }
     62        }
     63
     64        if (fpa->hdu) {
     65            psMetadataItem *fpaItem = psMetadataLookup(fpa->hdu->header, keyword);
     66            if (fpaItem) {
     67                // XXX: Need to clean up before returning
     68                return fpaItem;
     69            }
     70        }
     71
     72        if (fpa->phu) {
     73            psMetadataItem *fpaItem = psMetadataLookup(fpa->phu, keyword);
     74            if (fpaItem) {
     75                // XXX: Need to clean up before returning
     76                return fpaItem;
     77            }
     78        }
    7979    }
    8080
     
    8686// Look for a default
    8787psMetadataItem *p_pmFPAConceptGetFromDefault(pmFPA *fpa, // The FPA that contains the chip
    88                                              pmChip *chip, // The chip that contains the cell
    89                                              pmCell *cell, // The cell
    90                                              const char *concept // Name of concept
    91     )
    92 {
    93     bool mdOK = true;                   // Status of MD lookup
     88                                             pmChip *chip, // The chip that contains the cell
     89                                             pmCell *cell, // The cell
     90                                             const char *concept // Name of concept
     91    )
     92{
     93    bool mdOK = true;                   // Status of MD lookup
    9494    psMetadata *defaults = psMetadataLookupMD(&mdOK, fpa->camera, "DEFAULTS");
    9595    if (! mdOK) {
    96         psError(PS_ERR_IO, false, "Unable to find DEFAULTS in camera configuration.\n");
    97         return NULL;
     96        psError(PS_ERR_IO, false, "Unable to find DEFAULTS in camera configuration.\n");
     97        return NULL;
    9898    }
    9999
    100100    psMetadataItem *defItem = psMetadataLookup(defaults, concept);
    101101    if (defItem) {
    102         if (defItem->type == PS_DATA_METADATA) {
    103             // A dependent default
    104             psTrace(__func__, 7, "Evaluating dependent default....\n");
    105             psMetadata *dependents = defItem->data.V; // The list of dependents
    106             // Find out what it depends on
    107             psString dependName = psStringCopy(concept);
    108             psStringAppend(&dependName, ".DEPEND");
    109             psString dependsOn = psMetadataLookupString(&mdOK, defaults, dependName);
    110             if (! mdOK) {
    111                 psError(PS_ERR_IO, false, "Unable to find %s in camera configuration for dependent default"
    112                         " --- ignored\n", dependName);
    113                 // XXX: Need to clean up before returning
    114                 return NULL;
    115             }
    116             psFree(dependName);
    117             // Find the value of the dependent concept
    118             psMetadataItem *depItem = p_pmFPAConceptGetFromHeader(fpa, chip, cell, dependsOn);
    119             if (! depItem) {
    120                 psError(PS_ERR_IO, true, "Unable to find value for %s (required for %s)\n", dependsOn,
    121                         concept);
    122                 return NULL;
    123             }
    124             if (depItem->type != PS_DATA_STRING) {
    125                 psError(PS_ERR_IO, true, "Value of %s is not of type string, as required for dependency"
    126                         " --- ignored.\n", dependsOn);
    127             }
    128 
    129             defItem = psMetadataLookup(dependents, depItem->data.V);    // This is now what we were after
    130         }
     102        if (defItem->type == PS_DATA_METADATA) {
     103            // A dependent default
     104            psTrace(__func__, 7, "Evaluating dependent default....\n");
     105            psMetadata *dependents = defItem->data.V; // The list of dependents
     106            // Find out what it depends on
     107            psString dependName = psStringCopy(concept);
     108            psStringAppend(&dependName, ".DEPEND");
     109            psString dependsOn = psMetadataLookupString(&mdOK, defaults, dependName);
     110            if (! mdOK) {
     111                psError(PS_ERR_IO, false, "Unable to find %s in camera configuration for dependent default"
     112                        " --- ignored\n", dependName);
     113                // XXX: Need to clean up before returning
     114                return NULL;
     115            }
     116            psFree(dependName);
     117            // Find the value of the dependent concept
     118            psMetadataItem *depItem = p_pmFPAConceptGetFromHeader(fpa, chip, cell, dependsOn);
     119            if (! depItem) {
     120                psError(PS_ERR_IO, true, "Unable to find value for %s (required for %s)\n", dependsOn,
     121                        concept);
     122                return NULL;
     123            }
     124            if (depItem->type != PS_DATA_STRING) {
     125                psError(PS_ERR_IO, true, "Value of %s is not of type string, as required for dependency"
     126                        " --- ignored.\n", dependsOn);
     127            }
     128
     129            defItem = psMetadataLookup(dependents, depItem->data.V);    // This is now what we were after
     130        }
    131131    }
    132132
    133133    // XXX: Need to clean up before returning
    134     return defItem;                     // defItem is either NULL or points to what was desired
     134    return defItem;                     // defItem is either NULL or points to what was desired
    135135}
    136136
     
    139139// XXX: Not tested
    140140psMetadataItem *p_pmFPAConceptGetFromDB(pmFPA *fpa, // The FPA that contains the chip
    141                                         pmChip *chip, // The chip that contains the cell
    142                                         pmCell *cell, // The cell
    143                                         psDB *db,       // DB handle
    144                                         const char *concept // Name of concept
     141                                        pmChip *chip, // The chip that contains the cell
     142                                        pmCell *cell, // The cell
     143                                        psDB *db,       // DB handle
     144                                        const char *concept // Name of concept
    145145    )
    146146{
    147147    if (! db) {
    148         // No database initialised
    149         return NULL;
    150     }
    151 
    152     bool mdStatus = true;               // Status of MD lookup
     148        // No database initialised
     149        return NULL;
     150    }
     151
     152    bool mdStatus = true;               // Status of MD lookup
    153153    psMetadata *database = psMetadataLookupMD(&mdStatus, fpa->camera, "DATABASE");
    154154    if (! mdStatus) {
    155         // No error, because not everyone needs to use the DB
    156         return NULL;
     155        // No error, because not everyone needs to use the DB
     156        return NULL;
    157157    }
    158158
    159159    psMetadata *dbLookup = psMetadataLookupMD(&mdStatus, database, concept);
    160160    if (dbLookup) {
    161         const char *tableName = psMetadataLookupString(&mdStatus, dbLookup, "TABLE"); // Name of the table
    162         const char *colName = psMetadataLookupString(&mdStatus, dbLookup, "COLUMN"); // Name of the column
    163         const char *givenCols = psMetadataLookupString(&mdStatus, dbLookup, "GIVENDBCOL"); // Name of "where"
    164                                                                                            // columns
    165         const char *givenPS = psMetadataLookupString(&mdStatus, dbLookup, "GIVENPS"); // Values for "where"
    166                                                                                       // columns
    167        
    168         // Now, need to get the "given"s
    169         if (strlen(givenCols) || strlen(givenPS)) {
    170             psList *cols = papSplit(givenCols, ",;"); // List of column names
    171             psList *values = papSplit(givenPS, ",;"); // List of value names for the columns
    172             psMetadata *selection = psMetadataAlloc(); // The stuff to select in the DB
    173             if (cols->n != values->n) {
    174                 psLogMsg(__func__, PS_LOG_WARN, "The GIVENDBCOL and GIVENPS entries for %s do not have "
    175                         "the same number of entries --- ignored.\n", concept);
    176             } else {
    177                 // Iterators for the lists
    178                 psListIterator *colsIter = psListIteratorAlloc(cols, PS_LIST_HEAD, false);
    179                 psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false);
    180                 char *column = NULL;    // Name of the column
    181                 while (column = psListGetAndIncrement(colsIter)) {
    182                     char *name = psListGetAndIncrement(valuesIter); // Name for the value
    183                     if (!strlen(column) || !strlen(name)) {
    184                         psLogMsg(__func__, PS_LOG_WARN, "One of the columns or value names for %s is "
    185                                 " empty --- ignored.\n", concept);
    186                     } else {
    187                         // Search for the value name
    188                         psMetadataItem *item = p_pmFPAConceptGetFromHeader(fpa, chip, cell, name);
    189                         if (! item) {
    190                             item = p_pmFPAConceptGetFromDefault(fpa, chip, cell, name);
    191                         }
    192                         if (! item) {
    193                             psLogMsg(__func__, PS_LOG_ERROR, "Unable to find the value name %s for DB "
    194                                      " lookup on %s --- ignored.\n", name, concept);
    195                         } else {
    196                             // We need to create a new psMetadataItem.  I don't think we can't simply hack
    197                             // the existing one, since that could conceivably cause memory leaks
    198                             psMetadataItem *newItem = psMetadataItemAlloc(concept, item->type,
    199                                                                           item->comment, item->data.V);
    200                             psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
    201                             psFree(newItem);
    202                         }
    203                     }
    204                     psFree(name);
    205                     psFree(column);
    206                 } // Iterating through the columns
    207                 psFree(colsIter);
    208                 psFree(valuesIter);
    209                
    210                 psArray *dbResult = psDBSelectRows(db, tableName, selection, 2); // Lookup result
    211                 // Note that we use limit=2 in order to test if there are multiple rows returned
    212                
    213                 psMetadataItem *result = NULL; // The final result of the DB lookup
    214                 if (dbResult->n == 0) {
    215                     psLogMsg(__func__, PS_LOG_WARN, "Unable to find any rows in DB for %s --- ignored\n",
    216                              concept);
    217                 } else {
    218                     if (dbResult-> n > 1) {
    219                         psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
    220                                 " using the first one only.\n", concept);
    221                     }
    222                     result = (psMetadataItem*)dbResult->data[0];
    223                 }
    224                 // XXX: Need to clean up before returning
    225                 return result;
    226             }
    227             psFree(cols);
    228             psFree(values);
    229         }
     161        const char *tableName = psMetadataLookupString(&mdStatus, dbLookup, "TABLE"); // Name of the table
     162        const char *colName = psMetadataLookupString(&mdStatus, dbLookup, "COLUMN"); // Name of the column
     163        const char *givenCols = psMetadataLookupString(&mdStatus, dbLookup, "GIVENDBCOL"); // Name of "where"
     164                                                                                           // columns
     165        const char *givenPS = psMetadataLookupString(&mdStatus, dbLookup, "GIVENPS"); // Values for "where"
     166                                                                                      // columns
     167
     168        // Now, need to get the "given"s
     169        if (strlen(givenCols) || strlen(givenPS)) {
     170            psList *cols = papSplit(givenCols, ",;"); // List of column names
     171            psList *values = papSplit(givenPS, ",;"); // List of value names for the columns
     172            psMetadata *selection = psMetadataAlloc(); // The stuff to select in the DB
     173            if (cols->n != values->n) {
     174                psLogMsg(__func__, PS_LOG_WARN, "The GIVENDBCOL and GIVENPS entries for %s do not have "
     175                        "the same number of entries --- ignored.\n", concept);
     176            } else {
     177                // Iterators for the lists
     178                psListIterator *colsIter = psListIteratorAlloc(cols, PS_LIST_HEAD, false);
     179                psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false);
     180                char *column = NULL;    // Name of the column
     181                while (column = psListGetAndIncrement(colsIter)) {
     182                    char *name = psListGetAndIncrement(valuesIter); // Name for the value
     183                    if (!strlen(column) || !strlen(name)) {
     184                        psLogMsg(__func__, PS_LOG_WARN, "One of the columns or value names for %s is "
     185                                " empty --- ignored.\n", concept);
     186                    } else {
     187                        // Search for the value name
     188                        psMetadataItem *item = p_pmFPAConceptGetFromHeader(fpa, chip, cell, name);
     189                        if (! item) {
     190                            item = p_pmFPAConceptGetFromDefault(fpa, chip, cell, name);
     191                        }
     192                        if (! item) {
     193                            psLogMsg(__func__, PS_LOG_ERROR, "Unable to find the value name %s for DB "
     194                                     " lookup on %s --- ignored.\n", name, concept);
     195                        } else {
     196                            // We need to create a new psMetadataItem.  I don't think we can't simply hack
     197                            // the existing one, since that could conceivably cause memory leaks
     198                            psMetadataItem *newItem = psMetadataItemAlloc(concept, item->type,
     199                                                                          item->comment, item->data.V);
     200                            psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
     201                            psFree(newItem);
     202                        }
     203                    }
     204                    psFree(name);
     205                    psFree(column);
     206                } // Iterating through the columns
     207                psFree(colsIter);
     208                psFree(valuesIter);
     209
     210                psArray *dbResult = psDBSelectRows(db, tableName, selection, 2); // Lookup result
     211                // Note that we use limit=2 in order to test if there are multiple rows returned
     212
     213                psMetadataItem *result = NULL; // The final result of the DB lookup
     214                if (dbResult->n == 0) {
     215                    psLogMsg(__func__, PS_LOG_WARN, "Unable to find any rows in DB for %s --- ignored\n",
     216                             concept);
     217                } else {
     218                    if (dbResult-> n > 1) {
     219                        psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
     220                                " using the first one only.\n", concept);
     221                    }
     222                    result = (psMetadataItem*)dbResult->data[0];
     223                }
     224                // XXX: Need to clean up before returning
     225                return result;
     226            }
     227            psFree(cols);
     228            psFree(values);
     229        }
    230230    } // Doing the "given"s.
    231231
     
    236236// Concept lookup
    237237psMetadataItem *p_pmFPAConceptGet(pmFPA *fpa, // The FPA
    238                                   pmChip *chip,// The chip
    239                                   pmCell *cell, // The cell
    240                                   psDB *db, // DB handle
    241                                   const char *concept // Concept name
     238                                  pmChip *chip,// The chip
     239                                  pmCell *cell, // The cell
     240                                  psDB *db, // DB handle
     241                                  const char *concept // Concept name
    242242    )
    243243{
     
    245245    psMetadataItem *item = p_pmFPAConceptGetFromCamera(cell, concept);
    246246    if (! item) {
    247         item = p_pmFPAConceptGetFromHeader(fpa, chip, cell, concept);
     247        item = p_pmFPAConceptGetFromHeader(fpa, chip, cell, concept);
    248248    }
    249249    if (! item) {
     
    257257
    258258
    259 void p_pmFPAConceptGetF32(pmFPA *fpa,   // The FPA
    260                           pmChip *chip, // The chip
    261                           pmCell *cell, // The cell
    262                           psDB *db,     // DB handle
    263                           psMetadata *concepts, // The concepts MD
    264                           const char *name, // Name of the concept
    265                           const char *comment // Comment for concept
     259void p_pmFPAConceptGetF32(pmFPA *fpa,   // The FPA
     260                          pmChip *chip, // The chip
     261                          pmCell *cell, // The cell
     262                          psDB *db,     // DB handle
     263                          psMetadata *concepts, // The concepts MD
     264                          const char *name, // Name of the concept
     265                          const char *comment // Comment for concept
    266266    )
    267267{
     
    269269    float value = NAN;
    270270    if (item) {
    271         switch (item->type) {
    272           case PS_DATA_F32:
    273             value = item->data.F32;
    274             break;
    275           case PS_DATA_F64:
    276             // Assume it's OK to truncate to floating point from double
    277             value = (float)item->data.F64;
    278             break;
    279           case PS_DATA_S32:
    280             // Promote to float
    281             value = (float)item->data.S32;
    282             break;
    283           default:
    284             psError(PS_ERR_IO, true, "Concept %s (%s) is not of floating point type (%x) --- treating as "
    285                     "undefined.\n", name, comment, item->type);
    286         }
     271        switch (item->type) {
     272          case PS_DATA_F32:
     273            value = item->data.F32;
     274            break;
     275          case PS_DATA_F64:
     276            // Assume it's OK to truncate to floating point from double
     277            value = (float)item->data.F64;
     278            break;
     279          case PS_DATA_S32:
     280            // Promote to float
     281            value = (float)item->data.S32;
     282            break;
     283          default:
     284            psError(PS_ERR_IO, true, "Concept %s (%s) is not of floating point type (%x) --- treating as "
     285                    "undefined.\n", name, comment, item->type);
     286        }
    287287    } else {
    288         psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
     288        psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
    289289    }
    290290    psTrace(__func__, 7, "Adding %s (%s): %f\n", name, comment, value);
    291291
    292     psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_TYPE_F32, comment, value);
    293 }
    294 
    295 void p_pmFPAConceptGetF64(pmFPA *fpa,   // The FPA
    296                           pmChip *chip, // The chip
    297                           pmCell *cell, // The cell
    298                           psDB *db,     // DB handle
    299                           psMetadata *concepts, // The concepts MD
    300                           const char *name, // Name of the concept
    301                           const char *comment // Comment for concept
     292    psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_TYPE_F32 | PS_META_REPLACE, comment, value);
     293}
     294
     295void p_pmFPAConceptGetF64(pmFPA *fpa,   // The FPA
     296                          pmChip *chip, // The chip
     297                          pmCell *cell, // The cell
     298                          psDB *db,     // DB handle
     299                          psMetadata *concepts, // The concepts MD
     300                          const char *name, // Name of the concept
     301                          const char *comment // Comment for concept
    302302    )
    303303{
     
    305305    double value = NAN;
    306306    if (item) {
    307         switch (item->type) {
    308           case PS_TYPE_F64:
    309             value = item->data.F64;
    310             break;
    311           case PS_TYPE_F32:
    312             // Promote to double
    313             value = (double)item->data.F32;
    314             break;
    315           case PS_TYPE_S32:
    316             // Promote to double
    317             value = (double)item->data.S32;
    318             break;
    319           default:
    320             psError(PS_ERR_IO, true, "Concept %s (%s) is not of double-precision floating point type (%x) "
    321                     "--- treating as undefined.\n", name, comment, item->type);
    322         }
     307        switch (item->type) {
     308          case PS_TYPE_F64:
     309            value = item->data.F64;
     310            break;
     311          case PS_TYPE_F32:
     312            // Promote to double
     313            value = (double)item->data.F32;
     314            break;
     315          case PS_TYPE_S32:
     316            // Promote to double
     317            value = (double)item->data.S32;
     318            break;
     319          default:
     320            psError(PS_ERR_IO, true, "Concept %s (%s) is not of double-precision floating point type (%x) "
     321                    "--- treating as undefined.\n", name, comment, item->type);
     322        }
    323323    } else {
    324         psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
     324        psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
    325325    }
    326326    psTrace(__func__, 7, "Adding %s (%s): %f\n", name, comment, value);
    327327
    328     psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_TYPE_F64, comment, value);
    329 }
    330 
    331 void p_pmFPAConceptGetS32(pmFPA *fpa,   // The FPA
    332                           pmChip *chip, // The chip
    333                           pmCell *cell, // The cell
    334                           psDB *db,     // DB handle
    335                           psMetadata *concepts, // The concepts MD
    336                           const char *name, // Name of the concept
    337                           const char *comment // Comment for concept
     328    psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_TYPE_F64 | PS_META_REPLACE, comment, value);
     329}
     330
     331void p_pmFPAConceptGetS32(pmFPA *fpa,   // The FPA
     332                          pmChip *chip, // The chip
     333                          pmCell *cell, // The cell
     334                          psDB *db,     // DB handle
     335                          psMetadata *concepts, // The concepts MD
     336                          const char *name, // Name of the concept
     337                          const char *comment // Comment for concept
    338338    )
    339339{
     
    341341    int value = 0;
    342342    if (item) {
    343         switch (item->type) {
    344           case PS_TYPE_S32:
    345             value = item->data.S32;
    346             break;
    347           case PS_TYPE_F32:
    348             psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F32 --- converting.\n",
    349                      name, comment);
    350             value = (int)item->data.F32;
    351             break;
    352           case PS_TYPE_F64:
    353             psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F64 --- converting.\n",
    354                      name, comment);
    355             value = (int)item->data.F64;
    356             break;
    357           default:
    358             psError(PS_ERR_IO, true, "Concept %s (%s) is not of integer type (%x) --- treating as "
    359                     "undefined.\n", name, comment, item->type);
    360         }
     343        switch (item->type) {
     344          case PS_TYPE_S32:
     345            value = item->data.S32;
     346            break;
     347          case PS_TYPE_F32:
     348            psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F32 --- converting.\n",
     349                     name, comment);
     350            value = (int)item->data.F32;
     351            break;
     352          case PS_TYPE_F64:
     353            psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F64 --- converting.\n",
     354                     name, comment);
     355            value = (int)item->data.F64;
     356            break;
     357          default:
     358            psError(PS_ERR_IO, true, "Concept %s (%s) is not of integer type (%x) --- treating as "
     359                    "undefined.\n", name, comment, item->type);
     360        }
    361361    } else {
    362         psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
     362        psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
    363363    }
    364364    psTrace(__func__, 7, "Adding %s (%s): %d\n", name, comment, value);
    365    
    366     psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_TYPE_S32, comment, value);
     365
     366    psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_TYPE_S32 | PS_META_REPLACE, comment, value);
    367367}
    368368
    369369void p_pmFPAConceptGetString(pmFPA *fpa, // The FPA
    370                              pmChip *chip, // The chip
    371                              pmCell *cell, // The cell
    372                              psDB *db,  // DB handle
    373                              psMetadata *concepts, // The concepts MD
    374                              const char *name, // Name of the concept
    375                              const char *comment // Comment for concept
     370                             pmChip *chip, // The chip
     371                             pmCell *cell, // The cell
     372                             psDB *db,  // DB handle
     373                             psMetadata *concepts, // The concepts MD
     374                             const char *name, // Name of the concept
     375                             const char *comment // Comment for concept
    376376    )
    377377{
     
    379379    psString value = NULL;
    380380    if (item) {
    381         switch (item->type) {
    382           case PS_DATA_STRING:
    383             value = psMemIncrRefCounter(item->data.V);
    384             break;
    385           case PS_DATA_F32:
    386             psStringAppend(&value, "%f", item->data.F32);
    387             break;
    388           case PS_DATA_S32:
    389             psStringAppend(&value, "%d", item->data.S32);
    390             break;
    391           default:
    392             psError(PS_ERR_IO, true, "Concept %s (%s) is not of string type (%x) --- treating as "
    393                     "undefined.\n", name, comment, item->type);
    394         }
     381        switch (item->type) {
     382          case PS_DATA_STRING:
     383            value = psMemIncrRefCounter(item->data.V);
     384            break;
     385          case PS_DATA_F32:
     386            psStringAppend(&value, "%f", item->data.F32);
     387            break;
     388          case PS_DATA_S32:
     389            psStringAppend(&value, "%d", item->data.S32);
     390            break;
     391          default:
     392            psError(PS_ERR_IO, true, "Concept %s (%s) is not of string type (%x) --- treating as "
     393                    "undefined.\n", name, comment, item->type);
     394        }
    395395    } else {
    396         psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
    397         value = psStringCopy("");
     396        psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
     397        value = psStringCopy("");
    398398    }
    399399    psTrace(__func__, 7, "Adding %s (%s): %s\n", name, comment, value);
    400400
    401     psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_DATA_STRING, comment, value);
     401    psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_DATA_STRING | PS_META_REPLACE, comment, value);
    402402    psFree(value);
    403403}
     
    409409
    410410// Ingest concepts for the FPA
    411 void pmFPAIngestConcepts(pmFPA *fpa,    // The FPA
    412                          psDB *db       // DB handle
     411void pmFPAIngestConcepts(pmFPA *fpa,    // The FPA
     412                         psDB *db       // DB handle
    413413    )
    414414{
    415415    if (! fpa->concepts) {
    416         fpa->concepts = psMetadataAlloc();
     416        fpa->concepts = psMetadataAlloc();
    417417    }
    418418
     
    436436    // FPA.RA
    437437    {
    438         double ra = NAN;                // The RA
    439         psMetadataItem *raItem = p_pmFPAConceptGet(fpa, NULL, NULL, db, "FPA.RA"); // The FPA.RA item
    440         if (raItem) {
    441             switch (raItem->type) {
    442               case PS_TYPE_F32:
    443                 ra = raItem->data.F32;
    444                 break;
    445               case PS_TYPE_F64:
    446                 ra = raItem->data.F64;
    447                 break;
    448               case PS_DATA_STRING:
    449                 // Sexagesimal format
    450                 {
    451                     int big, medium;
    452                     float small;
    453                     // XXX: Upgrade path is to allow dd:mm.mmm
    454                     if (sscanf(raItem->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
    455                         sscanf(raItem->data.V, "%d %d %f", &big, &medium, &small) != 3) {
    456                         psError(PS_ERR_IO, true, "Cannot interpret FPA.RA: %s\n", raItem->data.V);
    457                         break;
    458                     }
    459                     ra = abs(big) + (float)medium/60.0 + small/3600.0;
    460                     if (big < 0) {
    461                         ra *= -1.0;
    462                     }
    463                 }
    464                 break;
    465               default:
    466                 psError(PS_ERR_IO, true, "FPA.RA is of an unexpected type: %x\n", raItem->type);
    467             }
    468            
    469             // How to interpret the RA
    470             const psMetadata *camera = fpa->camera; // Camera configuration data
    471             bool mdok = true;           // Status of MD lookup
    472             psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
    473             if (mdok && formats) {
    474                 psString raFormat = psMetadataLookupString(&mdok, formats, "FPA.RA");
    475                 if (mdok && strlen(raFormat) > 0) {
    476                     if (strcasecmp(raFormat, "HOURS") == 0) {
    477                         ra *= M_PI / 12.0;
    478                     } else if (strcasecmp(raFormat, "DEGREES") == 0) {
    479                         ra *= M_PI / 180.0;
    480                     } else if (strcasecmp(raFormat, "RADIANS") == 0) {
    481                         // No action required
    482                     } else {
    483                         psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.RA in FORMATS (%s) --- assuming"
    484                                 " HOURS.\n");
    485                         ra *= M_PI / 12.0;
    486                     }
    487                 } else {
    488                     psError(PS_ERR_IO, false, "Unable to find FPA.RA in FORMATS --- assuming HOURS.\n");
    489                     ra *= M_PI / 12.0;
    490                 }
    491             } else {
    492                 psError(PS_ERR_IO, false, "Unable to find FORMAT metadata in camera configuration --- "
    493                         "assuming format for FPA.RA is HOURS.\n");
    494                 ra *= M_PI / 12.0;
    495             }
    496         } else {
    497             psError(PS_ERR_IO, false, "Couldn't find FPA.RA.\n");
    498         }
    499 
    500         psMetadataAdd(fpa->concepts, PS_LIST_TAIL, "FPA.RA", PS_DATA_F64,
    501                       "Right Ascension of the boresight (radians)", ra);
     438        double ra = NAN;                // The RA
     439        psMetadataItem *raItem = p_pmFPAConceptGet(fpa, NULL, NULL, db, "FPA.RA"); // The FPA.RA item
     440        if (raItem) {
     441            switch (raItem->type) {
     442              case PS_TYPE_F32:
     443                ra = raItem->data.F32;
     444                break;
     445              case PS_TYPE_F64:
     446                ra = raItem->data.F64;
     447                break;
     448              case PS_DATA_STRING:
     449                // Sexagesimal format
     450                {
     451                    int big, medium;
     452                    float small;
     453                    // XXX: Upgrade path is to allow dd:mm.mmm
     454                    if (sscanf(raItem->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
     455                        sscanf(raItem->data.V, "%d %d %f", &big, &medium, &small) != 3) {
     456                        psError(PS_ERR_IO, true, "Cannot interpret FPA.RA: %s\n", raItem->data.V);
     457                        break;
     458                    }
     459                    ra = abs(big) + (float)medium/60.0 + small/3600.0;
     460                    if (big < 0) {
     461                        ra *= -1.0;
     462                    }
     463                }
     464                break;
     465              default:
     466                psError(PS_ERR_IO, true, "FPA.RA is of an unexpected type: %x\n", raItem->type);
     467            }
     468
     469            // How to interpret the RA
     470            const psMetadata *camera = fpa->camera; // Camera configuration data
     471            bool mdok = true;           // Status of MD lookup
     472            psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
     473            if (mdok && formats) {
     474                psString raFormat = psMetadataLookupString(&mdok, formats, "FPA.RA");
     475                if (mdok && strlen(raFormat) > 0) {
     476                    if (strcasecmp(raFormat, "HOURS") == 0) {
     477                        ra *= M_PI / 12.0;
     478                    } else if (strcasecmp(raFormat, "DEGREES") == 0) {
     479                        ra *= M_PI / 180.0;
     480                    } else if (strcasecmp(raFormat, "RADIANS") == 0) {
     481                        // No action required
     482                    } else {
     483                        psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.RA in FORMATS (%s) --- assuming"
     484                                " HOURS.\n");
     485                        ra *= M_PI / 12.0;
     486                    }
     487                } else {
     488                    psError(PS_ERR_IO, false, "Unable to find FPA.RA in FORMATS --- assuming HOURS.\n");
     489                    ra *= M_PI / 12.0;
     490                }
     491            } else {
     492                psError(PS_ERR_IO, false, "Unable to find FORMAT metadata in camera configuration --- "
     493                        "assuming format for FPA.RA is HOURS.\n");
     494                ra *= M_PI / 12.0;
     495            }
     496        } else {
     497            psError(PS_ERR_IO, false, "Couldn't find FPA.RA.\n");
     498        }
     499
     500        psMetadataAdd(fpa->concepts, PS_LIST_TAIL, "FPA.RA", PS_DATA_F64 | PS_META_REPLACE,
     501                      "Right Ascension of the boresight (radians)", ra);
    502502
    503503    }
     
    505505    // FPA.DEC
    506506    {
    507         double dec = NAN;               // The DEC
    508         psMetadataItem *decItem = p_pmFPAConceptGet(fpa, NULL, NULL, db, "FPA.DEC"); // The FPA.DEC item
    509         if (decItem) {
    510             switch (decItem->type) {
    511               case PS_TYPE_F32:
    512                 dec = decItem->data.F32;
    513                 break;
    514               case PS_TYPE_F64:
    515                 dec = decItem->data.F64;
    516                 break;
    517               case PS_DATA_STRING:
    518                 // Sexagesimal format
    519                 {
    520                     int big, medium;
    521                     float small;
    522                     // XXX: Upgrade path is to allow dd:mm.mmm
    523                     if (sscanf(decItem->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
    524                         sscanf(decItem->data.V, "%d %d %f", &big, &medium, &small) != 3) {
    525                         psError(PS_ERR_IO, true, "Cannot interpret FPA.DEC: %s\n", decItem->data.V);
    526                         break;
    527                     }
    528                     dec = abs(big) + (float)medium/60.0 + small/3600.0;
    529                     if (big < 0) {
    530                         dec *= -1.0;
    531                     }
    532                 }
    533                 break;
    534               default:
    535                 psError(PS_ERR_IO, true, "FPA.DEC is of an unexpected type: %x\n", decItem->type);
    536             }
    537            
    538             // How to interpret the DEC
    539             const psMetadata *camera = fpa->camera; // Camera configuration data
    540             bool mdok = true;           // Status of MD lookup
    541             psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
    542             if (mdok && formats) {
    543                 psString decFormat = psMetadataLookupString(&mdok, formats, "FPA.DEC");
    544                 if (mdok && strlen(decFormat) > 0) {
    545                     if (strcasecmp(decFormat, "HOURS") == 0) {
    546                         dec *= M_PI / 12.0;
    547                     } else if (strcasecmp(decFormat, "DEGREES") == 0) {
    548                         dec *= M_PI / 180.0;
    549                     } else if (strcasecmp(decFormat, "RADIANS") == 0) {
    550                         // No action required
    551                     } else {
    552                         psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.DEC in FORMATS (%s) --- "
    553                                 "assuming DEGREES.\n");
    554                         dec *= M_PI / 180.0;
    555                     }
    556                 } else {
    557                     psError(PS_ERR_IO, false, "Unable to find FPA.DEC in FORMATS --- assuming DEGREES.\n");
    558                     dec *= M_PI / 180.0;
    559                 }
    560             } else {
    561                 psError(PS_ERR_IO, false, "Unable to find FORMATS metadata in camera configuration --- "
    562                         "assuming format for FPA.DEC is DEGREES.\n");
    563                 dec *= M_PI / 180.0;
    564             }
    565         } else {
    566             psError(PS_ERR_IO, false, "Couldn't find FPA.DEC.\n");
    567         }
    568 
    569         psMetadataAdd(fpa->concepts, PS_LIST_TAIL, "FPA.DEC", PS_DATA_F64,
    570                       "Declination of the boresight (radians)", dec);
     507        double dec = NAN;               // The DEC
     508        psMetadataItem *decItem = p_pmFPAConceptGet(fpa, NULL, NULL, db, "FPA.DEC"); // The FPA.DEC item
     509        if (decItem) {
     510            switch (decItem->type) {
     511              case PS_TYPE_F32:
     512                dec = decItem->data.F32;
     513                break;
     514              case PS_TYPE_F64:
     515                dec = decItem->data.F64;
     516                break;
     517              case PS_DATA_STRING:
     518                // Sexagesimal format
     519                {
     520                    int big, medium;
     521                    float small;
     522                    // XXX: Upgrade path is to allow dd:mm.mmm
     523                    if (sscanf(decItem->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
     524                        sscanf(decItem->data.V, "%d %d %f", &big, &medium, &small) != 3) {
     525                        psError(PS_ERR_IO, true, "Cannot interpret FPA.DEC: %s\n", decItem->data.V);
     526                        break;
     527                    }
     528                    dec = abs(big) + (float)medium/60.0 + small/3600.0;
     529                    if (big < 0) {
     530                        dec *= -1.0;
     531                    }
     532                }
     533                break;
     534              default:
     535                psError(PS_ERR_IO, true, "FPA.DEC is of an unexpected type: %x\n", decItem->type);
     536            }
     537
     538            // How to interpret the DEC
     539            const psMetadata *camera = fpa->camera; // Camera configuration data
     540            bool mdok = true;           // Status of MD lookup
     541            psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
     542            if (mdok && formats) {
     543                psString decFormat = psMetadataLookupString(&mdok, formats, "FPA.DEC");
     544                if (mdok && strlen(decFormat) > 0) {
     545                    if (strcasecmp(decFormat, "HOURS") == 0) {
     546                        dec *= M_PI / 12.0;
     547                    } else if (strcasecmp(decFormat, "DEGREES") == 0) {
     548                        dec *= M_PI / 180.0;
     549                    } else if (strcasecmp(decFormat, "RADIANS") == 0) {
     550                        // No action required
     551                    } else {
     552                        psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.DEC in FORMATS (%s) --- "
     553                                "assuming DEGREES.\n");
     554                        dec *= M_PI / 180.0;
     555                    }
     556                } else {
     557                    psError(PS_ERR_IO, false, "Unable to find FPA.DEC in FORMATS --- assuming DEGREES.\n");
     558                    dec *= M_PI / 180.0;
     559                }
     560            } else {
     561                psError(PS_ERR_IO, false, "Unable to find FORMATS metadata in camera configuration --- "
     562                        "assuming format for FPA.DEC is DEGREES.\n");
     563                dec *= M_PI / 180.0;
     564            }
     565        } else {
     566            psError(PS_ERR_IO, false, "Couldn't find FPA.DEC.\n");
     567        }
     568
     569        psMetadataAdd(fpa->concepts, PS_LIST_TAIL, "FPA.DEC", PS_DATA_F64 | PS_META_REPLACE,
     570                      "Declination of the boresight (radians)", dec);
    571571
    572572    }
     
    577577
    578578// Ingest concepts for the chip
    579 bool pmChipIngestConcepts(pmChip *chip, // The chip
    580                           psDB *db      // DB handle
    581     )
    582 {
    583     pmFPA *fpa = chip->parent;          // The parent FPA
     579bool pmChipIngestConcepts(pmChip *chip, // The chip
     580                          psDB *db      // DB handle
     581    )
     582{
     583    pmFPA *fpa = chip->parent;          // The parent FPA
    584584
    585585    if (! chip->concepts) {
    586         chip->concepts = psMetadataAlloc();
     586        chip->concepts = psMetadataAlloc();
    587587    }
    588588
     
    591591    // Pau.
    592592}
     593
     594
     595// Add corrective to a position --- in case the user wants FORTRAN indexing (like the FITS standard...)
     596static bool correctPosition(pmFPA *fpa, // FPA, contains the camera configuration
     597                            pmCell *cell, // Cell containing the concept to correct
     598                            const char *conceptName // Name of concept to correct
     599    )
     600{
     601    bool mdok = false;              // Result of MD lookup
     602    psMetadata *formats = psMetadataLookupMD(&mdok, fpa->camera, "FORMATS");
     603    if (mdok && formats) {
     604        psString format = psMetadataLookupString(&mdok, formats, conceptName);
     605        if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
     606            psMetadataItem *valueItem = psMetadataLookup(cell->concepts, conceptName);
     607            valueItem->data.S32 -= 1;
     608        }
     609    }
     610    return true;
     611}
     612
     613bool p_pmCellIngestConcept(pmFPA *fpa,  // The FPA
     614                           pmChip *chip, // The chip
     615                           pmCell *cell, // The cell
     616                           psDB *db,    // DB handle
     617                           const char *concept // Name of the concept
     618    )
     619{
     620    if (strcmp(concept, "CELL.GAIN") == 0) {
     621        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.GAIN", "CCD gain (e/count)");
     622    } else if (strcmp(concept, "CELL.READNOISE") == 0) {
     623        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.READNOISE", "CCD read noise (e)");
     624    } else if (strcmp(concept, "CELL.SATURATION") == 0) {
     625        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.SATURATION",
     626                             "Saturation level (ADU)");
     627    } else if (strcmp(concept, "CELL.BAD") == 0) {
     628        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.BAD", "Bad level (ADU)");
     629    } else if (strcmp(concept, "CELL.XPARITY") == 0) {
     630        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.XPARITY",
     631                             "Orientation in x compared to the rest of the FPA");
     632    } else if (strcmp(concept, "CELL.YPARITY") == 0) {
     633        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.YPARITY",
     634                             "Orientation in y compared to the rest of the FPA");
     635    } else if (strcmp(concept, "CELL.READDIR") == 0) {
     636        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.READDIR",
     637                             "Read direction: 1=row, 2=col");
     638    } else if (strcmp(concept, "CELL.EXPOSURE") == 0) { // used to be READOUT.EXPOSURE
     639        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.EXPOSURE", "Exposure time (sec)");
     640    } else if (strcmp(concept, "CELL.DARKTIME") == 0) { // used to be READOUT.DARKTIME
     641        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.DARKTIME",
     642                             "Time since CCD flush (sec)");
     643        // These take some extra work
     644    } else if (strcmp(concept, "CELL.TRIMSEC") == 0) {
     645        psRegion *trimsec = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed by value)
     646
     647        psMetadataItem *secItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TRIMSEC");
     648        if (! secItem) {
     649            psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.\n");
     650            *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
     651        } else if (secItem->type != PS_DATA_STRING) {
     652            psError(PS_ERR_IO, true, "CELL.TRIMSEC is not of type STR (%x)\n", secItem->type);
     653            *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
     654        } else {
     655            psString section = secItem->data.V; // The section string
     656
     657            psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TRIMSEC.SOURCE");
     658            if (! sourceItem) {
     659                psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.SOURCE.\n");
     660                *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
     661            } else if (sourceItem->type != PS_DATA_STRING) {
     662                psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
     663                *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
     664            } else {
     665                psString source = sourceItem->data.V; // The source string
     666
     667                if (strcasecmp(source, "VALUE") == 0) {
     668                    *trimsec = psRegionFromString(section);
     669                } else if (strcasecmp(source, "HEADER") == 0) {
     670                    psMetadata *header = NULL; // The FITS header
     671                    if (cell->hdu) {
     672                        header = cell->hdu->header;
     673                    } else if (chip->hdu) {
     674                        header = chip->hdu->header;
     675                    } else if (fpa->hdu) {
     676                        header = fpa->hdu->header;
     677                    }
     678                    if (! header) {
     679                        psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
     680                        *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
     681                    } else {
     682                        bool mdok = true;               // Status of MD lookup
     683                        psString secValue = psMetadataLookupString(&mdok, header, section);
     684                        if (! mdok || ! secValue) {
     685                            psError(PS_ERR_IO, false, "Unable to locate header %s\n", section);
     686                            *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
     687                        } else {
     688                            *trimsec = psRegionFromString(secValue);
     689                        }
     690                    }
     691                } else {
     692                    psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE (%s) is not HEADER or VALUE --- trying "
     693                            "VALUE.\n", source);
     694                    *trimsec = psRegionFromString(section);
     695                } // Value of CELL.TRIMSEC.SOURCE
     696            } // Looking up CELL.TRIMSEC.SOURCE
     697        } // Looking up CELL.TRIMSEC
     698
     699        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.TRIMSEC", PS_DATA_UNKNOWN | PS_META_REPLACE,
     700                      "Trim section", trimsec);
     701        psFree(trimsec);
     702
     703    } else if (strcmp(concept, "CELL.BIASSEC") == 0) {
     704        psList *biassecs = psListAlloc(NULL); // List of bias sections
     705
     706        psMetadataItem *secItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.BIASSEC");
     707        if (! secItem) {
     708            psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.\n");
     709        } else if (secItem->type != PS_DATA_STRING) {
     710            psError(PS_ERR_IO, true, "CELL.BIASSEC is not of type STR (%x)\n", secItem->type);
     711        } else {
     712            psString sections = secItem->data.V; // The section string
     713
     714            psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.BIASSEC.SOURCE");
     715            if (! sourceItem) {
     716                psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.SOURCE.\n");
     717            } else if (sourceItem->type != PS_DATA_STRING) {
     718                psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
     719            } else {
     720                psString source = sourceItem->data.V; // The source string
     721
     722                psList *secList = papSplit(sections, " ;"); // List of sections
     723                psListIterator *secIter = psListIteratorAlloc(secList, PS_LIST_HEAD, false); // Iterator over
     724                                                                                             // sections
     725                psString aSection = NULL; // A section from the list
     726                while (aSection = psListGetAndIncrement(secIter)) {
     727                    psRegion *region = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed
     728                                                                  // by value)
     729
     730                    if (strcasecmp(source, "VALUE") == 0) {
     731                        *region = psRegionFromString(aSection);
     732                    } else if (strcasecmp(source, "HEADER") == 0) {
     733                        psMetadata *header = NULL; // The FITS header
     734                        if (cell->hdu) {
     735                            header = cell->hdu->header;
     736                        } else if (chip->hdu) {
     737                            header = chip->hdu->header;
     738                        } else if (fpa->hdu) {
     739                            header = fpa->hdu->header;
     740                        }
     741                        if (! header) {
     742                            psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
     743                            *region = psRegionSet(0.0,0.0,0.0,0.0);
     744                        } else {
     745                            bool mdok = true;           // Status of MD lookup
     746                            psString secValue = psMetadataLookupString(&mdok, header, aSection);
     747                            if (! mdok || ! secValue) {
     748                                psError(PS_ERR_IO, false, "Unable to locate header %s\n", aSection);
     749                                *region = psRegionSet(0.0,0.0,0.0,0.0);
     750                            } else {
     751                                *region = psRegionFromString(secValue);
     752                            }
     753                        }
     754                    } else {
     755                        psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE (%s) is not HEADER or VALUE --- trying "
     756                                "VALUE.\n", source);
     757                        *region = psRegionFromString(aSection);
     758                    } // Value of CELL.BIASSEC.SOURCE
     759
     760                    psListAdd(biassecs, PS_LIST_TAIL, region);
     761                    psFree(region);
     762                } // Iterating over multiple sections
     763                psFree(secIter);
     764                psFree(secList);
     765            } // Looking up CELL.BIASSEC.SOURCE
     766        } // Looking up CELL.BIASSEC
     767
     768        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.BIASSEC", PS_DATA_LIST | PS_META_REPLACE,
     769                      "Bias sections", biassecs);
     770        psFree(biassecs);
     771
     772    } else if (strcmp(concept, "CELL.XBIN") == 0) {
     773        int xBin = 1;                   // Binning factor in x
     774        psMetadataItem *binItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.XBIN");
     775        if (! binItem) {
     776            psError(PS_ERR_IO, false, "Couldn't find CELL.XBIN.\n");
     777        } else if (binItem->type == PS_DATA_STRING) {
     778            psString binString = binItem->data.V; // The string containing the binning
     779            if (sscanf(binString, "%d %*d", &xBin) != 1 &&
     780                sscanf(binString, "%d,%*d", &xBin) != 1) {
     781                psError(PS_ERR_IO, true, "Unable to read string to get x binning: %s\n", binString);
     782            }
     783        } else if (binItem->type == PS_TYPE_S32) {
     784            xBin = binItem->data.S32;
     785        } else {
     786            psError(PS_ERR_IO, true, "Note sure how to interpret CELL.XBIN of type %x --- assuming 1.\n",
     787                    binItem->type);
     788        }
     789
     790        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.XBIN", PS_TYPE_S32 | PS_META_REPLACE,
     791                      "Binning in x", xBin);
     792
     793    } else if (strcmp(concept, "CELL.YBIN") == 0) {
     794        int yBin = 1;                   // Binning factor in y
     795        psMetadataItem *binItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.YBIN");
     796        if (! binItem) {
     797            psError(PS_ERR_IO, false, "Couldn't find CELL.YBIN.\n");
     798        } else if (binItem->type == PS_DATA_STRING) {
     799            psString binString = binItem->data.V; // The string containing the binning
     800            if (sscanf(binString, "%*d %d", &yBin) != 1 &&
     801                sscanf(binString, "%*d,%d", &yBin) != 1) {
     802                psError(PS_ERR_IO, true, "Unable to read string to get y binning: %s\n", binString);
     803            }
     804        } else if (binItem->type == PS_TYPE_S32) {
     805            yBin = binItem->data.S32;
     806        } else {
     807            psError(PS_ERR_IO, true, "Note sure how to interpret CELL.YBIN of type %x --- assuming 1.\n",
     808                    binItem->type);
     809        }
     810
     811        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.YBIN", PS_TYPE_S32 | PS_META_REPLACE,
     812                      "Binning in y", yBin);
     813
     814    } else if (strcmp(concept, "CELL.TIME") == 0 || strcmp(concept, "CELL.TIMESYS") == 0) {
     815        // Do CELL.TIME and CELL.TIMESYS together
     816        psTime *time = NULL;            // The time
     817        psTimeType timeSys = PS_TIME_UTC; // The time system
     818
     819        // CELL.TIMESYS
     820        psMetadataItem *sysItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TIMESYS");
     821        if (! sysItem) {
     822            psError(PS_ERR_IO, true, "Couldn't find CELL.TIMESYS --- assuming UTC.\n");
     823        } else if (sysItem->type != PS_DATA_STRING) {
     824            psError(PS_ERR_IO, true, "CELL.TIMESYS isn't of type STRING --- assuming UTC.\n");
     825        } else {
     826            psString sys = sysItem->data.V; // The time system string
     827            if (strcasecmp(sys, "TAI") == 0) {
     828                timeSys = PS_TIME_TAI;
     829            } else if (strcasecmp(sys, "UTC") == 0) {
     830                timeSys = PS_TIME_UTC;
     831            } else if (strcasecmp(sys, "UT1") == 0) {
     832                timeSys = PS_TIME_UT1;
     833            } else if (strcasecmp(sys, "TT") == 0) {
     834                timeSys = PS_TIME_TT;
     835            } else {
     836                psError(PS_ERR_IO, true, "Can't interpret CELL.TIMESYS --- assuming UTC.\n");
     837            }
     838        }
     839        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.TIMESYS", PS_TYPE_S32 | PS_META_REPLACE,
     840                      "Time system", timeSys);
     841
     842        psMetadataItem *timeItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TIME");
     843        if (! timeItem) {
     844            psError(PS_ERR_IO, false, "Couldn't find CELL.TIME.\n");
     845        } else {
     846            // Get format
     847            const psMetadata *camera = fpa->camera; // The camera configuration data
     848            bool mdok = true;           // Status of MD lookup
     849            psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
     850            if (mdok && formats) {
     851                psString timeFormat = psMetadataLookupString(&mdok, formats, "CELL.TIME");
     852                if (mdok && strlen(timeFormat) > 0) {
     853                    switch (timeItem->type) {
     854                      case PS_DATA_STRING:
     855                        {
     856                            psString timeString = timeItem->data.V;     // String with the time
     857                            if (strcasecmp(timeFormat, "ISO") == 0) {
     858                                // timeString contains an ISO time
     859                                time = psTimeFromISO(timeString, timeSys);
     860                            } else if (strstr(timeFormat, "SEPARATE")) {
     861                                // timeString contains headers for the date and time
     862                                psMetadata *header = NULL; // The FITS header
     863                                if (cell->hdu) {
     864                                    header = cell->hdu->header;
     865                                } else if (chip->hdu) {
     866                                    header = chip->hdu->header;
     867                                } else if (fpa->hdu) {
     868                                    header = fpa->hdu->header;
     869                                }
     870                                if (! header) {
     871                                    psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
     872                                } else {
     873                                    // Get the headers
     874                                    char *stuff1 = strpbrk(timeString, " ,;");
     875                                    psString dateName = psStringNCopy(timeString,
     876                                                                      strlen(timeString) - strlen(stuff1));
     877                                    char *stuff2 = strpbrk(stuff1, "abcdefghijklmnopqrstuvwxyz"
     878                                                           "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
     879                                    psString timeName = psStringCopy(stuff2);
     880
     881                                    bool mdok = true; // Status of MD lookup
     882                                    psString dateString = psMetadataLookupString(&mdok, header, dateName);
     883                                    psFree(dateName);
     884                                    int day = 0, month = 0, year = 0;
     885                                    if (sscanf(dateString, "%d-%d-%d", &day, &month, &year) != 3 &&
     886                                        sscanf(dateString, "%d/%d/%d", &day, &month, &year) != 3) {
     887                                        psError(PS_ERR_IO, true, "Unable to read date: %s\n", dateString);
     888                                    } else {
     889                                        if (strstr(timeFormat, "BACKWARDS")) {
     890                                            int temp = day;
     891                                            day = year;
     892                                            year = temp;
     893                                        }
     894                                        if (strstr(timeFormat, "PRE2000") || year < 2000) {
     895                                            year += 2000;
     896                                        }
     897
     898                                        psMetadataItem *timeItem = psMetadataLookup(header, timeName);
     899                                        if (! timeItem) {
     900                                            psError(PS_ERR_IO, false, "Unable to find time header: %s\n",
     901                                                    timeName);
     902                                        } else if (timeItem->type == PS_DATA_STRING) {
     903                                            // Time is a string, in the usual way:
     904                                            psStringAppend(&dateString, "T%s", timeItem->data.V);
     905                                        } else {
     906                                            // Assume that time is specified in Second of Day
     907                                            double seconds = NAN;
     908                                            switch (timeItem->type) {
     909                                              case PS_TYPE_S32:
     910                                                seconds = timeItem->data.S32;
     911                                                break;
     912                                              case PS_TYPE_F32:
     913                                                seconds = timeItem->data.F32;
     914                                                break;
     915                                              case PS_TYPE_F64:
     916                                                seconds = timeItem->data.F64;
     917                                                break;
     918                                              default:
     919                                                psError(PS_ERR_IO, true, "Time header (%s) is not of an "
     920                                                        "expected type: %x\n", timeName, timeItem->type);
     921                                            }
     922                                            // Now print to timeString as "hh:mm:ss.ss"
     923                                            int hours = seconds / 3600;
     924                                            seconds -= (double)hours * 3600.0;
     925                                            int minutes = seconds / 60;
     926                                            seconds -= (double)minutes * 60.0;
     927                                            psStringAppend(&dateString, "T%02d:%02d:%02f", hours, minutes,
     928                                                           seconds);
     929                                        }
     930                                        time = psTimeFromISO(dateString, timeSys);
     931                                    } // Reading date and time
     932                                    psFree(timeName);
     933                                } // Reading headers
     934                            } else {
     935                                psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%s) --- trying "
     936                                        "ISO\n", timeString);
     937                                time = psTimeFromISO(timeString, timeSys);
     938                            } // Interpreting the time string
     939                        }
     940                        break;
     941                      case PS_TYPE_F32:
     942                        {
     943                            double timeValue = (double)timeItem->data.F32;
     944                            if (strcasecmp(timeFormat, "JD") == 0) {
     945                                time = psTimeFromJD(timeValue);
     946                            } else if (strcasecmp(timeFormat, "MJD") == 0) {
     947                                time = psTimeFromMJD(timeValue);
     948                            } else {
     949                                psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
     950                                        "JD\n", timeValue);
     951                                time = psTimeFromJD(timeValue);
     952                            }
     953                        }
     954                        break;
     955                      case PS_TYPE_F64:
     956                        {
     957                            double timeValue = (double)timeItem->data.F64;
     958                            if (strcasecmp(timeFormat, "JD") == 0) {
     959                                time = psTimeFromJD(timeValue);
     960                            } else if (strcasecmp(timeFormat, "MJD") == 0) {
     961                                time = psTimeFromMJD(timeValue);
     962                            } else {
     963                                psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
     964                                        "JD\n", timeValue);
     965                                time = psTimeFromJD(timeValue);
     966                            }
     967                        }
     968                        break;
     969                      default:
     970                        psError(PS_ERR_IO, true, "Unable to parse CELL.TIME.\n");
     971                    }
     972                } else {
     973                    psError(PS_ERR_IO, false, "Unable to find CELL.TIME in FORMATS.\n");
     974                } // Getting the format
     975            } else {
     976                psError(PS_ERR_IO, false, "Unable to find FORMATS in camera configuration.\n");
     977            } // Getting the formats
     978        } // Getting CELL.TIME
     979
     980        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.TIME", PS_DATA_TIME | PS_META_REPLACE,
     981                      "Time of exposure", time);
     982        psFree(time);
     983
     984        // These are new and experimental concepts: CELL.X0 and CELL.Y0
     985    } else if (strcmp(concept, "CELL.X0") == 0) {
     986        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.X0", "Position of (0,0) on the chip");
     987        correctPosition(fpa, cell, "CELL.X0");
     988    } else if (strcmp(concept, "CELL.Y0") == 0) {
     989        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.Y0", "Position of (0,0) on the chip");
     990        correctPosition(fpa, cell, "CELL.Y0");
     991    }
     992
     993    return true;
     994}
     995
    593996
    594997
    595998// Ingest concepts for the cell
    596999bool pmCellIngestConcepts(pmCell *cell, // The cell
    597                           psDB *db      // DB handle
    598     )
    599 {
    600     pmChip *chip = cell->parent;        // The parent chip
    601     pmFPA *fpa = chip->parent;          // The parent FPA
     1000                          psDB *db      // DB handle
     1001    )
     1002{
     1003    pmChip *chip = cell->parent;        // The parent chip
     1004    pmFPA *fpa = chip->parent;          // The parent FPA
    6021005
    6031006    if (! cell->concepts) {
    604         cell->concepts = psMetadataAlloc();
     1007        cell->concepts = psMetadataAlloc();
    6051008    }
    6061009
    6071010    // CELL.NAME --- added by pmFPAConstruct
    6081011
    609     // CELL.GAIN
    610     p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.GAIN", "CCD gain (e/count)");
    611 
    612     // CELL.READNOISE
    613     p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.READNOISE", "CCD read noise (e)");
    614 
    615     // CELL.SATURATION
    616     p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.SATURATION", "Saturation level (ADU)");
    617 
    618     // CELL.BAD
    619     p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.BAD", "Bad level (ADU)");
    620 
    621     // CELL.XPARITY
    622     p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.XPARITY", "Orientation in x compared to the "
    623                   "rest of the FPA");
    624 
    625     // CELL.YPARITY
    626     p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.YPARITY", "Orientation in y compared to the "
    627                   "rest of the FPA");
    628 
    629     // CELL.READDIR
    630     p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.READDIR", "Read direction: 1=row, 2=col");
     1012    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.GAIN");
     1013    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.READNOISE");
     1014    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.SATURATION");
     1015    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.BAD");
     1016    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.XPARITY");
     1017    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.YPARITY");
     1018    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.READDIR");
    6311019
    6321020    // These used to be pmReadoutGetExposure and pmReadoutGetDarkTime, but that doesn't really make sense at
     
    6341022    // REALLY derived?  They're not in the FITS headers, because a readout is a plane in a 3D image.  We'll
    6351023    // have to dream up some additional suffix to specify these, but for now....
    636 
    637     // CELL.EXPOSURE (used to be READOUT.EXPOSURE)
    638     p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.EXPOSURE", "Exposure time (sec)");
    639 
    640     // CELL.DARKTIME (used to be READOUT.DARKTIME)
    641     p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.DARKTIME", "Time since CCD flush (sec)");
    642 
    643     // These take some extra work
    644 
    645     // CELL.TRIMSEC
    646     {
    647         psRegion *trimsec = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed by value)
    648 
    649         psMetadataItem *secItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TRIMSEC");
    650         if (! secItem) {
    651             psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.\n");
    652             *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
    653         } else if (secItem->type != PS_DATA_STRING) {
    654             psError(PS_ERR_IO, true, "CELL.TRIMSEC is not of type STR (%x)\n", secItem->type);
    655             *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
    656         } else {
    657             psString section = secItem->data.V; // The section string
    658 
    659             psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TRIMSEC.SOURCE");
    660             if (! sourceItem) {
    661                 psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.SOURCE.\n");
    662                 *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
    663             } else if (sourceItem->type != PS_DATA_STRING) {
    664                 psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
    665                 *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
    666             } else {
    667                 psString source = sourceItem->data.V; // The source string
    668            
    669                 if (strcasecmp(source, "VALUE") == 0) {
    670                     *trimsec = psRegionFromString(section);
    671                 } else if (strcasecmp(source, "HEADER") == 0) {
    672                     psMetadata *header = NULL; // The FITS header
    673                     if (cell->hdu) {
    674                         header = cell->hdu->header;
    675                     } else if (chip->hdu) {
    676                         header = chip->hdu->header;
    677                     } else if (fpa->hdu) {
    678                             header = fpa->hdu->header;
    679                     }
    680                     if (! header) {
    681                         psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
    682                         *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
    683                     } else {
    684                         bool mdok = true;               // Status of MD lookup
    685                         psString secValue = psMetadataLookupString(&mdok, header, section);
    686                         if (! mdok || ! secValue) {
    687                             psError(PS_ERR_IO, false, "Unable to locate header %s\n", section);
    688                             *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
    689                         } else {
    690                             *trimsec = psRegionFromString(secValue);
    691                         }
    692                     }
    693                 } else {
    694                     psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE (%s) is not HEADER or VALUE --- trying "
    695                             "VALUE.\n", source);
    696                     *trimsec = psRegionFromString(section);
    697                 } // Value of CELL.TRIMSEC.SOURCE
    698             } // Looking up CELL.TRIMSEC.SOURCE
    699         } // Looking up CELL.TRIMSEC
    700 
    701         psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.TRIMSEC", PS_DATA_UNKNOWN,
    702                       "Trim section", trimsec);
    703         psFree(trimsec);
    704     }
    705 
    706     // CELL.BIASSEC
    707     {
    708         psList *biassecs = psListAlloc(NULL); // List of bias sections
    709 
    710         psMetadataItem *secItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.BIASSEC");
    711         if (! secItem) {
    712             psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.\n");
    713         } else if (secItem->type != PS_DATA_STRING) {
    714             psError(PS_ERR_IO, true, "CELL.BIASSEC is not of type STR (%x)\n", secItem->type);
    715         } else {
    716             psString sections = secItem->data.V; // The section string
    717 
    718             psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.BIASSEC.SOURCE");
    719             if (! sourceItem) {
    720                 psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.SOURCE.\n");
    721             } else if (sourceItem->type != PS_DATA_STRING) {
    722                 psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
    723             } else {
    724                 psString source = sourceItem->data.V; // The source string
    725 
    726                 psList *secList = papSplit(sections, " ;"); // List of sections
    727                 psListIterator *secIter = psListIteratorAlloc(secList, PS_LIST_HEAD, false); // Iterator over
    728                                                                                              // sections
    729                 psString aSection = NULL; // A section from the list
    730                 while (aSection = psListGetAndIncrement(secIter)) {
    731                     psRegion *region = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed
    732                                                                   // by value)
    733 
    734                     if (strcasecmp(source, "VALUE") == 0) {
    735                         *region = psRegionFromString(aSection);
    736                     } else if (strcasecmp(source, "HEADER") == 0) {
    737                         psMetadata *header = NULL; // The FITS header
    738                         if (cell->hdu) {
    739                             header = cell->hdu->header;
    740                         } else if (chip->hdu) {
    741                             header = chip->hdu->header;
    742                         } else if (fpa->hdu) {
    743                             header = fpa->hdu->header;
    744                         }
    745                         if (! header) {
    746                             psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
    747                             *region = psRegionSet(0.0,0.0,0.0,0.0);
    748                         } else {
    749                             bool mdok = true;           // Status of MD lookup
    750                             psString secValue = psMetadataLookupString(&mdok, header, aSection);
    751                             if (! mdok || ! secValue) {
    752                                 psError(PS_ERR_IO, false, "Unable to locate header %s\n", aSection);
    753                                 *region = psRegionSet(0.0,0.0,0.0,0.0);
    754                             } else {
    755                                 *region = psRegionFromString(secValue);
    756                             }
    757                         }
    758                     } else {
    759                         psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE (%s) is not HEADER or VALUE --- trying "
    760                                 "VALUE.\n", source);
    761                         *region = psRegionFromString(aSection);
    762                     } // Value of CELL.BIASSEC.SOURCE
    763                    
    764                     psListAdd(biassecs, PS_LIST_TAIL, region);
    765                     psFree(region);
    766                 } // Iterating over multiple sections
    767                 psFree(secIter);
    768                 psFree(secList);
    769             } // Looking up CELL.BIASSEC.SOURCE
    770         } // Looking up CELL.BIASSEC
    771 
    772         psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.BIASSEC", PS_DATA_LIST, "Bias sections", biassecs);
    773         psFree(biassecs);
    774     }
    775 
    776     // CELL.XBIN
    777     {
    778         int xBin = 1;                   // Binning factor in x
    779         psMetadataItem *binItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.XBIN");
    780         if (! binItem) {
    781             psError(PS_ERR_IO, false, "Couldn't find CELL.XBIN.\n");
    782         } else if (binItem->type == PS_DATA_STRING) {
    783             psString binString = binItem->data.V; // The string containing the binning
    784             if (sscanf(binString, "%d %*d", &xBin) != 1 &&
    785                 sscanf(binString, "%d,%*d", &xBin) != 1) {
    786                 psError(PS_ERR_IO, true, "Unable to read string to get x binning: %s\n", binString);
    787             }
    788         } else if (binItem->type == PS_TYPE_S32) {
    789             xBin = binItem->data.S32;
    790         } else {
    791             psError(PS_ERR_IO, true, "Note sure how to interpret CELL.XBIN of type %x --- assuming 1.\n",
    792                     binItem->type);
    793         }
    794 
    795         psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.XBIN", PS_TYPE_S32, "Binning in x", xBin);
    796     }
    797 
    798     // CELL.XBIN
    799     {
    800         int yBin = 1;                   // Binning factor in y
    801         psMetadataItem *binItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.YBIN");
    802         if (! binItem) {
    803             psError(PS_ERR_IO, false, "Couldn't find CELL.YBIN.\n");
    804         } else if (binItem->type == PS_DATA_STRING) {
    805             psString binString = binItem->data.V; // The string containing the binning
    806             if (sscanf(binString, "%*d %d", &yBin) != 1 &&
    807                 sscanf(binString, "%*d,%d", &yBin) != 1) {
    808                 psError(PS_ERR_IO, true, "Unable to read string to get y binning: %s\n", binString);
    809             }
    810         } else if (binItem->type == PS_TYPE_S32) {
    811             yBin = binItem->data.S32;
    812         } else {
    813             psError(PS_ERR_IO, true, "Note sure how to interpret CELL.YBIN of type %x --- assuming 1.\n",
    814                     binItem->type);
    815         }
    816 
    817         psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.YBIN", PS_TYPE_S32, "Binning in y", yBin);
    818     }
    819 
    820     // CELL.TIME and CELL.TIMESYS
    821     {
    822         psTime *time = NULL;            // The time
    823         psTimeType timeSys = PS_TIME_UTC; // The time system
    824 
    825         // CELL.TIMESYS
    826         psMetadataItem *sysItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TIMESYS");
    827         if (! sysItem) {
    828             psError(PS_ERR_IO, true, "Couldn't find CELL.TIMESYS --- assuming UTC.\n");
    829         } else if (sysItem->type != PS_DATA_STRING) {
    830             psError(PS_ERR_IO, true, "CELL.TIMESYS isn't of type STRING --- assuming UTC.\n");
    831         } else {
    832             psString sys = sysItem->data.V; // The time system string
    833             if (strcasecmp(sys, "TAI") == 0) {
    834                 timeSys = PS_TIME_TAI;
    835             } else if (strcasecmp(sys, "UTC") == 0) {
    836                 timeSys = PS_TIME_UTC;
    837             } else if (strcasecmp(sys, "UT1") == 0) {
    838                 timeSys = PS_TIME_UT1;
    839             } else if (strcasecmp(sys, "TT") == 0) {
    840                 timeSys = PS_TIME_TT;
    841             } else {
    842                 psError(PS_ERR_IO, true, "Can't interpret CELL.TIMESYS --- assuming UTC.\n");
    843             }
    844         }
    845         psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.TIMESYS", PS_TYPE_S32, "Time system", timeSys);
    846 
    847         psMetadataItem *timeItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TIME");
    848         if (! timeItem) {
    849             psError(PS_ERR_IO, false, "Couldn't find CELL.TIME.\n");
    850         } else {
    851             // Get format
    852             const psMetadata *camera = fpa->camera; // The camera configuration data
    853             bool mdok = true;           // Status of MD lookup
    854             psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
    855             if (mdok && formats) {
    856                 psString timeFormat = psMetadataLookupString(&mdok, formats, "CELL.TIME");
    857                 if (mdok && strlen(timeFormat) > 0) {
    858                     switch (timeItem->type) {
    859                       case PS_DATA_STRING:
    860                         {
    861                             psString timeString = timeItem->data.V;     // String with the time
    862                             if (strcasecmp(timeFormat, "ISO") == 0) {
    863                                 // timeString contains an ISO time
    864                                 time = psTimeFromISO(timeString, timeSys);
    865                             } else if (strstr(timeFormat, "SEPARATE")) {
    866                                 // timeString contains headers for the date and time
    867                                 psMetadata *header = NULL; // The FITS header
    868                                 if (cell->hdu) {
    869                                     header = cell->hdu->header;
    870                                 } else if (chip->hdu) {
    871                                     header = chip->hdu->header;
    872                                 } else if (fpa->hdu) {
    873                                     header = fpa->hdu->header;
    874                                 }
    875                                 if (! header) {
    876                                     psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
    877                                 } else {
    878                                     // Get the headers
    879                                     char *stuff1 = strpbrk(timeString, " ,;");
    880                                     psString dateName = psStringNCopy(timeString,
    881                                                                       strlen(timeString) - strlen(stuff1));
    882                                     char *stuff2 = strpbrk(stuff1,
    883                                                            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
    884                                     psString timeName = psStringCopy(stuff2);
    885                                    
    886                                     bool mdok = true; // Status of MD lookup
    887                                     psString dateString = psMetadataLookupString(&mdok, header, dateName);
    888                                     psFree(dateName);
    889                                     int day = 0, month = 0, year = 0;
    890                                     if (sscanf(dateString, "%d-%d-%d", &day, &month, &year) != 3 &&
    891                                         sscanf(dateString, "%d/%d/%d", &day, &month, &year) != 3) {
    892                                         psError(PS_ERR_IO, true, "Unable to read date: %s\n", dateString);
    893                                     } else {
    894                                         if (strstr(timeFormat, "BACKWARDS")) {
    895                                             int temp = day;
    896                                             day = year;
    897                                             year = temp;
    898                                         }
    899                                         if (strstr(timeFormat, "PRE2000") || year < 2000) {
    900                                             year += 2000;
    901                                         }
    902                                        
    903                                         psMetadataItem *timeItem = psMetadataLookup(header, timeName);
    904                                         if (! timeItem) {
    905                                             psError(PS_ERR_IO, false, "Unable to find time header: %s\n",
    906                                                     timeName);
    907                                         } else if (timeItem->type == PS_DATA_STRING) {
    908                                             // Time is a string, in the usual way:
    909                                             psStringAppend(&dateString, "T%s", timeItem->data.V);
    910                                         } else {
    911                                             // Assume that time is specified in Second of Day
    912                                             double seconds = NAN;
    913                                             switch (timeItem->type) {
    914                                               case PS_TYPE_S32:
    915                                                 seconds = timeItem->data.S32;
    916                                                 break;
    917                                               case PS_TYPE_F32:
    918                                                 seconds = timeItem->data.F32;
    919                                                 break;
    920                                               case PS_TYPE_F64:
    921                                                 seconds = timeItem->data.F64;
    922                                                 break;
    923                                               default:
    924                                                 psError(PS_ERR_IO, true, "Time header (%s) is not of an "
    925                                                         "expected type: %x\n", timeName, timeItem->type);
    926                                             }
    927                                             // Now print to timeString as "hh:mm:ss.ss"
    928                                             int hours = seconds / 3600;
    929                                             seconds -= (double)hours * 3600.0;
    930                                             int minutes = seconds / 60;
    931                                             seconds -= (double)minutes * 60.0;
    932                                             psStringAppend(&dateString, "T%02d:%02d:%02f", hours, minutes,
    933                                                            seconds);
    934                                         }
    935                                         time = psTimeFromISO(dateString, timeSys);
    936                                     } // Reading date and time
    937                                     psFree(timeName);
    938                                 } // Reading headers
    939                             } else {
    940                                 psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%s) --- trying "
    941                                         "ISO\n", timeString);
    942                                 time = psTimeFromISO(timeString, timeSys);
    943                             } // Interpreting the time string
    944                         }
    945                         break;
    946                       case PS_TYPE_F32:
    947                         {
    948                             double timeValue = (double)timeItem->data.F32;
    949                             if (strcasecmp(timeFormat, "JD") == 0) {
    950                                 time = psTimeFromJD(timeValue);
    951                             } else if (strcasecmp(timeFormat, "MJD") == 0) {
    952                                 time = psTimeFromMJD(timeValue);
    953                             } else {
    954                                 psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
    955                                         "JD\n", timeValue);
    956                                 time = psTimeFromJD(timeValue);
    957                             }
    958                         }
    959                         break;
    960                       case PS_TYPE_F64:
    961                         {
    962                             double timeValue = (double)timeItem->data.F64;
    963                             if (strcasecmp(timeFormat, "JD") == 0) {
    964                                 time = psTimeFromJD(timeValue);
    965                             } else if (strcasecmp(timeFormat, "MJD") == 0) {
    966                                 time = psTimeFromMJD(timeValue);
    967                             } else {
    968                                 psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
    969                                         "JD\n", timeValue);
    970                                 time = psTimeFromJD(timeValue);
    971                             }
    972                         }
    973                         break;
    974                       default:
    975                         psError(PS_ERR_IO, true, "Unable to parse CELL.TIME.\n");
    976                     }
    977                 } else {
    978                     psError(PS_ERR_IO, false, "Unable to find CELL.TIME in FORMATS.\n");
    979                 } // Getting the format
    980             } else {
    981                 psError(PS_ERR_IO, false, "Unable to find FORMATS in camera configuration.\n");
    982             } // Getting the formats
    983         } // Getting CELL.TIME
    984 
    985         psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.TIME", PS_DATA_UNKNOWN, "Time of exposure", time);
    986         psFree(time);
    987     }
    988 
    989     // These are new and experimental concepts: CELL.X0 and CELL.Y0
    990 
    991     // CELL.X0
    992     p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.X0", "Position of (0,0) on the chip ");
    993     // CELL.Y0
    994     p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.Y0", "Position of (0,0) on the chip");
    995     // Add corrective
    996     {
    997         const psMetadata *camera = fpa->camera;
    998         bool mdok = false;              // Result of MD lookup
    999         psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
    1000         if (mdok && formats) {
    1001             psString format = psMetadataLookupString(&mdok, formats, "CELL.X0");
    1002             if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
    1003                 psMetadataItem *cellx0 = psMetadataLookup(cell->concepts, "CELL.X0");
    1004                 cellx0->data.S32 -= 1;
    1005             }
    1006             format = psMetadataLookupString(&mdok, formats, "CELL.Y0");
    1007             if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
    1008                 psMetadataItem *celly0 = psMetadataLookup(cell->concepts, "CELL.Y0");
    1009                 celly0->data.S32 -= 1;
    1010             }
    1011         }
    1012     }
    1013 
    1014     // Pau.
     1024    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.EXPOSURE");
     1025    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.DARKTIME");
     1026
     1027
     1028    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.TRIMSEC");
     1029    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.BIASSEC");
     1030    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.TIME"); // This also does CELL.TIMESYS
     1031    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.XBIN");
     1032    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.YBIN");
     1033    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.X0");
     1034    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.Y0");
     1035
    10151036}
    10161037
     
    10181039// Retrieve a concept
    10191040psMetadataItem *pmCellGetConcept(pmCell *cell, // The cell
    1020                                 const char *concept // The concept
     1041                                const char *concept // The concept
    10211042    )
    10221043{
    10231044    psMetadataItem *item = psMetadataLookup(cell->concepts, concept);
    10241045    if (! item) {
    1025         pmChip *chip = cell->parent;
    1026         item = psMetadataLookup(chip->concepts, concept);
    1027         if (! item) {
    1028             pmFPA *fpa = chip->parent;
    1029             item = psMetadataLookup(fpa->concepts, concept);
    1030         }
    1031     }
    1032     return item;                        // item is either NULL or is what we want.
    1033 }
     1046        pmChip *chip = cell->parent;
     1047        item = psMetadataLookup(chip->concepts, concept);
     1048        if (! item) {
     1049            pmFPA *fpa = chip->parent;
     1050            item = psMetadataLookup(fpa->concepts, concept);
     1051        }
     1052    }
     1053    return item;                        // item is either NULL or is what we want.
     1054}
Note: See TracChangeset for help on using the changeset viewer.