1  /* fitstopnm.c - read a FITS file and produce a PNM.
2  **
3  ** Copyright (C) 1989 by Jef Poskanzer.
4  **
5  ** Permission to use, copy, modify, and distribute this software and its
6  ** documentation for any purpose and without fee is hereby granted, provided
7  ** that the above copyright notice appear in all copies and that both that
8  ** copyright notice and this permission notice appear in supporting
9  ** documentation.  This software is provided "as is" without express or
10  ** implied warranty.
11  **
12  ** Hacked up version by Daniel Briggs  (dbriggs@nrao.edu)  20-Oct-92
13  **
14  ** Include floating point formats, more or less.  Will only work on
15  ** machines that understand IEEE-754.  Added -scanmax -printmax
16  ** -min -max and -noraw.  Ignore axes past 3, instead of error (many packages
17  ** use pseudo axes).  Use a finite scale when max=min.  NB: Min and max
18  ** are the real world FITS values (scaled), so watch out when bzer & bscale
19  ** are not 0 & 1.  Datamin & datamax interpreted correctly in scaled case,
20  ** and initialization changed to less likely values.  If datamin & max are
21  ** not present in the header, the a first pass is made to determine them
22  ** from the array values.
23  **
24  ** Modified by Alberto Accomazzi (alberto@cfa.harvard.edu), Dec 1, 1992.
25  **
26  ** Added understanding of 3-plane FITS files, the program is renamed
27  ** fitstopnm.  Fixed some non-ansi declarations (DBL_MAX and FLT_MAX
28  ** replace MAXDOUBLE and MAXFLOAT), fixed some scaling parameters to
29  ** map the full FITS data resolution to the maximum PNM resolution,
30  ** disabled min max scanning when reading from stdin.
31  */
32 
33 /*
34   The official specification of FITS format (which is for more than
35   just visual images) is at
36   ftp://legacy.gsfc.nasa.gov/fits_info/fits_office/fits_standard.pdf
37 
38   An example FITS file is at
39 
40     http://fits.gsfc.nasa.gov/nrao_data/tests/incunabula/mndrll-8.fits
41 
42 */
43 
44 #include <string.h>
45 #include <float.h>
46 #include <assert.h>
47 
48 #include "pm_config.h"
49 #include "pm_c_util.h"
50 #include "mallocvar.h"
51 #include "floatcode.h"
52 #include "shhopt.h"
53 #include "pnm.h"
54 
55 
56 
57 struct CmdlineInfo {
58     const char * inputFileName;
59     unsigned int image;  /* zero if unspecified */
60     float max;
61     unsigned int maxSpec;
62     float min;
63     unsigned int minSpec;
64     unsigned int scanmax;
65     unsigned int printmax;
66     unsigned int noraw;
67         /* This is for backward compatibility only.  Use the common option
68            -plain now.  (pnm_init() processes -plain).
69         */
70     unsigned int verbose;
71     unsigned int omaxval;
72     unsigned int omaxvalSpec;
73 };
74 
75 
76 
77 static void
parseCommandLine(int argc,const char ** argv,struct CmdlineInfo * const cmdlineP)78 parseCommandLine(int argc, const char ** argv,
79                  struct CmdlineInfo * const cmdlineP) {
80 /* --------------------------------------------------------------------------
81    Parse program command line described in Unix standard form by argc
82    and argv.  Return the information in the options as *cmdlineP.
83 
84    If command line is internally inconsistent (invalid options, etc.),
85    issue error message to stderr and abort program.
86 
87    Note that the strings we return are stored in the storage that
88    was passed to us as the argv array.  We also trash *argv.
89 --------------------------------------------------------------------------*/
90     optEntry * option_def;
91         /* Instructions to pm_optParseOptions3 on how to parse our options. */
92     optStruct3 opt;
93 
94     unsigned int imageSpec;
95     unsigned int option_def_index;
96 
97     MALLOCARRAY_NOFAIL(option_def, 100);
98 
99     option_def_index = 0;   /* incremented by OPTENT3 */
100     OPTENT3(0, "image",    OPT_UINT,
101             &cmdlineP->image,   &imageSpec,                            0);
102     OPTENT3(0, "min",      OPT_FLOAT,
103             &cmdlineP->min,     &cmdlineP->minSpec,                    0);
104     OPTENT3(0, "max",      OPT_FLOAT,
105             &cmdlineP->max,     &cmdlineP->maxSpec,                    0);
106     OPTENT3(0, "scanmax",  OPT_FLAG,
107             NULL,               &cmdlineP->scanmax,                    0);
108     OPTENT3(0, "printmax", OPT_FLAG,
109             NULL,               &cmdlineP->printmax,                   0);
110     OPTENT3(0, "noraw",    OPT_FLAG,
111             NULL,               &cmdlineP->noraw,                      0);
112     OPTENT3(0, "verbose",  OPT_FLAG,
113             NULL,               &cmdlineP->verbose,                    0);
114     OPTENT3(0, "omaxval",  OPT_UINT,
115             &cmdlineP->omaxval, &cmdlineP->omaxvalSpec,                0);
116 
117     opt.opt_table = option_def;
118     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
119     opt.allowNegNum = FALSE;   /* We have no parms that are negative numbers */
120 
121     /* Set some defaults the lazy way (using multiple setting of variables) */
122 
123     pm_optParseOptions3(&argc, (char**)argv, opt, sizeof(opt), 0);
124         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
125 
126     if (imageSpec) {
127         if (cmdlineP->image == 0)
128             pm_error("You may not specify zero for the image number.  "
129                      "Images are numbered starting at 1.");
130     } else
131         cmdlineP->image = 0;
132 
133     if (cmdlineP->maxSpec && cmdlineP->minSpec) {
134         if (cmdlineP->max <= cmdlineP->min)
135             pm_error("-max must be greater than -min.  You specified "
136                      "-max=%f, -min=%f", cmdlineP->max, cmdlineP->min);
137     }
138 
139     if (argc-1 < 1)
140         cmdlineP->inputFileName = "-";
141     else {
142         cmdlineP->inputFileName = argv[1];
143 
144         if (argc-1 > 1)
145             pm_error("Too many arguments (%u).  The only non-option argument "
146                      "is the input file name.", argc-1);
147     }
148     free(option_def);
149 }
150 
151 
152 
153 struct FITS_Header {
154   int simple;       /* basic format or not */
155   int bitpix;
156       /* number of bits per pixel, positive for integer, negative
157          for floating point
158       */
159   int naxis;        /* number of axes */
160   int naxis1;       /* number of points on axis 1 */
161   int naxis2;       /* number of points on axis 2 */
162   int naxis3;       /* number of points on axis 3 */
163   double datamin;   /* min # (Physical value!) */
164   double datamax;   /* max #     "       "     */
165   double bzer;      /* Physical value = Array value*bscale + bzero */
166   double bscale;
167 };
168 
169 
170 typedef enum {
171     VF_CHAR, VF_SHORT, VF_LONG, VF_FLOAT, VF_DOUBLE
172 } valFmt;
173 
174 struct fitsRasterInfo {
175     valFmt valFmt;
176     double bzer;
177     double bscale;
178 };
179 
180 /* This code deals properly with integers, no matter what the byte order
181    or integer size of the host machine.  We handle sign extension manually to
182    prevent problems with signed/unsigned characters.  We read floating point
183    values properly only when the host architecture conforms to IEEE-754.  If
184    you need to tweak this code for other machines, you might want to get a
185    copy of the FITS documentation from nssdca.gsfc.nasa.gov
186 */
187 
188 static void
readFitsChar(FILE * const ifP,double * const vP)189 readFitsChar(FILE *   const ifP,
190              double * const vP) {
191 
192     /* 8 bit FITS integers are unsigned */
193 
194     int const ich = getc(ifP);
195 
196     if (ich == EOF)
197         pm_error("EOF / read error");
198     else
199         *vP = ich;
200 }
201 
202 
203 
204 static void
readFitsShort(FILE * const ifP,double * const vP)205 readFitsShort(FILE *   const ifP,
206               double * const vP) {
207 
208     int ich;
209     int ival;
210     unsigned char c[8];
211 
212     ich = getc(ifP);
213 
214     if (ich == EOF)
215         pm_error("EOF / read error");
216 
217     c[0] = ich;
218 
219     ich = getc(ifP);
220 
221     if (ich == EOF)
222         pm_error("EOF / read error");
223 
224     c[1] = ich;
225 
226     if (c[0] & 0x80)
227         ival = ~0xFFFF | c[0] << 8 | c[1];
228     else
229         ival = c[0] << 8 | c[1];
230 
231     *vP = ival;
232 }
233 
234 
235 
236 static void
readFitsLong(FILE * const ifP,double * const vP)237 readFitsLong(FILE *   const ifP,
238              double * const vP) {
239 
240     unsigned int i;
241     long int lval;
242     unsigned char c[4];
243 
244     for (i = 0; i < 4; ++i) {
245         int const ich = getc(ifP);
246         if (ich == EOF)
247             pm_error("EOF / read error");
248         c[i] = ich;
249     }
250 
251     if (c[0] & 0x80)
252         lval = ~0xFFFFFFFF | c[0] << 24 | c[1] << 16 | c[2] << 8 | c[3];
253     else
254         lval = c[0] << 24 | c[1] << 16 | c[2] << 8 | c[3] << 0;
255 
256     *vP = lval;
257 }
258 
259 
260 
261 static void
readFitsFloat(FILE * const ifP,double * const vP)262 readFitsFloat(FILE *   const ifP,
263               double * const vP) {
264 
265     unsigned int i;
266     pm_bigendFloat bigend;
267 
268     for (i = 0; i < 4; ++i) {
269         int const ich = getc(ifP);
270         if (ich == EOF)
271             pm_error("EOF / read error");
272         bigend.bytes[i] = ich;
273     }
274 
275     *vP = pm_floatFromBigendFloat(bigend);
276 }
277 
278 
279 
280 static void
readFitsDouble(FILE * const ifP,double * const vP)281 readFitsDouble(FILE *   const ifP,
282                double * const vP) {
283 
284     unsigned int i;
285     pm_bigendDouble bigend;
286 
287     for (i = 0; i < 8; ++i) {
288         int const ich = getc(ifP);
289         if (ich == EOF)
290             pm_error("EOF / read error");
291         bigend.bytes[i] = ich;
292     }
293 
294     *vP = pm_doubleFromBigendDouble(bigend);
295 }
296 
297 
298 
299 static valFmt
valFmtFromBitpix(int const bitpix)300 valFmtFromBitpix(int const bitpix) {
301 /*----------------------------------------------------------------------------
302    Return the format of a "value" in the FITS file, given the value
303    of the BITPIX header in the FITS file.
304 
305    BITPIX has a stupid format wherein it is fundamentally the number
306    of bits per value, but its sign indicates whether it is integer
307    or floating point.
308 -----------------------------------------------------------------------------*/
309     switch (bitpix) {
310     case  +8: return VF_CHAR;
311     case +16: return VF_SHORT;
312     case +32: return VF_LONG;
313     case -32: return VF_FLOAT;
314     case -64: return VF_DOUBLE;
315     default:
316         /* Every possibility is covered above. */
317         assert(false);
318         return 0;  /* quiet compiler warning */
319     }
320 }
321 
322 
323 
324 static void
readVal(FILE * const ifP,valFmt const fmt,double * const vP)325 readVal(FILE *   const ifP,
326         valFmt   const fmt,
327         double * const vP) {
328 
329     switch (fmt) {
330     case VF_CHAR:
331         readFitsChar(ifP, vP);
332         break;
333 
334     case VF_SHORT:
335         readFitsShort(ifP, vP);
336         break;
337 
338     case VF_LONG:
339         readFitsLong(ifP, vP);
340         break;
341 
342     case VF_FLOAT:
343         readFitsFloat(ifP, vP);
344         break;
345 
346     case VF_DOUBLE:
347         readFitsDouble(ifP, vP);
348         break;
349     }
350 }
351 
352 
353 
354 static void
readCard(FILE * const ifP,char * const buf)355 readCard(FILE * const ifP,
356          char * const buf) {
357 
358     size_t bytesRead;
359 
360     bytesRead = fread(buf, 1, 80, ifP);
361     if (bytesRead == 0)
362         pm_error("error reading header");
363 }
364 
365 
366 
367 static void
readFitsHeader(FILE * const ifP,struct FITS_Header * const hP)368 readFitsHeader(FILE *               const ifP,
369                struct FITS_Header * const hP) {
370 
371     int seenEnd;
372 
373     seenEnd = 0;
374     /* Set defaults */
375     hP->simple  = 0;
376     hP->bzer    = 0.0;
377     hP->bscale  = 1.0;
378     hP->datamin = - DBL_MAX;
379     hP->datamax = DBL_MAX;
380 
381     while (!seenEnd) {
382         unsigned int i;
383         for (i = 0; i < 36; ++i) {
384             char buf[80];
385             char c;
386 
387             readCard(ifP, buf);
388 
389             if (sscanf(buf, "SIMPLE = %c", &c) == 1) {
390                 if (c == 'T' || c == 't')
391                     hP->simple = 1;
392             } else if (sscanf(buf, "BITPIX = %d", &(hP->bitpix)) == 1);
393             else if (sscanf(buf, "NAXIS = %d", &(hP->naxis)) == 1);
394             else if (sscanf(buf, "NAXIS1 = %d", &(hP->naxis1)) == 1);
395             else if (sscanf(buf, "NAXIS2 = %d", &(hP->naxis2)) == 1);
396             else if (sscanf(buf, "NAXIS3 = %d", &(hP->naxis3)) == 1);
397             else if (sscanf(buf, "DATAMIN = %lf", &(hP->datamin)) == 1);
398             else if (sscanf(buf, "DATAMAX = %lf", &(hP->datamax)) == 1);
399             else if (sscanf(buf, "BZERO = %lf", &(hP->bzer)) == 1);
400             else if (sscanf(buf, "BSCALE = %lf", &(hP->bscale)) == 1);
401             else if (strncmp(buf, "END ", 4 ) == 0) seenEnd = 1;
402         }
403     }
404 }
405 
406 
407 
408 static void
interpretPlanes(struct FITS_Header const fitsHeader,unsigned int const imageRequest,bool const verbose,unsigned int * const imageCountP,bool * const multiplaneP,unsigned int * const desiredImageP)409 interpretPlanes(struct FITS_Header const fitsHeader,
410                 unsigned int       const imageRequest,
411                 bool               const verbose,
412                 unsigned int *     const imageCountP,
413                 bool *             const multiplaneP,
414                 unsigned int *     const desiredImageP) {
415 
416     if (fitsHeader.naxis == 2) {
417         *imageCountP   = 1;
418         *multiplaneP   = FALSE;
419         *desiredImageP = 1;
420     } else {
421         if (imageRequest) {
422             if (imageRequest > fitsHeader.naxis3)
423                 pm_error("Only %u plane%s in this file.  "
424                          "You requested image %u",
425                          fitsHeader.naxis3, fitsHeader.naxis3 > 1 ? "s" : "",
426                          imageRequest);
427             else {
428                 *imageCountP   = fitsHeader.naxis3;
429                 *multiplaneP   = FALSE;
430                 *desiredImageP = imageRequest;
431             }
432         } else {
433             if (fitsHeader.naxis3 == 3) {
434                 *imageCountP   = 1;
435                 *multiplaneP   = TRUE;
436                 *desiredImageP = 1;
437             } else if (fitsHeader.naxis3 > 1)
438                 pm_error("This FITS file contains multiple (%u) images.  "
439                          "You must specify which one you want with a "
440                          "-image option.", fitsHeader.naxis3);
441             else {
442                 *imageCountP   = fitsHeader.naxis3;
443                 *multiplaneP   = FALSE;
444                 *desiredImageP = 1;
445             }
446         }
447     }
448     if (verbose) {
449 
450         pm_message("FITS stream is %smultiplane", *multiplaneP ? "" : "not ");
451         pm_message("We will take image %u (1 is first) of %u "
452                    "in the FITS stream",
453                    *desiredImageP, *imageCountP);
454     }
455 }
456 
457 
458 
459 static void
scanImageForMinMax(FILE * const ifP,unsigned int const images,int const cols,int const rows,valFmt const valFmt,double const bscale,double const bzer,unsigned int const imagenum,bool const multiplane,double * const dataminP,double * const datamaxP)460 scanImageForMinMax(FILE *       const ifP,
461                    unsigned int const images,
462                    int          const cols,
463                    int          const rows,
464                    valFmt       const valFmt,
465                    double       const bscale,
466                    double       const bzer,
467                    unsigned int const imagenum,
468                    bool         const multiplane,
469                    double *     const dataminP,
470                    double *     const datamaxP) {
471 
472     /* Note that a value in the file might be Not-A-Number.  We ignore
473        such entries in computing the minimum and maximum for the image.
474     */
475     double dmax, dmin;
476     unsigned int image;
477     pm_filepos rasterPos;
478     double fmaxval;
479 
480     pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
481 
482     pm_message("Scanning file for scaling parameters");
483 
484     switch (valFmt) {
485     case VF_CHAR:   fmaxval = 255.0;        break;
486     case VF_SHORT:  fmaxval = 65535.0;      break;
487     case VF_LONG:   fmaxval = 4294967295.0; break;
488     case VF_FLOAT:  fmaxval = FLT_MAX;      break;
489     case VF_DOUBLE: fmaxval = DBL_MAX;      break;
490     }
491 
492     dmax = -fmaxval;
493     dmin = fmaxval;
494     for (image = 1; image <= images; ++image) {
495         unsigned int row;
496         for (row = 0; row < rows; ++row) {
497             unsigned int col;
498             for (col = 0; col < cols; ++col) {
499                 double val;
500                 readVal(ifP, valFmt, &val);
501                 if (image == imagenum || multiplane ) {
502                     /* Note: if 'val' is NaN, result is 2nd operand */
503                     dmax = MAX(val, dmax);
504                     dmin = MIN(val, dmin);
505                 }
506             }
507         }
508         if (bscale < 0.0) {
509             double const origDmax = dmax;
510             dmax = dmin;
511             dmin = origDmax;
512         }
513     }
514     *dataminP = dmin * bscale + bzer;
515     *datamaxP = dmax * bscale + bzer;
516 
517     pm_message("Scan results: min=%f max=%f", *dataminP, *datamaxP);
518 
519     pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
520 }
521 
522 
523 
524 static void
computeMinMax(FILE * const ifP,unsigned int const images,int const cols,int const rows,struct FITS_Header const h,unsigned int const imagenum,bool const multiplane,bool const forcemin,bool const forcemax,double const frmin,double const frmax,double * const dataminP,double * const datamaxP)525 computeMinMax(FILE *             const ifP,
526               unsigned int       const images,
527               int                const cols,
528               int                const rows,
529               struct FITS_Header const h,
530               unsigned int       const imagenum,
531               bool               const multiplane,
532               bool               const forcemin,
533               bool               const forcemax,
534               double             const frmin,
535               double             const frmax,
536               double *           const dataminP,
537               double *           const datamaxP) {
538 
539     double datamin, datamax;
540 
541     datamin = -DBL_MAX;  /* initial assumption */
542     datamax = DBL_MAX;   /* initial assumption */
543 
544     if (forcemin)
545         datamin = frmin;
546     if (forcemax)
547         datamax = frmax;
548 
549     if (datamin == -DBL_MAX)
550         datamin = h.datamin;
551     if (datamax == DBL_MAX)
552         datamax = h.datamax;
553 
554     if (datamin == -DBL_MAX || datamax == DBL_MAX) {
555         double scannedDatamin, scannedDatamax;
556         scanImageForMinMax(ifP, images, cols, rows,
557                            valFmtFromBitpix(h.bitpix), h.bscale, h.bzer,
558                            imagenum, multiplane,
559                            &scannedDatamin, &scannedDatamax);
560 
561         if (datamin == -DBL_MAX)
562             datamin = scannedDatamin;
563         if (datamax == DBL_MAX)
564             datamax = scannedDatamax;
565     }
566     *dataminP = datamin;
567     *datamaxP = datamax;
568 }
569 
570 
571 
572 static xelval
determineMaxval(struct CmdlineInfo const cmdline,valFmt const valFmt,double const datamax,double const datamin)573 determineMaxval(struct CmdlineInfo const cmdline,
574                 valFmt             const valFmt,
575                 double             const datamax,
576                 double             const datamin) {
577 
578     xelval retval;
579 
580     if (cmdline.omaxvalSpec)
581         retval = cmdline.omaxval;
582     else {
583         if (valFmt == VF_FLOAT || valFmt == VF_DOUBLE) {
584             /* samples are floating point, which means the resolution
585                could be anything.  So we just pick a convenient maxval
586                of 255.  Before Netpbm 10.20 (January 2004), we did
587                maxval = max - min for floating point as well as
588                integer samples.
589             */
590             retval = 255;
591             if (cmdline.verbose)
592                 pm_message("FITS image has floating point samples.  "
593                            "Using maxval = %u.", (unsigned int)retval);
594         } else {
595             retval = MAX(1, MIN(PNM_OVERALLMAXVAL, datamax - datamin));
596             if (cmdline.verbose)
597                 pm_message("FITS image has samples in the range %d-%d.  "
598                            "Using maxval %u.",
599                            (int)(datamin+0.5), (int)(datamax+0.5),
600                            (unsigned int)retval);
601         }
602     }
603     return retval;
604 }
605 
606 
607 
608 static void
convertPgmRaster(FILE * const ifP,unsigned int const cols,unsigned int const rows,xelval const maxval,unsigned int const desiredImage,unsigned int const imageCount,struct fitsRasterInfo const rasterInfo,double const scale,double const datamin,xel ** const xels)609 convertPgmRaster(FILE *                const ifP,
610                  unsigned int          const cols,
611                  unsigned int          const rows,
612                  xelval                const maxval,
613                  unsigned int          const desiredImage,
614                  unsigned int          const imageCount,
615                  struct fitsRasterInfo const rasterInfo,
616                  double                const scale,
617                  double                const datamin,
618                  xel **                const xels) {
619 
620     /* Note: the FITS specification does not give the association between
621        file position and image position (i.e. is the first pixel in the
622        file the top left, bottom left, etc.).  We use the common sense,
623        popular order of row major, top to bottom, left to right.  This
624        has been the case and accepted since 1989, but in 2008, we discovered
625        that Gimp and ImageMagick do bottom to top.
626     */
627     unsigned int image;
628 
629     pm_message("writing PGM file");
630 
631     for (image = 1; image <= desiredImage; ++image) {
632         unsigned int row;
633         if (image != desiredImage)
634             pm_message("skipping image plane %u of %u", image, imageCount);
635         else if (imageCount > 1)
636             pm_message("reading image plane %u of %u", image, imageCount);
637         for (row = 0; row < rows; ++row) {
638             unsigned int col;
639             for (col = 0; col < cols; ++col) {
640                 double val;
641                 readVal(ifP, rasterInfo.valFmt, &val);
642                 {
643                     double const t = scale *
644                         (val * rasterInfo.bscale + rasterInfo.bzer - datamin);
645                     xelval const tx = MAX(0, MIN(t, maxval));
646                     if (image == desiredImage)
647                         PNM_ASSIGN1(xels[row][col], tx);
648                 }
649             }
650         }
651     }
652 }
653 
654 
655 
656 static void
convertPpmRaster(FILE * const ifP,unsigned int const cols,unsigned int const rows,xelval const maxval,struct fitsRasterInfo const rasterInfo,double const scale,double const datamin,xel ** const xels)657 convertPpmRaster(FILE *                const ifP,
658                  unsigned int          const cols,
659                  unsigned int          const rows,
660                  xelval                const maxval,
661                  struct fitsRasterInfo const rasterInfo,
662                  double                const scale,
663                  double                const datamin,
664                  xel **                const xels) {
665 /*----------------------------------------------------------------------------
666    Read the FITS raster from file *ifP into xels[][].  Image dimensions
667    are 'cols' by 'rows'.  The FITS raster is 3 planes composing one
668    image: a red plane followed by a green plane followed by a blue plane.
669 -----------------------------------------------------------------------------*/
670     unsigned int plane;
671 
672     pm_message("Writing PPM file "
673                "(Probably not what you want - consider an -image option)");
674 
675     for (plane = 0; plane < 3; ++plane) {
676         unsigned int row;
677         pm_message("reading image plane %u (%s)",
678                    plane, plane == 0 ? "red" : plane == 1 ? "green" : "blue");
679         for (row = 0; row < rows; ++row) {
680             unsigned int col;
681             for (col = 0; col < cols; ++col) {
682                 double val;
683                 readVal(ifP, rasterInfo.valFmt, &val);
684                 {
685                     double const t = scale *
686                         (val * rasterInfo.bscale + rasterInfo.bzer - datamin);
687                     xelval const sample = MAX(0, MIN(t, maxval));
688 
689                     switch (plane) {
690                     case 0: PPM_PUTR(xels[row][col], sample); break;
691                     case 1: PPM_PUTG(xels[row][col], sample); break;
692                     case 2: PPM_PUTB(xels[row][col], sample); break;
693                     }
694                 }
695             }
696         }
697     }
698 }
699 
700 
701 
702 static void
convertRaster(FILE * const ifP,unsigned int const cols,unsigned int const rows,xelval const maxval,bool const forceplain,bool const multiplane,unsigned int const desiredImage,unsigned int const imageCount,struct fitsRasterInfo const rasterInfo,double const scale,double const datamin)703 convertRaster(FILE *                const ifP,
704               unsigned int          const cols,
705               unsigned int          const rows,
706               xelval                const maxval,
707               bool                  const forceplain,
708               bool                  const multiplane,
709               unsigned int          const desiredImage,
710               unsigned int          const imageCount,
711               struct fitsRasterInfo const rasterInfo,
712               double                const scale,
713               double                const datamin) {
714 
715     xel ** xels;
716     int format;
717 
718     xels = pnm_allocarray(cols, rows);
719 
720     if (multiplane) {
721         format = PPM_FORMAT;
722         convertPpmRaster(ifP, cols, rows, maxval, rasterInfo, scale, datamin,
723                          xels);
724     } else {
725         format = PGM_FORMAT;
726         convertPgmRaster(ifP, cols, rows, maxval,
727                          desiredImage, imageCount, rasterInfo, scale, datamin,
728                          xels);
729     }
730     pnm_writepnm(stdout, xels, cols, rows, maxval, format, forceplain);
731     pnm_freearray(xels, rows);
732 }
733 
734 
735 
736 int
main(int argc,const char * argv[])737 main(int argc, const char * argv[]) {
738 
739     struct CmdlineInfo cmdline;
740     FILE * ifP;
741     unsigned int cols, rows;
742     xelval maxval;
743     double scale;
744     double datamin, datamax;
745     struct FITS_Header fitsHeader;
746     struct fitsRasterInfo rasterInfo;
747 
748     unsigned int imageCount;
749     unsigned int desiredImage;
750         /* Plane number (starting at one) of plane that contains the image
751            we want.
752         */
753     bool multiplane;
754         /* This is a one-image multiplane stream; 'desiredImage'
755            is undefined
756         */
757 
758     pm_proginit(&argc, argv);
759 
760     parseCommandLine(argc, argv, &cmdline);
761 
762     ifP = pm_openr(cmdline.inputFileName);
763 
764     readFitsHeader(ifP, &fitsHeader);
765 
766     if (!fitsHeader.simple)
767         pm_error("FITS file is not in simple format, can't read");
768 
769     if (fitsHeader.naxis != 2 && fitsHeader.naxis != 3)
770         pm_message("Warning: FITS file has %u axes", fitsHeader.naxis);
771 
772     cols = fitsHeader.naxis1;
773     rows = fitsHeader.naxis2;
774 
775     rasterInfo.bscale = fitsHeader.bscale;
776     rasterInfo.bzer   = fitsHeader.bzer;
777     rasterInfo.valFmt = valFmtFromBitpix(fitsHeader.bitpix);
778 
779     interpretPlanes(fitsHeader, cmdline.image, cmdline.verbose,
780                     &imageCount, &multiplane, &desiredImage);
781 
782     computeMinMax(ifP, imageCount, cols, rows, fitsHeader,
783                   desiredImage, multiplane,
784                   cmdline.minSpec, cmdline.maxSpec,
785                   cmdline.min, cmdline.max,
786                   &datamin, &datamax);
787 
788     maxval = determineMaxval(cmdline, rasterInfo.valFmt, datamax, datamin);
789 
790     if (datamax - datamin == 0)
791         scale = 1.0;
792     else
793         scale = maxval / (datamax - datamin);
794 
795     if (cmdline.printmax)
796         printf("%f %f\n", datamin, datamax);
797     else
798         convertRaster(ifP, cols, rows, maxval, cmdline.noraw,
799                       multiplane, desiredImage, imageCount,
800                       rasterInfo, scale, datamin);
801 
802     pm_close(ifP);
803     pm_close(stdout);
804 
805     return 0;
806 }
807 
808 
809 
810