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