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 #ifdef HAVE_CONFIG_H
22 #  include <config.h>
23 #endif
24 #include <string.h>
25 
26 #include "core/siril.h"
27 #include "core/OS_utils.h"
28 #include "algos/statistics.h"
29 #include "algos/annotate.h"
30 #include "algos/background_extraction.h"
31 #include "algos/plateSolver.h"
32 #include "gui/image_interactions.h"
33 #include "gui/image_display.h"
34 #include "gui/utils.h"
35 #include "gui/callbacks.h"
36 #include "gui/dialogs.h"
37 #include "gui/message_dialog.h"
38 #include "gui/preferences.h"
39 #include "io/conversion.h"
40 #include "io/sequence.h"
41 #include "io/image_format_fits.h"
42 #include "io/single_image.h"
43 #include "gui/PSF_list.h"
44 #include "gui/histogram.h"
45 #include "gui/progress_and_log.h"
46 #include "core/undo.h"
47 #include "core/processing.h"
48 
49 /* Closes and frees resources attached to the single image opened in gfit.
50  * If a sequence is loaded and one of its images is displayed, nothing is done.
51  */
close_single_image()52 void close_single_image() {
53 	if (sequence_is_loaded() && com.seq.current >= 0)
54 		return;
55 	fprintf(stdout, "MODE: closing single image\n");
56 	/* we need to close all dialogs in order to avoid bugs
57 	 * with previews
58 	 */
59 	if (!com.headless) {
60 		siril_close_preview_dialogs();
61 	}
62 	free_image_data();
63 	undo_flush();
64 }
65 
66 /* frees resources when changing sequence or closing a single image
67  * (image size may vary, so we reallocate) */
free_image_data()68 void free_image_data() {
69 	fprintf(stdout, "free_image_data() called, clearing loaded image\n");
70 	/* WARNING: single_image.fit references the actual fits image,
71 	 * shouldn't it be used here instead of gfit? */
72 	if (!single_image_is_loaded() && sequence_is_loaded())
73 		save_stats_from_fit(&gfit, &com.seq, com.seq.current);
74 	clearfits(&gfit);
75 	invalidate_WCS_keywords(&gfit);
76 	if (!com.headless) {
77 		clear_stars_list();
78 		delete_selected_area();
79 		clear_sampling_setting_box();	// clear focal and pixel pitch info
80 		free_background_sample_list(com.grad_samples);
81 		com.grad_samples = NULL;
82 		g_slist_free_full(com.found_object, (GDestroyNotify)free_object);
83 		com.found_object = NULL;
84 		reset_display_offset();
85 		reset_zoom_default();
86 	}
87 	clear_histograms();
88 
89 	for (int vport = 0; vport < MAXGRAYVPORT; vport++) {
90 		if (com.graybuf[vport]) {
91 			free(com.graybuf[vport]);
92 			com.graybuf[vport] = NULL;
93 		}
94 		if (com.surface[vport]) {
95 			cairo_surface_destroy(com.surface[vport]);
96 			com.surface[vport] = NULL;
97 		}
98 		com.surface_stride[vport] = 0;
99 		com.surface_height[vport] = 0;
100 	}
101 	if (!com.headless)
102 		activate_tab(com.cvport);
103 	if (com.rgbbuf) {
104 		free(com.rgbbuf);
105 		com.rgbbuf = NULL;
106 	}
107 	if (com.uniq) {
108 		free(com.uniq->filename);
109 		com.uniq->fileexist = FALSE;
110 		free(com.uniq->comment);
111 		free(com.uniq->layers);
112 		free(com.uniq);
113 		com.uniq = NULL;
114 	}
115 
116 	if (!com.headless) {
117 		/* free alignment preview data */
118 		for (int i = 0; i < PREVIEW_NB; i++) {
119 			if (com.preview_surface[i]) {
120 				cairo_surface_destroy(com.preview_surface[i]);
121 				com.preview_surface[i] = NULL;
122 			}
123 		}
124 		if (com.refimage_surface) {
125 			cairo_surface_destroy(com.refimage_surface);
126 			com.refimage_surface = NULL;
127 		}
128 	}
129 }
130 
end_read_single_image(gpointer p)131 static gboolean end_read_single_image(gpointer p) {
132 	set_GUI_CAMERA();
133 	set_GUI_photometry();
134 	return FALSE;
135 }
136 
137 /**
138  * Reads an image from disk and stores it in the user allocated destination
139  * fits.
140  * @param filename
141  * @param dest
142  * @param realname_out argument can be NULL, and if not, it is set to the
143  * real file name of the loaded file, since the given filename can be without
144  * extension.
145  * @param is_sequence is set to TRUE if the loaded image is in fact a SER or AVI sequence. Can be NULL
146  * @return
147  */
read_single_image(const char * filename,fits * dest,char ** realname_out,gboolean allow_sequences,gboolean * is_sequence,gboolean allow_dialogs,gboolean force_float)148 int read_single_image(const char *filename, fits *dest, char **realname_out,
149 		gboolean allow_sequences, gboolean *is_sequence, gboolean allow_dialogs,
150 		gboolean force_float) {
151 	int retval;
152 	image_type imagetype;
153 	char *realname = NULL;
154 	gboolean single_sequence = FALSE;
155 
156 	retval = stat_file(filename, &imagetype, &realname);
157 	if (retval) {
158 		char *msg = siril_log_message(_("Error opening image %s: file not found or not supported.\n"), filename);
159 		siril_message_dialog( GTK_MESSAGE_ERROR, _("Error"), msg);
160 		set_cursor_waiting(FALSE);
161 		free(realname);
162 		return 1;
163 	}
164 	if (imagetype == TYPESER || imagetype == TYPEAVI ||
165 				(imagetype == TYPEFITS && fitseq_is_fitseq(realname, NULL))) {
166 		if (allow_sequences) {
167 			retval = read_single_sequence(realname, imagetype);
168 			single_sequence = TRUE;
169 		} else {
170 			siril_log_message(_("Cannot open a sequence from here\n"));
171 			return 1;
172 		}
173 	} else {
174 		retval = any_to_fits(imagetype, realname, dest, allow_dialogs, force_float, com.pref.debayer.open_debayer);
175 		if (!retval)
176 			debayer_if_needed(imagetype, dest, FALSE);
177 	}
178 	if (is_sequence) {
179 		*is_sequence = single_sequence;
180 	}
181 	if (retval && retval != OPEN_IMAGE_CANCEL)
182 		siril_log_message(_("Opening %s failed.\n"), realname);
183 	if (realname_out)
184 		*realname_out = realname;
185 	else
186 		free(realname);
187 	com.filter = (int) imagetype;
188 	siril_add_idle(end_read_single_image, NULL);
189 	return retval;
190 }
191 
end_open_single_image(gpointer arg)192 static gboolean end_open_single_image(gpointer arg) {
193 	open_single_image_from_gfit();
194 	return FALSE;
195 }
196 
197 /* This function is used to load a single image, meaning outside a sequence,
198  * whether a sequence is loaded or not, whether an image was already loaded or
199  * not. The opened file is available in the usual global variable for current
200  * image, gfit.
201  */
open_single_image(const char * filename)202 int open_single_image(const char* filename) {
203 	int retval;
204 	char *realname;
205 	gboolean is_single_sequence;
206 
207 	close_sequence(FALSE);	// closing a sequence if loaded
208 	close_single_image();	// close the previous image and free resources
209 
210 	retval = read_single_image(filename, &gfit, &realname, TRUE, &is_single_sequence, TRUE, FALSE);
211 	if (retval == 2) {
212 		siril_message_dialog(GTK_MESSAGE_ERROR, _("Error opening file"),
213 				_("This file could not be opened because "
214 						"its extension is not supported."));
215 		return 1;
216 	}
217 
218 	if (retval < 0) {
219 		siril_message_dialog(GTK_MESSAGE_ERROR, _("Error opening file"),
220 				_("There was an error when opening this image. "
221 						"See the log for more information."));
222 		return 1;
223 	}
224 
225 	if (!is_single_sequence) {
226 		fprintf(stdout, "Loading image OK, now displaying\n");
227 
228 		/* Now initializing com struct */
229 		com.seq.current = UNRELATED_IMAGE;
230 		com.uniq = calloc(1, sizeof(single));
231 		com.uniq->filename = realname;
232 		com.uniq->fileexist = get_type_from_filename(com.uniq->filename) == TYPEFITS;
233 		com.uniq->nb_layers = gfit.naxes[2];
234 		com.uniq->layers = calloc(com.uniq->nb_layers, sizeof(layer_info));
235 		com.uniq->fit = &gfit;
236 		siril_add_idle(end_open_single_image, realname);
237 	}
238 	return retval;
239 }
240 
241 /* creates a single_image structure and displays a single image, found in gfit.
242  */
open_single_image_from_gfit()243 void open_single_image_from_gfit() {
244 	/* now initializing everything
245 	 * code based on seq_load_image or set_seq (sequence.c) */
246 
247 	initialize_display_mode();
248 
249 	init_layers_hi_and_lo_values(MIPSLOHI);		// If MIPS-LO/HI exist we load these values. If not it is min/max
250 
251 	sliders_mode_set_state(com.sliders);
252 	set_cutoff_sliders_max_values();
253 	set_cutoff_sliders_values();
254 
255 	set_display_mode();
256 	update_prepro_interface(TRUE);
257 	adjust_sellabel();
258 
259 	display_filename();	// display filename in gray window
260 	set_precision_switch(); // set precision on screen
261 
262 	/* update menus */
263 	update_MenuItem();
264 
265 	close_tab();
266 	update_gfit_histogram_if_needed();
267 	redraw(com.cvport, REMAP_ALL);
268 }
269 
270 /* searches the image for minimum and maximum pixel value, on each layer
271  * the values are stored in fit->min[layer] and fit->max[layer] */
image_find_minmax(fits * fit)272 int image_find_minmax(fits *fit) {
273 	int layer;
274 	if (fit->maxi > 0.0)
275 		return 0;
276 	for (layer = 0; layer < fit->naxes[2]; ++layer) {
277 		// calling statistics() saves stats in the fit already, we don't need
278 		// to use the returned handle
279 		free_stats(statistics(NULL, -1, fit, layer, NULL, STATS_MINMAX, TRUE));
280 		if (!fit->stats || !fit->stats[layer])
281 			return -1;
282 		fit->maxi = max(fit->maxi, fit->stats[layer]->max);
283 		fit->mini = min(fit->mini, fit->stats[layer]->min);
284 	}
285 	return 0;
286 }
287 
fit_get_minmax(fits * fit,int layer)288 static int fit_get_minmax(fits *fit, int layer) {
289 	// calling statistics() saves stats in the fit already, we don't need
290 	// to use the returned handle
291 	free_stats(statistics(NULL, -1, fit, layer, NULL, STATS_MINMAX, FALSE));
292 	if (!fit->stats[layer])
293 		return -1;
294 	return 0;
295 }
296 
fit_get_max(fits * fit,int layer)297 double fit_get_max(fits *fit, int layer) {
298 	if (fit_get_minmax(fit, layer))
299 		return -1.0;
300 	return fit->stats[layer]->max;
301 }
302 
fit_get_min(fits * fit,int layer)303 double fit_get_min(fits *fit, int layer) {
304 	if (fit_get_minmax(fit, layer))
305 		return -1.0;
306 	return fit->stats[layer]->min;
307 }
308 
fit_lohi_to_layers(fits * fit,double lo,double hi,layer_info * layer)309 static void fit_lohi_to_layers(fits *fit, double lo, double hi, layer_info *layer) {
310 	if (fit->type == DATA_USHORT) {
311 		layer->lo = (WORD)lo;
312 		layer->hi = (WORD)hi;
313 	}
314 	else if (fit->type == DATA_FLOAT) {
315 		layer->lo = float_to_ushort_range((float)lo);
316 		layer->hi = float_to_ushort_range((float)hi);
317 	}
318 }
319 
320 /* gfit has been loaded, now we copy the hi and lo values into the com.uniq or com.seq layers.
321  * gfit.hi and gfit.lo may only be available in some FITS files; if they are not available, the
322  * min and max value for the layer is used.
323  * If gfit changed, its hi and lo values need to be updated, and they are taken from min and
324  * max.
325  */
init_layers_hi_and_lo_values(sliders_mode force_minmax)326 void init_layers_hi_and_lo_values(sliders_mode force_minmax) {
327 	if (force_minmax == USER) return;
328 	int i, nb_layers;
329 	layer_info *layers=NULL;
330 	static GtkToggleButton *chainedbutton = NULL;
331 	gboolean is_chained;
332 
333 	chainedbutton = GTK_TOGGLE_BUTTON(lookup_widget("checkbutton_chain"));
334 	is_chained = gtk_toggle_button_get_active(chainedbutton);
335 
336 	if (com.uniq && com.uniq->layers && com.seq.current != RESULT_IMAGE) {
337 		nb_layers = com.uniq->nb_layers;
338 		layers = com.uniq->layers;
339 	} else if (sequence_is_loaded() && com.seq.layers) {
340 		nb_layers = com.seq.nb_layers;
341 		layers = com.seq.layers;
342 	} else {
343 		fprintf(stderr, "COULD NOT INIT HI AND LO VALUES\n");
344 		return;
345 	}
346 	for (i=0; i<nb_layers; i++) {
347 		if (gfit.hi == 0 || force_minmax == MINMAX) {
348 			com.sliders = MINMAX;
349 			if (!is_chained) {
350 				fit_lohi_to_layers(&gfit, fit_get_min(&gfit, i),
351 						fit_get_max(&gfit, i), &layers[i]);
352 			}
353 			else {
354 				image_find_minmax(&gfit);
355 				fit_lohi_to_layers(&gfit, gfit.mini, gfit.maxi, &layers[i]);
356 			}
357 		} else {
358 			com.sliders = MIPSLOHI;
359 			layers[i].hi = gfit.hi;
360 			layers[i].lo = gfit.lo;
361 		}
362 	}
363 }
364 
365 /* was level_adjust, to call when gfit changed and need min/max to be recomputed. */
adjust_cutoff_from_updated_gfit()366 void adjust_cutoff_from_updated_gfit() {
367 	invalidate_stats_from_fit(&gfit);
368 	if (!com.script) {
369 		invalidate_gfit_histogram();
370 		compute_histo_for_gfit();
371 		init_layers_hi_and_lo_values(com.sliders);
372 		set_cutoff_sliders_values();
373 	}
374 }
375 
single_image_is_loaded()376 int single_image_is_loaded() {
377 	return (com.uniq != NULL && com.uniq->nb_layers > 0);
378 }
379 
380