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