/src/graphicsmagick/coders/fits.c
Line | Count | Source |
1 | | /* |
2 | | % Copyright (C) 2003-2025 GraphicsMagick Group |
3 | | % Copyright (C) 2002 ImageMagick Studio |
4 | | % Copyright 1991-1999 E. I. du Pont de Nemours and Company |
5 | | % |
6 | | % This program is covered by multiple licenses, which are described in |
7 | | % Copyright.txt. You should have received a copy of Copyright.txt with this |
8 | | % package; otherwise see http://www.graphicsmagick.org/www/Copyright.html. |
9 | | % |
10 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
11 | | % % |
12 | | % % |
13 | | % % |
14 | | % FFFFF IIIII TTTTT SSSSS % |
15 | | % F I T SS % |
16 | | % FFF I T SSS % |
17 | | % F I T SS % |
18 | | % F IIIII T SSSSS % |
19 | | % % |
20 | | % % |
21 | | % Read/Write Flexible Image Transport System Images. % |
22 | | % % |
23 | | % % |
24 | | % Software Design % |
25 | | % John Cristy % |
26 | | % July 1992 % |
27 | | % Jaroslav Fojtik % |
28 | | % 2008 - 2022 % |
29 | | % % |
30 | | % % |
31 | | % % |
32 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
33 | | % |
34 | | % |
35 | | */ |
36 | | |
37 | | /* |
38 | | Include declarations. |
39 | | */ |
40 | | #include "magick/studio.h" |
41 | | #include "magick/blob.h" |
42 | | #include "magick/colormap.h" |
43 | | #include "magick/constitute.h" |
44 | | #include "magick/enum_strings.h" |
45 | | #include "magick/magick.h" |
46 | | #include "magick/monitor.h" |
47 | | #include "magick/pixel_cache.h" |
48 | | #include "magick/utility.h" |
49 | | #include "magick/version.h" |
50 | | |
51 | | /* |
52 | | Forward declarations. |
53 | | */ |
54 | | static unsigned int |
55 | | WriteFITSImage(const ImageInfo *,Image *); |
56 | | |
57 | | |
58 | 62.8M | #define FITS_BLOCK_SIZE 2880 |
59 | 3.82M | #define FITS_ROW_SIZE 80 |
60 | | |
61 | | |
62 | | /* Convert signed values to unsigned - and reverse. */ |
63 | | static void FixSignedValues(unsigned char *data, int size, unsigned step, unsigned endian) |
64 | 35.1k | { |
65 | 35.1k | if(endian != MSBEndian) |
66 | 0 | { |
67 | 0 | data += step - 1; /* LSB has most signifficant byte at the end */ |
68 | 0 | } /* MSB has most signifficant byte first */ |
69 | | |
70 | 347k | while(size-->0) |
71 | 312k | { |
72 | 312k | *data ^= 0x80; |
73 | 312k | data += step; |
74 | 312k | } |
75 | 35.1k | } |
76 | | |
77 | | /* |
78 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
79 | | % % |
80 | | % % |
81 | | % % |
82 | | % I s F I T S % |
83 | | % % |
84 | | % % |
85 | | % % |
86 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
87 | | % |
88 | | % Method IsFITS returns True if the image format type, identified by the |
89 | | % magick string, is FITS. |
90 | | % |
91 | | % The format of the IsFITS method is: |
92 | | % |
93 | | % unsigned int IsFITS(const unsigned char *magick,const size_t length) |
94 | | % |
95 | | % A description of each parameter follows: |
96 | | % |
97 | | % o status: Method IsFITS returns True if the image format type is FITS. |
98 | | % |
99 | | % o magick: This string is generally the first few bytes of an image file |
100 | | % or blob. |
101 | | % |
102 | | % o length: Specifies the length of the magick string. |
103 | | % |
104 | | % |
105 | | */ |
106 | | static unsigned int IsFITS(const unsigned char *magick,const size_t length) |
107 | 0 | { |
108 | 0 | if (length < 6) |
109 | 0 | return(False); |
110 | 0 | if (LocaleNCompare((char *) magick,"IT0",3) == 0) |
111 | 0 | return(True); |
112 | 0 | if (LocaleNCompare((char *) magick,"SIMPLE",6) == 0) |
113 | 0 | return(True); |
114 | 0 | return(False); |
115 | 0 | } |
116 | | |
117 | | /* |
118 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
119 | | % % |
120 | | % % |
121 | | % % |
122 | | % R e a d F I T S I m a g e % |
123 | | % % |
124 | | % % |
125 | | % % |
126 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
127 | | % |
128 | | % Method ReadFITSImage reads a FITS image file and returns it. It |
129 | | % allocates the memory necessary for the new Image structure and returns a |
130 | | % pointer to the new image. |
131 | | % |
132 | | % The format of the ReadFITSImage method is: |
133 | | % |
134 | | % Image *ReadFITSImage(const ImageInfo *image_info, |
135 | | % ExceptionInfo *exception) |
136 | | % |
137 | | % A description of each parameter follows: |
138 | | % |
139 | | % o image: Method ReadFITSImage returns a pointer to the image after |
140 | | % reading. A null image is returned if there is a memory shortage or if |
141 | | % the image cannot be read. |
142 | | % |
143 | | % o filename: Specifies the name of the image to read. |
144 | | % |
145 | | % o exception: return any errors or warnings in this structure. |
146 | | % |
147 | | % |
148 | | */ |
149 | | static Image *ReadFITSImage(const ImageInfo *image_info, |
150 | | ExceptionInfo *exception) |
151 | 26.5k | { |
152 | 26.5k | typedef struct _FITSInfo |
153 | 26.5k | { |
154 | 26.5k | char |
155 | 26.5k | extensions_exist; |
156 | | |
157 | 26.5k | int |
158 | 26.5k | simple, |
159 | 26.5k | bits_per_pixel, |
160 | 26.5k | columns, |
161 | 26.5k | rows, |
162 | 26.5k | number_axes, |
163 | 26.5k | number_scenes; |
164 | | |
165 | 26.5k | double |
166 | 26.5k | min_data, |
167 | 26.5k | max_data, |
168 | 26.5k | zero, |
169 | 26.5k | scale; |
170 | 26.5k | } FITSInfo; |
171 | | |
172 | 26.5k | char |
173 | 26.5k | keyword[FITS_ROW_SIZE+1], |
174 | 26.5k | value[FITS_ROW_SIZE+1]; |
175 | | |
176 | 26.5k | FITSInfo |
177 | 26.5k | fits_info; |
178 | | |
179 | 26.5k | Image |
180 | 26.5k | *image; |
181 | | |
182 | 26.5k | int |
183 | 26.5k | c; |
184 | | |
185 | 26.5k | int |
186 | 26.5k | logging; |
187 | | |
188 | 26.5k | long |
189 | 26.5k | packet_size, |
190 | 26.5k | scene, |
191 | 26.5k | y, |
192 | 26.5k | ax_number; |
193 | | |
194 | 26.5k | register PixelPacket |
195 | 26.5k | *q; |
196 | | |
197 | 26.5k | unsigned char |
198 | 26.5k | *fits_pixels; |
199 | | |
200 | 26.5k | unsigned int |
201 | 26.5k | status, |
202 | 26.5k | value_expected; |
203 | | |
204 | 26.5k | magick_uintmax_t |
205 | 26.5k | number_pixels; |
206 | | |
207 | 26.5k | ImportPixelAreaOptions import_options; |
208 | | |
209 | 26.5k | magick_off_t |
210 | 26.5k | file_size, |
211 | 26.5k | remaining; |
212 | | |
213 | | /* |
214 | | Open image file. |
215 | | */ |
216 | 26.5k | assert(image_info != (const ImageInfo *) NULL); |
217 | 26.5k | assert(image_info->signature == MagickSignature); |
218 | 26.5k | assert(exception != (ExceptionInfo *) NULL); |
219 | 26.5k | assert(exception->signature == MagickSignature); |
220 | | |
221 | 26.5k | logging = LogMagickEvent(CoderEvent,GetMagickModule(),"enter"); |
222 | | |
223 | 26.5k | image=AllocateImage(image_info); |
224 | 26.5k | image->rows=0; |
225 | 26.5k | image->columns=0; |
226 | 26.5k | status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); |
227 | 26.5k | if (status == False) |
228 | 26.5k | ThrowReaderException(FileOpenError,UnableToOpenFile,image); |
229 | | |
230 | 26.5k | file_size=GetBlobSize(image); |
231 | | |
232 | | /* |
233 | | Verify file header |
234 | | */ |
235 | 26.5k | if (ReadBlob(image,sizeof(keyword),keyword) != sizeof(keyword)) |
236 | 23.5k | ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image); |
237 | | |
238 | 23.5k | if ((LocaleNCompare(keyword,"IT0",3) != 0) && |
239 | 11.9k | (LocaleNCompare(keyword,"SIMPLE",6) != 0)) |
240 | 23.5k | ThrowReaderException(CorruptImageError,ImproperImageHeader,image); |
241 | | |
242 | | /* |
243 | | Seek back to beginning of file |
244 | | */ |
245 | 23.5k | SeekBlob(image,0,SEEK_SET); |
246 | | |
247 | | /* |
248 | | Initialize common part of image header. |
249 | | */ |
250 | 23.5k | fits_info.extensions_exist=0; |
251 | 23.5k | fits_info.simple=False; |
252 | | |
253 | 23.5k | ImportPixelAreaOptionsInit(&import_options); |
254 | 23.5k | import_options.endian = MSBEndian; |
255 | 23.5k | import_options.sample_type = UnsignedQuantumSampleType; |
256 | | |
257 | | /* |
258 | | Decode image header. |
259 | | */ |
260 | 23.5k | if ((c=ReadBlobByte(image)) == EOF) |
261 | 23.5k | ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image); |
262 | | |
263 | 42.8k | ReadExtension: |
264 | 42.8k | if (logging) (void)LogMagickEvent(CoderEvent,GetMagickModule(), |
265 | 0 | "Reading FITS HDU at position: %Xh", |
266 | 0 | (unsigned)TellBlob(image) ); |
267 | | |
268 | | /* |
269 | | Initialize image header for all subheaders. |
270 | | */ |
271 | 42.8k | fits_info.bits_per_pixel=8; |
272 | 42.8k | fits_info.columns=1; |
273 | 42.8k | fits_info.rows=1; |
274 | 42.8k | fits_info.number_axes=0; |
275 | 42.8k | fits_info.number_scenes=1; |
276 | 42.8k | fits_info.min_data=0.0; |
277 | 42.8k | fits_info.max_data=0.0; |
278 | 42.8k | fits_info.zero=0.0; |
279 | 42.8k | fits_info.scale=1.0; |
280 | 42.8k | number_pixels = 0; |
281 | | |
282 | 42.8k | for ( ; ; ) |
283 | 923k | { |
284 | 923k | if (!isalnum((int) c)) |
285 | 371k | c=ReadBlobByte(image); |
286 | 551k | else |
287 | 551k | { |
288 | 551k | register char |
289 | 551k | *p; |
290 | | |
291 | | /* |
292 | | Determine a keyword and its value. |
293 | | */ |
294 | 551k | p=keyword; |
295 | 551k | do |
296 | 3.48M | { |
297 | 3.48M | if ((p-keyword) < (FITS_ROW_SIZE-1)) |
298 | 3.44M | *p++=c; |
299 | 3.48M | if ((c=ReadBlobByte(image)) == EOF) |
300 | 3.48M | ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image); |
301 | 3.48M | } while (isalnum(c) || (c == '_')); |
302 | 548k | *p='\0'; |
303 | 548k | if (LocaleCompare(keyword,"END") == 0) |
304 | 22.3k | break; |
305 | 526k | value_expected=False; |
306 | 3.17M | while (isspace(c) || (c == '=')) |
307 | 2.65M | { |
308 | 2.65M | if (c == '=') |
309 | 144k | value_expected=True; |
310 | 2.65M | if ((c=ReadBlobByte(image)) == EOF) |
311 | 2.64M | ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image); |
312 | 2.64M | } |
313 | 524k | if (value_expected == False) |
314 | 396k | continue; |
315 | 127k | p=value; |
316 | 127k | if (c == '\'') |
317 | 3.34k | c=ReadBlobByte(image); |
318 | | |
319 | 466k | while (isalnum(c) || (c == '-') || (c == '+') || (c == '.')) |
320 | 340k | { |
321 | 340k | if ((p-value) < (FITS_ROW_SIZE-1)) |
322 | 333k | *p++=c; |
323 | 340k | if ((c=ReadBlobByte(image)) == EOF) |
324 | 339k | ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image); |
325 | 339k | } |
326 | 126k | *p='\0'; |
327 | | /* |
328 | | Assign a value to the specified keyword. |
329 | | */ |
330 | 126k | if (LocaleCompare(keyword,"SIMPLE") == 0) |
331 | 29.0k | fits_info.simple = (*value=='T') || (*value=='t'); |
332 | 126k | if (LocaleCompare(keyword,"EXTEND") == 0) |
333 | 2.49k | fits_info.extensions_exist = (*value== 'T') || (*value=='t'); |
334 | 126k | if (LocaleCompare(keyword,"BITPIX") == 0) |
335 | 5.07k | { |
336 | 5.07k | fits_info.bits_per_pixel=MagickAtoI(value); |
337 | | /* |
338 | | BITPIX valid values: |
339 | | 8 -- Character or unsigned binary integer |
340 | | 16 -- 16-bit two's complement binary integer |
341 | | 32 -- 32-bit two's complement binary integer |
342 | | -32 -- 32-bit floating point, single precision |
343 | | -64 -- 64-bit floating point, double precision |
344 | | */ |
345 | 5.07k | if (fits_info.bits_per_pixel > 0) |
346 | 1.47k | import_options.sample_type = UnsignedQuantumSampleType; |
347 | 5.07k | if (fits_info.bits_per_pixel < 0) |
348 | 3.12k | import_options.sample_type = FloatQuantumSampleType; |
349 | 5.07k | } |
350 | 126k | if (!LocaleNCompare(keyword,"NAXIS",5)) |
351 | 52.7k | { |
352 | 52.7k | if (keyword[5] == 0) ax_number=-1; |
353 | 32.5k | else |
354 | 32.5k | { |
355 | 32.5k | if (isdigit((int) keyword[5])) |
356 | 31.6k | ax_number = MagickAtoI(keyword+5); |
357 | 872 | else ax_number=-2; /*unsupported fits keyword*/ |
358 | 32.5k | } |
359 | 52.7k | y=0; |
360 | 52.7k | if (ax_number >= -1) |
361 | 50.8k | y = MagickAtoI(value); |
362 | 52.7k | switch (ax_number) |
363 | 52.7k | { |
364 | 20.2k | case -1:fits_info.number_axes = y; break; |
365 | 19.6k | case 1: fits_info.columns = (y <= 0) ? 1 : y; break; |
366 | 4.96k | case 2: fits_info.rows = (y <= 0) ? 1 : y; break; |
367 | 2.36k | case 3: fits_info.number_scenes = (y <=0 ) ? 1 : y; break; |
368 | 52.7k | } |
369 | 52.7k | if (ax_number > 0) |
370 | 29.1k | { |
371 | 29.1k | if (number_pixels == 0) |
372 | 17.8k | number_pixels = (magick_uintmax_t) y; |
373 | 11.2k | else |
374 | 11.2k | number_pixels *= (y <= 0) ? 1U : (magick_uintmax_t) y; |
375 | 29.1k | } |
376 | 52.7k | } |
377 | | |
378 | 126k | if (LocaleCompare(keyword,"DATAMAX") == 0) |
379 | 1.00k | fits_info.max_data=MagickAtoF(value); |
380 | 126k | if (LocaleCompare(keyword,"DATAMIN") == 0) |
381 | 894 | fits_info.min_data=MagickAtoF(value); |
382 | 126k | if (LocaleCompare(keyword,"BZERO") == 0) |
383 | 475 | fits_info.zero=MagickAtoF(value); |
384 | 126k | if (LocaleCompare(keyword,"BSCALE") == 0) |
385 | 751 | fits_info.scale=MagickAtoF(value); |
386 | 126k | if (LocaleCompare(keyword,"XENDIAN") == 0) |
387 | 0 | { |
388 | 0 | if (LocaleCompare(keyword,"BIG") == 0) |
389 | 0 | import_options.endian = MSBEndian; |
390 | 0 | else |
391 | 0 | import_options.endian = LSBEndian; |
392 | 0 | } |
393 | 126k | } |
394 | 33.9M | while ((TellBlob(image) % 80) != 0) |
395 | 33.4M | if ((c=ReadBlobByte(image)) == EOF) |
396 | 488k | ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image); |
397 | 488k | if ((c=ReadBlobByte(image)) == EOF) |
398 | 484k | ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image); |
399 | 484k | } |
400 | | |
401 | 33.9M | while ((TellBlob(image) % FITS_BLOCK_SIZE) != 0) /* Read till the rest of a block. */ |
402 | 33.8M | if ((c=ReadBlobByte(image)) == EOF) |
403 | 20.6k | ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image); |
404 | | |
405 | | /* |
406 | | Verify that required image information is defined. |
407 | | */ |
408 | 20.6k | if (logging) |
409 | 0 | { |
410 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
411 | 0 | "HDU read finished at offset %" MAGICK_OFF_F "Xh, " |
412 | 0 | "number of pixels is: %" MAGICK_UINTMAX_F "u (%d*%d*%d)", |
413 | 0 | TellBlob(image), |
414 | 0 | number_pixels, |
415 | 0 | fits_info.columns, |
416 | 0 | fits_info.rows, |
417 | 0 | fits_info.number_scenes); |
418 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
419 | 0 | "FITS header:\n" |
420 | 0 | " extensions_exist : 0x%02x\n" |
421 | 0 | " simple : %d\n" |
422 | 0 | " bits_per_pixel : %d\n" |
423 | 0 | " columns : %d\n" |
424 | 0 | " rows : %d\n" |
425 | 0 | " number_axes : %d\n" |
426 | 0 | " number_scenes : %d\n" |
427 | 0 | " min_data : %g\n" |
428 | 0 | " max_data : %g\n" |
429 | 0 | " zero : %g\n" |
430 | 0 | " scale : %g", |
431 | 0 | (int) fits_info.extensions_exist, |
432 | 0 | fits_info.simple, |
433 | 0 | fits_info.bits_per_pixel, |
434 | 0 | fits_info.columns, |
435 | 0 | fits_info.rows, |
436 | 0 | fits_info.number_axes, |
437 | 0 | fits_info.number_scenes, |
438 | 0 | fits_info.min_data, |
439 | 0 | fits_info.max_data, |
440 | 0 | fits_info.zero, |
441 | 0 | fits_info.scale); |
442 | 0 | } |
443 | | |
444 | 20.6k | if ((fits_info.bits_per_pixel != 8) && |
445 | 2.80k | (fits_info.bits_per_pixel != 16) && |
446 | 2.62k | (fits_info.bits_per_pixel != 32) && |
447 | 2.27k | (fits_info.bits_per_pixel != 64) && |
448 | 1.93k | (fits_info.bits_per_pixel != -32) && |
449 | 1.00k | (fits_info.bits_per_pixel != -64)) |
450 | 20.5k | ThrowReaderException(CorruptImageError,ImproperImageHeader,image); |
451 | | |
452 | 20.5k | if ((fits_info.columns <= 0) || |
453 | 20.5k | (fits_info.rows <= 0) || |
454 | 20.5k | (fits_info.number_axes < 0) || |
455 | 20.5k | (fits_info.number_scenes < 0)) |
456 | 20.5k | ThrowReaderException(CorruptImageError,ImproperImageHeader,image); |
457 | | |
458 | | /* |
459 | | Validate that remaining file size is sufficient for claimed |
460 | | image properties |
461 | | */ |
462 | 20.5k | packet_size=AbsoluteValue(fits_info.bits_per_pixel)/8; |
463 | 20.5k | remaining = file_size-TellBlob(image); |
464 | 20.5k | if (remaining == 0) |
465 | 39 | { |
466 | 39 | ThrowReaderException(CorruptImageError,InsufficientImageDataInFile, |
467 | 39 | image); |
468 | 0 | } |
469 | 20.4k | if (file_size != 0) |
470 | 20.4k | { |
471 | 20.4k | double |
472 | 20.4k | ratio = (((double) number_pixels*packet_size)/remaining); |
473 | | |
474 | 20.4k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
475 | 20.4k | "Remaining: %" MAGICK_OFF_F "d, Ratio: %g", |
476 | 20.4k | remaining, ratio); |
477 | | |
478 | 20.4k | if (ratio > 1.5) |
479 | 22 | { |
480 | 22 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
481 | 22 | "Unreasonable file size " |
482 | 22 | "(ratio of pixels to remaining file size %g)", |
483 | 22 | ratio); |
484 | 22 | ThrowReaderException(CorruptImageError,InsufficientImageDataInFile, |
485 | 22 | image); |
486 | 0 | } |
487 | 20.4k | } |
488 | | |
489 | 20.4k | if ((!fits_info.simple) || (fits_info.number_axes < 1) || |
490 | 12.9k | (fits_info.number_axes > 4) || (number_pixels == 0)) |
491 | 9.24k | { |
492 | 9.24k | if (!fits_info.extensions_exist) /* when extensions exists, process further */ |
493 | 9.20k | ThrowReaderException(CorruptImageError,ImageTypeNotSupported,image); |
494 | | |
495 | 9.20k | number_pixels = (number_pixels*AbsoluteValue(fits_info.bits_per_pixel)) / 8; |
496 | 9.20k | number_pixels = ((number_pixels+FITS_BLOCK_SIZE-1) / |
497 | 9.20k | FITS_BLOCK_SIZE) * FITS_BLOCK_SIZE; /* raw data block size */ |
498 | 9.20k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
499 | 9.20k | "Seek CUR: %" MAGICK_OFF_F "u",(magick_off_t) number_pixels); |
500 | 9.20k | SeekBlob(image,(magick_off_t) number_pixels,SEEK_CUR); |
501 | 9.20k | } |
502 | 11.2k | else |
503 | 11.2k | { |
504 | 11.2k | for (scene=0; scene < (long) fits_info.number_scenes; scene++) |
505 | 11.2k | { |
506 | 11.2k | if (image->rows!=0 && image->columns!=0) |
507 | 10.0k | { |
508 | | /* |
509 | | Allocate next image structure. |
510 | | */ |
511 | 10.0k | AllocateNextImage(image_info,image); |
512 | 10.0k | if (image->next == (Image *) NULL) |
513 | 0 | { |
514 | 0 | DestroyImageList(image); |
515 | 0 | return((Image *) NULL); |
516 | 0 | } |
517 | 10.0k | image=SyncNextImageInList(image); |
518 | 10.0k | if (!MagickMonitorFormatted(TellBlob(image),file_size,exception, |
519 | 10.0k | LoadImagesText,image->filename)) |
520 | 0 | break; |
521 | 10.0k | } |
522 | | |
523 | | /* |
524 | | Create linear colormap. |
525 | | */ |
526 | 11.2k | image->columns = fits_info.columns; |
527 | 11.2k | image->rows = fits_info.rows; |
528 | 11.2k | if (fits_info.bits_per_pixel>=0) |
529 | 10.1k | image->depth = Min(QuantumDepth,fits_info.bits_per_pixel); |
530 | 1.08k | else |
531 | 1.08k | image->depth = QuantumDepth; /* double type cell */ |
532 | | /* image->storage_class=PseudoClass; */ |
533 | 11.2k | image->storage_class = DirectClass; |
534 | 11.2k | image->scene=scene; |
535 | 11.2k | image->is_grayscale = 1; |
536 | | |
537 | 11.2k | if (image->logging) |
538 | 11.2k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
539 | 11.2k | "Frame[%lu] geometry %lux%lu, %u bits/pixel, %s", |
540 | 11.2k | scene, image->columns, image->rows, |
541 | 11.2k | fits_info.bits_per_pixel, |
542 | 11.2k | ClassTypeToString(image->storage_class)); |
543 | | |
544 | 11.2k | if (image->depth<=8 && fits_info.bits_per_pixel==8) |
545 | 9.84k | if (!AllocateImageColormap(image,1 << image->depth)) |
546 | 11.2k | ThrowReaderException(FileOpenError,UnableToOpenFile,image); |
547 | | |
548 | 11.2k | if (image_info->ping && (image_info->subrange != 0)) |
549 | 0 | if (image->scene >= (image_info->subimage+image_info->subrange-1)) |
550 | 0 | break; |
551 | | |
552 | 11.2k | if (CheckImagePixelLimits(image, exception) != MagickPass) |
553 | 11.1k | ThrowReaderException(ResourceLimitError,ImagePixelLimitExceeded,image); |
554 | | |
555 | | /* |
556 | | Initialize image structure. |
557 | | */ |
558 | 11.1k | packet_size=AbsoluteValue(fits_info.bits_per_pixel)/8; |
559 | | |
560 | | /* |
561 | | Validate that remaining file size is sufficient for claimed |
562 | | image properties |
563 | | */ |
564 | 11.1k | remaining = file_size-TellBlob(image); |
565 | 11.1k | if (remaining == 0) |
566 | 0 | { |
567 | 0 | ThrowReaderException(CorruptImageError,InsufficientImageDataInFile, |
568 | 0 | image); |
569 | 0 | } |
570 | 11.1k | if (file_size != 0) |
571 | 11.1k | { |
572 | 11.1k | double |
573 | 11.1k | ratio = (((double) image->columns*image->rows*packet_size)/remaining); |
574 | | |
575 | 11.1k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
576 | 11.1k | "Remaining: %" MAGICK_OFF_F "d, Ratio: %g", |
577 | 11.1k | remaining, ratio); |
578 | | |
579 | 11.1k | if (ratio > 1.5) |
580 | 13 | { |
581 | 13 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
582 | 13 | "Unreasonable file size " |
583 | 13 | "(ratio of pixels to remaining file size %g)", |
584 | 13 | ratio); |
585 | 13 | ThrowReaderException(CorruptImageError,InsufficientImageDataInFile, |
586 | 13 | image); |
587 | 0 | } |
588 | 11.1k | } |
589 | | |
590 | 11.1k | fits_pixels=MagickAllocateResourceLimitedArray(unsigned char *, image->columns, packet_size); |
591 | 11.1k | if (fits_pixels == (unsigned char *) NULL) |
592 | 11.1k | ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image); |
593 | | |
594 | | /* |
595 | | Recover minimum and maximum from data if not present in HDU |
596 | | */ |
597 | 11.1k | if ((fits_info.min_data == 0.0) && (fits_info.max_data == 0.0)) |
598 | 10.9k | { /* Determine minimum and maximum intensity. */ |
599 | 10.9k | if (fits_info.bits_per_pixel==-32) |
600 | 508 | (void) MagickFindRawImageMinMax(image, import_options.endian, image->columns, |
601 | 508 | image->rows, FloatPixel, packet_size*image->columns, |
602 | 508 | fits_pixels, &import_options.double_minvalue, |
603 | 508 | &import_options.double_maxvalue); |
604 | 10.9k | if (fits_info.bits_per_pixel==-64) |
605 | 417 | (void) MagickFindRawImageMinMax(image, import_options.endian, image->columns, |
606 | 417 | image->rows, DoublePixel, packet_size*image->columns, |
607 | 417 | fits_pixels, &import_options.double_minvalue, |
608 | 417 | &import_options.double_maxvalue); |
609 | 10.9k | } |
610 | 213 | else |
611 | 213 | { |
612 | 213 | import_options.double_maxvalue = fits_info.max_data; |
613 | 213 | import_options.double_minvalue = fits_info.min_data; |
614 | 213 | } |
615 | | |
616 | | /* |
617 | | Convert FITS pixels to pixel packets. |
618 | | */ |
619 | | |
620 | 250k | for (y=(long) image->rows-1; y >= 0; y--) |
621 | 246k | { |
622 | 246k | q=SetImagePixels(image,0,y,image->columns,1); |
623 | 246k | if (q == (PixelPacket *) NULL) |
624 | 0 | break; |
625 | | |
626 | 246k | if (ReadBlob(image, (size_t)packet_size*image->columns, fits_pixels) != (size_t)packet_size*image->columns) |
627 | 391 | { |
628 | 391 | if (logging) (void)LogMagickEvent(CoderEvent,GetMagickModule(), |
629 | 0 | " fits cannot read scanrow %u from a file.", (unsigned)y ); |
630 | 391 | break; /* goto ExitLoop; */ |
631 | 391 | } |
632 | | |
633 | 246k | switch(fits_info.bits_per_pixel) |
634 | 246k | { |
635 | 2.29k | case 16: FixSignedValues(fits_pixels, image->columns, 2, import_options.endian); |
636 | 2.29k | break; |
637 | 260 | case 32: FixSignedValues(fits_pixels, image->columns, 4, import_options.endian); |
638 | 260 | break; |
639 | 11.0k | case 64: FixSignedValues(fits_pixels, image->columns, 8, import_options.endian); |
640 | 11.0k | break; |
641 | 246k | } |
642 | | |
643 | 246k | if (ImportImagePixelArea(image, GrayQuantum, packet_size*8, fits_pixels, &import_options,0) == MagickFail) |
644 | 6.84k | { |
645 | 6.84k | if (logging) |
646 | 0 | (void)LogMagickEvent(CoderEvent,GetMagickModule(), |
647 | 0 | " fits failed to ImportImagePixelArea for a row %u", (unsigned)y); |
648 | 6.84k | break; |
649 | 6.84k | } |
650 | | |
651 | 239k | if (!SyncImagePixels(image)) |
652 | 0 | break; |
653 | 239k | if (QuantumTick(y,image->rows)) |
654 | 68.9k | if (!MagickMonitorFormatted(y,image->rows,exception, |
655 | 68.9k | LoadImageText,image->filename, |
656 | 68.9k | image->columns,image->rows)) |
657 | 0 | break; |
658 | 239k | } |
659 | 11.1k | MagickFreeResourceLimitedMemory(fits_pixels); |
660 | 11.1k | if (EOFBlob(image)) |
661 | 391 | { |
662 | 391 | ThrowException(exception,CorruptImageError,UnexpectedEndOfFile, image->filename); |
663 | 391 | break; |
664 | 391 | } |
665 | | |
666 | 10.7k | StopTimer(&image->timer); |
667 | | |
668 | | /* |
669 | | Proceed to next image. |
670 | | */ |
671 | 10.7k | if (image_info->subrange != 0) |
672 | 10.7k | if (image->scene >= (image_info->subimage+image_info->subrange-1)) |
673 | 10.7k | break; |
674 | 10.7k | } |
675 | 11.2k | } |
676 | | |
677 | 20.3k | if (fits_info.extensions_exist) |
678 | 19.8k | { |
679 | 28.9M | while ((TellBlob(image) % FITS_BLOCK_SIZE) != 0) /* Read till the rest of a block. */ |
680 | 28.8M | if ((c=ReadBlobByte(image)) == EOF) |
681 | 406 | break; |
682 | 19.8k | if (!EOFBlob(image)) |
683 | 19.4k | { /* Try to read a next extension block header. */ |
684 | 19.4k | if ((c=ReadBlobByte(image)) != EOF) |
685 | 19.3k | goto ReadExtension; |
686 | 19.4k | } |
687 | 19.8k | } |
688 | | |
689 | 949 | CloseBlob(image); |
690 | | |
691 | | /* |
692 | | Verify frames |
693 | | */ |
694 | 949 | image=GetFirstImageInList(image); |
695 | 949 | do |
696 | 8.28k | { |
697 | 8.28k | if ((image->columns == 0) || (image->rows == 0) || !GetPixelCachePresent(image)) |
698 | 29 | { |
699 | 29 | if (logging) |
700 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
701 | 0 | "Scene %lu: Image columns=%lu, rows=%lu, pixel-cache=%s", |
702 | 0 | image->scene, image->columns, image->rows, |
703 | 0 | GetPixelCachePresent(image) ? "Present" : "Missing!"); |
704 | 29 | ThrowReaderException(CorruptImageError,ImproperImageHeader,image); |
705 | 0 | } |
706 | 8.25k | if (image->next) |
707 | 7.33k | image=image->next; |
708 | 920 | else |
709 | 920 | break; |
710 | 8.25k | } while (1); |
711 | | |
712 | 920 | image=GetFirstImageInList(image); |
713 | | |
714 | 920 | if (logging) (void)LogMagickEvent(CoderEvent,GetMagickModule(),"return"); |
715 | 920 | return(image); |
716 | 949 | } |
717 | | |
718 | | /* |
719 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
720 | | % % |
721 | | % % |
722 | | % % |
723 | | % R e g i s t e r F I T S I m a g e % |
724 | | % % |
725 | | % % |
726 | | % % |
727 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
728 | | % |
729 | | % Method RegisterFITSImage adds attributes for the FITS image format to |
730 | | % the list of supported formats. The attributes include the image format |
731 | | % tag, a method to read and/or write the format, whether the format |
732 | | % supports the saving of more than one frame to the same file or blob, |
733 | | % whether the format supports native in-memory I/O, and a brief |
734 | | % description of the format. |
735 | | % |
736 | | % The format of the RegisterFITSImage method is: |
737 | | % |
738 | | % RegisterFITSImage(void) |
739 | | % |
740 | | */ |
741 | | ModuleExport void RegisterFITSImage(void) |
742 | 5 | { |
743 | 5 | MagickInfo |
744 | 5 | *entry; |
745 | | |
746 | 5 | entry=SetMagickInfo("FITS"); |
747 | 5 | entry->decoder=(DecoderHandler) ReadFITSImage; |
748 | 5 | entry->encoder=(EncoderHandler) WriteFITSImage; |
749 | 5 | entry->magick=(MagickHandler) IsFITS; |
750 | 5 | entry->adjoin=False; |
751 | 5 | entry->seekable_stream=True; |
752 | 5 | entry->description="Flexible Image Transport System"; |
753 | 5 | entry->module="FITS"; |
754 | 5 | (void) RegisterMagickInfo(entry); |
755 | 5 | } |
756 | | |
757 | | /* |
758 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
759 | | % % |
760 | | % % |
761 | | % % |
762 | | % U n r e g i s t e r F I T S I m a g e % |
763 | | % % |
764 | | % % |
765 | | % % |
766 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
767 | | % |
768 | | % Method UnregisterFITSImage removes format registrations made by the |
769 | | % FITS module from the list of supported formats. |
770 | | % |
771 | | % The format of the UnregisterFITSImage method is: |
772 | | % |
773 | | % UnregisterFITSImage(void) |
774 | | % |
775 | | */ |
776 | | ModuleExport void UnregisterFITSImage(void) |
777 | 0 | { |
778 | 0 | (void) UnregisterMagickInfo("FITS"); |
779 | 0 | } |
780 | | |
781 | | |
782 | | /* |
783 | | This functions inserts one row into a HDU. Note that according to |
784 | | FITS spec a card image contains 80 bytes of ASCII data. |
785 | | |
786 | | buffer - 2880 byte logical FITS record (initially memset to 0). |
787 | | data - string data to append at offset |
788 | | offset - offset into FITS record to write the data. |
789 | | */ |
790 | | int InsertRowHDU(char *buffer, const char *data, int offset) |
791 | 25.5k | { |
792 | 25.5k | size_t |
793 | 25.5k | len; |
794 | | |
795 | 25.5k | if (data == NULL) |
796 | 0 | return offset; |
797 | 25.5k | len = strlen(data); |
798 | 25.5k | len = Min(len,80); /* Each card image is 80 bytes max */ |
799 | | |
800 | 25.5k | if (len > (size_t) (((size_t)FITS_BLOCK_SIZE)-offset)) |
801 | 0 | len = ((size_t) FITS_BLOCK_SIZE)-offset; |
802 | | |
803 | 25.5k | (void) memcpy(buffer+offset,data,len); |
804 | 25.5k | return offset +80; |
805 | 25.5k | } |
806 | | |
807 | | /* |
808 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
809 | | % % |
810 | | % % |
811 | | % % |
812 | | % W r i t e F I T S I m a g e % |
813 | | % % |
814 | | % % |
815 | | % % |
816 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
817 | | % |
818 | | % Method WriteFITSImage writes a Flexible Image Transport System image to a |
819 | | % file as gray scale intensities [0..255]. |
820 | | % |
821 | | % The format of the WriteFITSImage method is: |
822 | | % |
823 | | % unsigned int WriteFITSImage(const ImageInfo *image_info,Image *image) |
824 | | % |
825 | | % A description of each parameter follows. |
826 | | % |
827 | | % o status: Method WriteFITSImage return True if the image is written. |
828 | | % False is returned is there is a memory shortage or if the image file |
829 | | % fails to write. |
830 | | % |
831 | | % o image_info: Specifies a pointer to a ImageInfo structure. |
832 | | % |
833 | | % o image: A pointer to an Image structure. |
834 | | % |
835 | | % |
836 | | */ |
837 | | static MagickPassFail WriteFITSImage(const ImageInfo *image_info,Image *image) |
838 | 378 | { |
839 | 378 | char |
840 | 378 | buffer[FITS_BLOCK_SIZE], |
841 | 378 | *fits_info; |
842 | | |
843 | 378 | long |
844 | 378 | y; |
845 | | |
846 | 378 | register const PixelPacket |
847 | 378 | *p; |
848 | | |
849 | 378 | unsigned char |
850 | 378 | *pixels; |
851 | | |
852 | 378 | unsigned int |
853 | 378 | quantum_size; |
854 | | |
855 | 378 | unsigned long |
856 | 378 | packet_size; |
857 | | |
858 | 378 | ExportPixelAreaOptions export_options; |
859 | | |
860 | 378 | MagickPassFail |
861 | 378 | status; |
862 | | |
863 | | /* |
864 | | Open output image file. |
865 | | */ |
866 | 378 | assert(image_info != (const ImageInfo *) NULL); |
867 | 378 | assert(image_info->signature == MagickSignature); |
868 | 378 | assert(image != (Image *) NULL); |
869 | 378 | assert(image->signature == MagickSignature); |
870 | 378 | status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception); |
871 | 378 | if (status == MagickFail) |
872 | 378 | ThrowWriterException(FileOpenError,UnableToOpenFile,image); |
873 | 378 | if (TransformColorspace(image,RGBColorspace) == MagickFail) |
874 | 0 | { |
875 | 0 | CloseBlob(image); |
876 | 0 | return MagickFail; |
877 | 0 | } |
878 | | |
879 | 378 | ExportPixelAreaOptionsInit(&export_options); |
880 | 378 | export_options.endian = MSBEndian; |
881 | 378 | export_options.sample_type = UnsignedQuantumSampleType; |
882 | | |
883 | 378 | do |
884 | 2.54k | { |
885 | 2.54k | if (image->depth <= 8) |
886 | 2.08k | { |
887 | 2.08k | quantum_size=8; |
888 | 2.08k | } |
889 | 458 | else if (image->depth <= 16) |
890 | 458 | { |
891 | 458 | quantum_size=16; |
892 | 458 | } |
893 | 0 | else |
894 | 0 | { |
895 | 0 | quantum_size=32; |
896 | 0 | } |
897 | | |
898 | | /* |
899 | | Allocate image memory. |
900 | | */ |
901 | 2.54k | packet_size=quantum_size/8; |
902 | 2.54k | fits_info=MagickAllocateResourceLimitedMemory(char *,FITS_BLOCK_SIZE); |
903 | 2.54k | if (fits_info == (char *) NULL) |
904 | 0 | { |
905 | 0 | ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image); |
906 | 0 | } |
907 | 2.54k | pixels=MagickAllocateResourceLimitedArray(unsigned char *,packet_size,image->columns); |
908 | 2.54k | if(pixels == (unsigned char *) NULL) |
909 | 0 | { |
910 | 0 | MagickFreeResourceLimitedMemory(fits_info); |
911 | 0 | ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image); |
912 | 0 | } |
913 | | |
914 | | /* |
915 | | Initialize image header. |
916 | | */ |
917 | 2.54k | memset(fits_info,' ',FITS_BLOCK_SIZE); |
918 | 2.54k | y = 0; |
919 | 2.54k | y = InsertRowHDU(fits_info, "SIMPLE = T", y); |
920 | 2.54k | FormatString(buffer, "BITPIX = %u",quantum_size); |
921 | 2.54k | y = InsertRowHDU(fits_info, buffer, y); |
922 | 2.54k | y = InsertRowHDU(fits_info, "NAXIS = 2", y); |
923 | 2.54k | FormatString(buffer, "NAXIS1 = %10lu",image->columns); |
924 | 2.54k | y = InsertRowHDU(fits_info, buffer, y); |
925 | 2.54k | FormatString(buffer, "NAXIS2 = %10lu",image->rows); |
926 | 2.54k | y = InsertRowHDU(fits_info, buffer, y); |
927 | 2.54k | FormatString(buffer, "DATAMIN = %10u",0); |
928 | 2.54k | y = InsertRowHDU(fits_info, buffer, y); |
929 | 2.54k | FormatString(buffer, "DATAMAX = %10lu", MaxValueGivenBits(quantum_size)); |
930 | 2.54k | y = InsertRowHDU(fits_info, buffer, y); |
931 | 2.54k | if (quantum_size>8) |
932 | 458 | { |
933 | 458 | FormatString(buffer, "BZERO = %10u", (quantum_size <= 16) ? 32768U : 2147483648U); |
934 | 458 | y = InsertRowHDU(fits_info, buffer, y); |
935 | 458 | } |
936 | | /* Magick version data can only be 60 bytes. */ |
937 | 2.54k | (void) FormatString(buffer, "HISTORY Created by %.60s.",MagickPackageName " " MagickLibVersionText); |
938 | 2.54k | y = InsertRowHDU(fits_info, buffer, y); |
939 | 2.54k | if(image->next!=NULL) |
940 | 2.16k | y = InsertRowHDU(fits_info, "EXTEND = T", y); |
941 | 2.54k | y = InsertRowHDU(fits_info, "END", y); |
942 | 2.54k | (void) WriteBlob(image, FITS_BLOCK_SIZE, (char *)fits_info); |
943 | | |
944 | | /* |
945 | | Convert image to fits scale PseudoColor class. |
946 | | */ |
947 | 145k | for (y=(long) image->rows-1; y >= 0; y--) |
948 | 142k | { |
949 | 142k | p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception); |
950 | 142k | if (p == (const PixelPacket *) NULL) |
951 | 0 | break; |
952 | 142k | if (ExportImagePixelArea(image,GrayQuantum,quantum_size,pixels,&export_options,0) == MagickFail) |
953 | 0 | break; |
954 | 142k | if (quantum_size == 16) |
955 | 21.5k | FixSignedValues(pixels, image->columns, 2, export_options.endian); |
956 | 142k | if (quantum_size == 32) |
957 | 0 | FixSignedValues(pixels, image->columns, 4, export_options.endian); |
958 | 142k | if (WriteBlob(image, (size_t) packet_size*image->columns,pixels) != (size_t) packet_size*image->columns) |
959 | 0 | break; |
960 | 142k | if (QuantumTick((size_t) image->rows-y-1,image->rows)) |
961 | 38.8k | { |
962 | 38.8k | status=MagickMonitorFormatted((size_t) image->rows-y-1,image->rows, |
963 | 38.8k | &image->exception,SaveImageText, |
964 | 38.8k | image->filename, |
965 | 38.8k | image->columns,image->rows); |
966 | 38.8k | if (status == False) |
967 | 0 | break; |
968 | 38.8k | } |
969 | 142k | } |
970 | | |
971 | | /* Calculate of padding */ |
972 | 2.54k | y = FITS_BLOCK_SIZE - (image->columns * image->rows * packet_size) % FITS_BLOCK_SIZE; |
973 | 2.54k | if(y > 0) |
974 | 2.54k | { |
975 | 2.54k | memset(fits_info, 0, y); |
976 | 2.54k | (void)WriteBlob(image,y,(char *) fits_info); |
977 | 2.54k | } |
978 | 2.54k | MagickFreeResourceLimitedMemory(fits_info); |
979 | 2.54k | MagickFreeResourceLimitedMemory(pixels); |
980 | | |
981 | 2.54k | if(image->next == (Image *) NULL) |
982 | 378 | { |
983 | 378 | if(image->logging) |
984 | 104 | (void)LogMagickEvent(CoderEvent,GetMagickModule(), "No more image frames in list."); |
985 | 378 | break; |
986 | 378 | } |
987 | 2.16k | image = SyncNextImageInList(image); |
988 | 2.16k | } while(1); |
989 | | |
990 | | /* Rewind image list. */ |
991 | 2.54k | while (image->previous != (Image *)NULL) |
992 | 2.16k | image=image->previous; |
993 | | |
994 | 378 | status &= CloseBlob(image); |
995 | 378 | return(status); |
996 | 378 | } |