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