1 /*
2 * This file is part of Siril, an astronomy image processor.
3 * Copyright (C) 2005-2011 Francois Meyer (dulle at free.fr)
4 * Copyright (C) 2012-2021 team free-astro (see more in AUTHORS file)
5 * Reference site is https://free-astro.org/index.php/Siril
6 *
7 * Siril is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * Siril is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Siril. If not, see <http://www.gnu.org/licenses/>.
19 *
20 *
21 * FITS sequences are not a sequence of FITS files but a FITS file containing a
22 * sequence. It simply has as many elements in the third dimension as the
23 * number of images in the sequence multiplied by the number of channels per
24 * image. Given its use of the third dimension, it's sometimes called FITS cube.
25 */
26
27 #include "core/siril.h"
28
29 #include "io/image_format_fits.h"
30 #include "gui/progress_and_log.h"
31 #include "core/siril_log.h"
32
33 #include "fits_sequence.h"
34
35 static int fitseq_write_image_for_writer(struct seqwriter_data *writer, fits *image, int index);
36 static int fitseq_prepare_for_multiple_read(fitseq *fitseq);
37 static int fitseq_multiple_close(fitseq *fitseq);
38
_find_hdus(fitsfile * fptr,int ** hdus,int * nb_im)39 static int _find_hdus(fitsfile *fptr, int **hdus, int *nb_im) {
40 int status = 0;
41 int nb_hdu, ref_naxis = -1, ref_bitpix = 0, nb_images = 0;
42 long ref_naxes[3] = { 0l };
43
44 fits_get_num_hdus(fptr, &nb_hdu, &status);
45 if (status || !nb_im)
46 return 1;
47
48 if (hdus) {
49 *hdus = malloc(nb_hdu * sizeof(int));
50 if (!*hdus) {
51 PRINT_ALLOC_ERR;
52 return 1;
53 }
54 }
55
56 for (int i = 0; i < nb_hdu; i++) {
57 status = 0;
58 int type;
59 if (fits_movabs_hdu(fptr, i + 1, &type, &status)) {
60 report_fits_error(status);
61 return -1;
62 }
63
64 if (type != IMAGE_HDU) continue;
65
66 long naxes[3];
67 int naxis;
68 int bitpix;
69 fits_get_img_param(fptr, 3, &bitpix, &naxis, naxes, &status);
70 if (status) {
71 report_fits_error(status);
72 break;
73 }
74
75 if (naxis > 0) {
76 if (ref_naxis == -1) {
77 ref_naxis = naxis;
78 ref_bitpix = bitpix;
79 memcpy(ref_naxes, naxes, sizeof naxes);
80 siril_debug_print("found reference HDU %ldx%ldx%d (%d)\n", naxes[0], naxes[1], naxis, bitpix);
81 } else {
82 if (naxis != ref_naxis || naxes[0] != ref_naxes[0] || naxes[1] != ref_naxes[1] || bitpix != ref_bitpix) {
83 fprintf(stderr, "another image was found in the FITS file but does not has the same parameters as the first one\n");
84 break;
85 }
86 }
87 if (hdus)
88 (*hdus)[nb_images] = i + 1;
89 nb_images++;
90 }
91 }
92
93 if (status) {
94 if (hdus) {
95 free(*hdus);
96 *hdus = NULL;
97 }
98 }
99 else {
100 *nb_im = nb_images;
101 siril_debug_print("found %d images with same params in the FITS sequence\n", nb_images);
102 // we could realloc *hdus, but it's not much useful
103 }
104 return status;
105 }
106
107
108 // test if a file is a multi-extension FITS, a.k.a FITS cube or FITS sequence
fitseq_is_fitseq(const char * filename,int * frames)109 int fitseq_is_fitseq(const char *filename, int *frames) {
110 fitsfile *fptr;
111 int status = 0;
112 if (siril_fits_open_diskfile(&fptr, filename, READONLY, &status))
113 return 0;
114
115 int nb_images;
116 status = _find_hdus(fptr, NULL, &nb_images);
117 if (frames) *frames = nb_images;
118
119 int status2 = 0;
120 fits_close_file(fptr, &status2);
121 return !status && nb_images > 1;
122 }
123
fitseq_init_struct(fitseq * fitseq)124 void fitseq_init_struct(fitseq *fitseq) {
125 fitseq->filename = NULL;
126 fitseq->bitpix = 0;
127 fitseq->orig_bitpix = 0;
128 fitseq->naxes[0] = 0;
129 fitseq->frame_count = 0;
130 fitseq->hdu_index = NULL;
131 fitseq->fptr = NULL;
132 fitseq->is_mt_capable = FALSE;
133 fitseq->thread_fptr = NULL;
134 fitseq->num_threads = 0;
135 fitseq->writer = NULL;
136 }
137
fitseq_open(const char * filename,fitseq * fitseq)138 int fitseq_open(const char *filename, fitseq *fitseq) {
139 if (fitseq->fptr) {
140 fprintf(stderr, "FITS sequence: file already opened, or badly closed\n");
141 return -1;
142 }
143
144 int status = 0;
145 siril_fits_open_diskfile(&(fitseq->fptr), filename, READONLY, &status);
146 if (status) {
147 report_fits_error(status);
148 siril_log_color_message(_("Cannot open FITS file %s\n"), "red", filename);
149 return -1;
150 }
151
152 if (_find_hdus(fitseq->fptr, &fitseq->hdu_index, &fitseq->frame_count) || fitseq->frame_count <= 1) {
153 siril_log_color_message(_("Cannot open FITS file %s: doesn't seem to be a FITS sequence\n"), "red", filename);
154 return -1;
155 }
156
157 if (fits_movabs_hdu(fitseq->fptr, fitseq->hdu_index[0], NULL, &status)) {
158 report_fits_error(status);
159 return -1;
160 }
161
162 // we store the first image's dimensions in the struct
163 int naxis;
164 status = 0;
165 fits_get_img_param(fitseq->fptr, 3, &(fitseq->bitpix), &naxis, fitseq->naxes, &status);
166 if (status || naxis <= 1 || fitseq->naxes[0] == 0 || fitseq->naxes[1] == 0) {
167 status = 0;
168 fits_close_file(fitseq->fptr, &status);
169 return -1;
170 }
171 if (naxis == 2)
172 fitseq->naxes[2] = 1;
173
174 manage_bitpix(fitseq->fptr, &(fitseq->bitpix), &(fitseq->orig_bitpix));
175
176 if (fitseq->bitpix == LONGLONG_IMG) {
177 siril_log_message(
178 _("FITS images with 64 bits signed integer per pixel.channel are not supported.\n"));
179 status = 0;
180 fits_close_file(fitseq->fptr, &status);
181 return -1;
182 }
183
184 fitseq->filename = strdup(filename);
185 siril_debug_print("fitseq_open: sequence %s has %d frames, bitpix = %d, naxis = %d, naxes = { %ld, %ld, %ld }\n",
186 filename, fitseq->frame_count, fitseq->bitpix, naxis,
187 fitseq->naxes[0], fitseq->naxes[1], fitseq->naxes[2]);
188
189 if (fits_is_reentrant()) {
190 fitseq->is_mt_capable = TRUE;
191 fprintf(stdout, "cfitsio was compiled with multi-thread support,"
192 " parallel read of images will be possible\n");
193 fitseq_prepare_for_multiple_read(fitseq);
194 } else {
195 fitseq->is_mt_capable = FALSE;
196 fprintf(stdout, "cfitsio was compiled without multi-thread support,"
197 " parallel read of images will be impossible\n");
198 siril_log_message(_("Your version of cfitsio does not support multi-threading\n"));
199 }
200
201 return 0;
202 }
203
204 /* dest must be filled with zeros */
fitseq_read_frame_internal(fitseq * fitseq,int index,fits * dest,gboolean force_float,fitsfile * fptr)205 static int fitseq_read_frame_internal(fitseq *fitseq, int index, fits *dest, gboolean force_float, fitsfile *fptr) {
206 if (!fptr)
207 return -1;
208
209 memcpy(dest->naxes, fitseq->naxes, sizeof fitseq->naxes);
210 dest->naxis = fitseq->naxes[2] == 3 ? 3 : 2;
211 dest->bitpix = fitseq->bitpix;
212 dest->orig_bitpix = fitseq->orig_bitpix;
213 dest->rx = dest->naxes[0];
214 dest->ry = dest->naxes[1];
215 dest->fptr = fptr;
216
217 siril_debug_print("reading HDU %d (of %s)\n", fitseq->hdu_index[index], fitseq->filename);
218 int status = 0;
219 if (fits_movabs_hdu(fptr, fitseq->hdu_index[index], NULL, &status)) {
220 report_fits_error(status);
221 return -1;
222 }
223
224 read_fits_header(dest); // stores useful header data in fit
225 dest->header = copy_header(dest); // for display
226
227 if (read_fits_with_convert(dest, fitseq->filename, force_float)) {
228 return -1;
229 }
230
231 return 0;
232 }
233
fitseq_read_frame(fitseq * fitseq,int index,fits * dest,gboolean force_float,int thread)234 int fitseq_read_frame(fitseq *fitseq, int index, fits *dest, gboolean force_float, int thread) {
235 fitsfile *fptr = fitseq->fptr;
236 if (thread >= 0 && thread < fitseq->num_threads && fitseq->thread_fptr) {
237 fptr = fitseq->thread_fptr[thread];
238 siril_debug_print("fitseq: thread %d reading FITS image\n", thread);
239 }
240 return fitseq_read_frame_internal(fitseq, index, dest, force_float, fptr);
241 }
242
243 // we read a partial image and return it as fits
fitseq_read_partial_fits(fitseq * fitseq,int layer,int index,fits * dest,const rectangle * area,gboolean do_photometry,int thread)244 int fitseq_read_partial_fits(fitseq *fitseq, int layer, int index, fits *dest, const rectangle *area, gboolean do_photometry, int thread) {
245 dest->type = get_data_type(fitseq->bitpix);
246 if (dest->type == DATA_UNSUPPORTED) {
247 siril_log_message(_("Unknown FITS data format in internal conversion\n"));
248 return -1;
249 }
250 if (new_fit_image(&dest, area->w, area->h, 1, dest->type))
251 return -1;
252 fitsfile *fptr = fitseq->fptr;
253 if (thread >= 0 && thread < fitseq->num_threads && fitseq->thread_fptr)
254 fptr = fitseq->thread_fptr[thread];
255 dest->fptr = fptr;
256 dest->bitpix = fitseq->bitpix;
257 dest->orig_bitpix = fitseq->orig_bitpix;
258
259 int status = 0;
260 if (fits_movabs_hdu(fptr, fitseq->hdu_index[index], NULL, &status)) {
261 report_fits_error(status);
262 return -1;
263 }
264
265 if (do_photometry)
266 fit_get_photometry_data(dest);
267
268 status = internal_read_partial_fits(fptr, fitseq->naxes[1], fitseq->bitpix,
269 dest->type == DATA_USHORT ? (void *)dest->data : (void *)dest->fdata,
270 layer, area);
271 return status;
272 }
273
274 // we read a partial image and return it as buffer
fitseq_read_partial(fitseq * fitseq,int layer,int index,void * buffer,const rectangle * area,int thread)275 int fitseq_read_partial(fitseq *fitseq, int layer, int index, void *buffer, const rectangle *area, int thread) {
276 if (area->x < 0 || area->y < 0 || area->x >= fitseq->naxes[0] || area->y >= fitseq->naxes[1]
277 || area->w <= 0 || area->h <= 0 || area->x + area->w > fitseq->naxes[0]
278 || area->y + area->h > fitseq->naxes[1]) {
279 fprintf(stderr, "partial read from FITS file has been requested outside image bounds or with invalid size\n");
280 return 1;
281 }
282
283 fitsfile *fptr = fitseq->fptr;
284 if (thread >= 0 && thread < fitseq->num_threads && fitseq->thread_fptr)
285 fptr = fitseq->thread_fptr[thread];
286
287 int status = 0;
288 if (fits_movabs_hdu(fptr, fitseq->hdu_index[index], NULL, &status)) {
289 report_fits_error(status);
290 return -1;
291 }
292
293 if (internal_read_partial_fits(fptr, fitseq->naxes[1], fitseq->bitpix, buffer, layer, area))
294 return 1;
295 flip_buffer(fitseq->bitpix, buffer, area);
296 return 0;
297 }
298
299 /* create a fits sequence with the given name into the given struct */
fitseq_create_file(const char * filename,fitseq * fitseq,int frame_count)300 int fitseq_create_file(const char *filename, fitseq *fitseq, int frame_count) {
301 g_unlink(filename); /* Delete old file if it already exists */
302 fitseq_init_struct(fitseq);
303
304 int status = 0;
305 if (siril_fits_create_diskfile(&fitseq->fptr, filename, &status)) { /* create new FITS file */
306 report_fits_error(status);
307 return 1;
308 }
309
310 fitseq->filename = strdup(filename);
311 fitseq->frame_count = frame_count;
312 fitseq->writer = malloc(sizeof(struct seqwriter_data));
313 fitseq->writer->write_image_hook = fitseq_write_image_for_writer;
314 fitseq->writer->sequence = fitseq;
315 siril_debug_print("Successfully created the FITS sequence file %s, for %d images, waiting for data\n",
316 fitseq->filename, fitseq->frame_count);
317
318 start_writer(fitseq->writer, frame_count);
319 return 0;
320 }
321
fitseq_write_image_for_writer(struct seqwriter_data * writer,fits * image,int index)322 static int fitseq_write_image_for_writer(struct seqwriter_data *writer, fits *image, int index) {
323 fitseq *fitseq = (struct fits_sequence *)writer->sequence;
324 int status = 0;
325 if (fits_create_img(fitseq->fptr, image->bitpix,
326 image->naxis, image->naxes, &status)) {
327 report_fits_error(status);
328 return 1;
329 }
330
331 image->fptr = fitseq->fptr;
332
333 if (com.pref.comp.fits_enabled) {
334 status = siril_fits_compress(image);
335 if (status) {
336 report_fits_error(status);
337 return 1;
338 }
339 }
340
341 return save_opened_fits(image); // warning: will change HDU
342 }
343
344 /* expected images (if a frame count is given on creation) MUST be notified in
345 * all cases, even with a NULL image if there is in fact no image to write for
346 * the index
347 */
fitseq_write_image(fitseq * fitseq,fits * image,int index)348 int fitseq_write_image(fitseq *fitseq, fits *image, int index) {
349 if (!fitseq->fptr) {
350 siril_log_color_message(_("Cannot save image in sequence not opened for writing\n"), "red");
351 return 1;
352 }
353 siril_debug_print("FITS sequence %s pending image save %d\n", fitseq->filename, index);
354 return seqwriter_append_write(fitseq->writer, image, index);
355 }
356
fitseq_destroy(fitseq * fitseq,gboolean abort)357 static int fitseq_destroy(fitseq *fitseq, gboolean abort) {
358 int retval = 0;
359 if (fitseq->writer) {
360 retval = stop_writer(fitseq->writer, abort);
361 free(fitseq->writer);
362 fitseq->writer = NULL;
363 }
364 retval |= fitseq_multiple_close(fitseq);
365 int status = 0;
366 fits_close_file(fitseq->fptr, &status);
367 if (fitseq->filename)
368 free(fitseq->filename);
369 return retval;
370 }
371
fitseq_close_and_delete_file(fitseq * fitseq)372 void fitseq_close_and_delete_file(fitseq *fitseq) {
373 char *filename = fitseq->filename;
374 fitseq->filename = NULL;
375 fitseq_destroy(fitseq, TRUE);
376 siril_log_message(_("Removing failed FITS sequence file: %s\n"), filename);
377 g_unlink(filename);
378 }
379
fitseq_close_file(fitseq * fitseq)380 int fitseq_close_file(fitseq *fitseq) {
381 return fitseq_destroy(fitseq, FALSE);
382 }
383
384 // to call after open to read with several threads in the file
fitseq_prepare_for_multiple_read(fitseq * fitseq)385 static int fitseq_prepare_for_multiple_read(fitseq *fitseq) {
386 if (fitseq->thread_fptr) return 0;
387 if (!fitseq->is_mt_capable) return 0;
388 fitseq->num_threads = g_get_num_processors();
389 fitseq->thread_fptr = malloc(fitseq->num_threads * sizeof(fitsfile *));
390 for (guint i = 0; i < fitseq->num_threads; i++) {
391 int status = 0;
392 if (siril_fits_open_diskfile(&fitseq->thread_fptr[i], fitseq->filename, READONLY, &status)) {
393 report_fits_error(status);
394 return -1;
395 }
396 }
397 siril_debug_print("initialized FITS sequence fd for %d threads reading\n", fitseq->num_threads);
398 return 0;
399 }
400
fitseq_multiple_close(fitseq * fitseq)401 static int fitseq_multiple_close(fitseq *fitseq) {
402 int retval = 0;
403 if (!fitseq->thread_fptr) return 0;
404 for (guint i = 0; i < fitseq->num_threads; i++) {
405 int status = 0;
406 fits_close_file(fitseq->thread_fptr[i], &status);
407 if (status)
408 retval = 1;
409 }
410 free(fitseq->thread_fptr);
411 fitseq->thread_fptr = NULL;
412 siril_debug_print("closing FITS sequence fd for %d threads\n", fitseq->num_threads);
413 return retval;
414 }
415
fitseq_set_current_frame(fitseq * fitseq,int frame)416 int fitseq_set_current_frame(fitseq *fitseq, int frame) {
417 if (frame < 0 || frame >= fitseq->frame_count)
418 return -1;
419 siril_debug_print("moving to HDU %d (of %s)\n", fitseq->hdu_index[frame], fitseq->filename);
420 int status = 0;
421 if (fits_movabs_hdu(fitseq->fptr, fitseq->hdu_index[frame], NULL, &status))
422 report_fits_error(status);
423 return status;
424 }
425
426