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 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <complex.h>
25 #include <fftw3.h>
26 #include <assert.h>
27 #include <float.h>
28 #include <math.h>
29 #include <gtk/gtk.h>
30 
31 #include "core/siril.h"
32 #include "core/proto.h"
33 #include "gui/callbacks.h"
34 #include "gui/utils.h"
35 #include "gui/image_display.h"
36 #include "gui/image_interactions.h"
37 #include "gui/message_dialog.h"
38 #include "gui/plot.h"
39 #include "gui/progress_and_log.h"
40 #include "gui/sequence_list.h"
41 #include "core/initfile.h"
42 #include "core/OS_utils.h"
43 #include "registration/registration.h"
44 #include "registration/matching/misc.h"
45 #include "registration/matching/match.h"
46 #include "registration/matching/atpmatch.h"
47 #include "stacking/stacking.h"
48 #include "algos/PSF.h"
49 #include "gui/PSF_list.h"
50 #include "algos/quality.h"
51 #include "io/sequence.h"
52 #include "io/ser.h"
53 #include "io/single_image.h"
54 #include "io/image_format_fits.h"
55 #include "opencv/opencv.h"
56 #include "opencv/ecc/ecc.h"
57 
58 #undef DEBUG
59 
60 static char *tooltip_text[] = { N_("<b>One Star Registration</b>: This is the simplest method to register deep-sky images. "
61 		"Because only one star is concerned for register, images are aligned using shifting "
62 		"(at a fraction of pixel). No rotation or scaling are performed. "
63 		"Shifts at pixel precision are saved in seq file."),
64 		N_("<b>Two or Three Stars Registration</b>: This method looks like the one star registration except one need to select "
65 		"two or three stars. This is very useful for field with a few stars."),
66 		N_("<b>Global Star Alignment</b>: This is a more powerful and accurate algorithm (but also slower) "
67 		"to perform deep-sky images. The global matching is based on triangle similarity method for automatically "
68 		"identify common stars in each image. "
69 		"A new sequence is created with the prefix of your choice (r_ by default)."),
70 		N_("<b>Image Pattern Alignment</b>: This is a simple registration by translation method "
71 		"using cross correlation in the spatial domain. This method is fast and is used to register "
72 		"planetary movies. It can also be used for some deep-sky images registration. "
73 		"Shifts at pixel precision are saved in seq file."),
74 		N_("<b>Enhanced Correlation Coefficient Maximization</b>: It is based on the enhanced correlation "
75 		"coefficient maximization algorithm. This method is more complex and slower than Image Pattern Alignment "
76 		"but no selection is required. It is good for moon surface images registration. Only translation is taken "
77 		"into account yet."),
78 		N_("<b>Comet/Asteroid Registration</b>: This algorithm is dedicated to the comet and asteroid registration. It is necessary to have timestamps "
79 		"stored in FITS header and to load a sequence of star aligned images. This methods makes a translation of a certain number of pixels depending on "
80 		"the timestamp of each images and the global shift of the object between the first and the last image.")
81 };
82 /* callback for the selected area event */
_reg_selected_area_callback()83 void _reg_selected_area_callback() {
84 	if (!com.headless)
85 		update_reg_interface(TRUE);
86 }
87 
88 static struct registration_method *reg_methods[NUMBER_OF_METHODS];
89 static gboolean end_register_idle(gpointer p);
90 
new_reg_method(const char * name,registration_function f,selection_type s,registration_type t)91 struct registration_method *new_reg_method(const char *name, registration_function f,
92 		selection_type s, registration_type t) {
93 	struct registration_method *reg = malloc(sizeof(struct registration_method));
94 	reg->name = strdup(name);
95 	reg->method_ptr = f;
96 	reg->sel = s;
97 	reg->type = t;
98 	return reg;
99 }
100 
initialize_registration_methods()101 void initialize_registration_methods() {
102 	GtkComboBoxText *regcombo;
103 	int i = 0, j = 0;
104 	GString *tip;
105 	gchar *ctip;
106 
107 	reg_methods[i++] = new_reg_method(_("One Star Registration (deep-sky)"),
108 			&register_shift_fwhm, REQUIRES_ANY_SELECTION, REGTYPE_DEEPSKY);
109 	reg_methods[i++] = new_reg_method(_("Two or Three Stars Registration (deep-sky)"),
110 			&register_3stars, REQUIRES_NO_SELECTION, REGTYPE_DEEPSKY);
111 	reg_methods[i++] = new_reg_method(_("Global Star Alignment (deep-sky)"),
112 			&register_star_alignment, REQUIRES_NO_SELECTION, REGTYPE_DEEPSKY);
113 	reg_methods[i++] = new_reg_method(_("Image Pattern Alignment (planetary - full disk)"),
114 			&register_shift_dft, REQUIRES_SQUARED_SELECTION, REGTYPE_PLANETARY);
115 	reg_methods[i++] = new_reg_method(_("Enhanced Correlation Coefficient (planetary - surfaces)"),
116 			&register_ecc, REQUIRES_NO_SELECTION, REGTYPE_PLANETARY);
117 	reg_methods[i++] = new_reg_method(_("Comet/Asteroid Registration"),
118 			&register_comet, REQUIRES_NO_SELECTION, REGTYPE_DEEPSKY);
119 	reg_methods[i] = NULL;
120 
121 	tip = g_string_new ("");
122 	for (j = 0; j < i; j ++) {
123 		tip = g_string_append(tip, _(tooltip_text[j]));
124 		if (j < i - 1)
125 			tip = g_string_append(tip, "\n\n");
126 	}
127 	ctip = g_string_free (tip, FALSE);
128 	gtk_widget_set_tooltip_markup(lookup_widget("comboboxregmethod"), ctip);
129 	g_free(ctip);
130 
131 	/* fill comboboxregmethod */
132 	regcombo = GTK_COMBO_BOX_TEXT(
133 			gtk_builder_get_object(builder, "comboboxregmethod"));
134 	gtk_combo_box_text_remove_all(regcombo);
135 	i = 0;
136 	while (reg_methods[i] != NULL) {
137 		gtk_combo_box_text_append_text(regcombo, reg_methods[i]->name);
138 		siril_log_message(_("Loading registration method: %s\n"),
139 				reg_methods[i]->name);
140 		i++;
141 	}
142 	if (i > 0) {
143 		gtk_combo_box_set_active(GTK_COMBO_BOX(regcombo), com.reg_settings);
144 	}
145 
146 	/* register to the new area selected event */
147 	register_selection_update_callback(_reg_selected_area_callback);
148 }
149 
get_selected_registration_method()150 struct registration_method *get_selected_registration_method() {
151 	GtkComboBoxText *regcombo = GTK_COMBO_BOX_TEXT(
152 			gtk_builder_get_object(builder, "comboboxregmethod"));
153 	int index = 0;
154 
155 	gchar *text = gtk_combo_box_text_get_active_text (regcombo);
156 	while (reg_methods[index] && text != NULL) {
157 		if (!strcmp(reg_methods[index]->name, text))
158 			break;
159 		index++;
160 	}
161 	g_free(text);
162 	return reg_methods[index];
163 }
164 
normalizeQualityData(struct registration_args * args,double q_min,double q_max)165 static void normalizeQualityData(struct registration_args *args, double q_min, double q_max) {
166 	int frame;
167 	double diff = q_max - q_min;
168 
169 	/* this case occurs when all images but one are excluded */
170 	if (diff == 0) {
171 		q_min = 0;
172 		diff = q_max;
173 	}
174 
175 	for (frame = 0; frame < args->seq->number; ++frame) {
176 		args->seq->regparam[args->layer][frame].quality -= q_min;
177 		args->seq->regparam[args->layer][frame].quality /= diff;
178 		/* if thread has been manually stopped, some values will be < 0 */
179 		if ((args->seq->regparam[args->layer][frame].quality < 0)
180 				|| isnan(args->seq->regparam[args->layer][frame].quality))
181 			args->seq->regparam[args->layer][frame].quality = -1.0;
182 	}
183 }
184 
185 /* Calculate shift in images to be aligned with the reference image, using
186  * discrete Fourrier transform on a square selected area and matching the
187  * phases.
188  */
register_shift_dft(struct registration_args * args)189 int register_shift_dft(struct registration_args *args) {
190 	fits fit_ref = { 0 };
191 	unsigned int frame, size, sqsize, j;
192 	fftwf_complex *ref, *in, *out, *convol;
193 	fftwf_plan p, q;
194 	int ret;
195 	int abort = 0;
196 	float nb_frames, cur_nb;
197 	int ref_image;
198 	regdata *current_regdata;
199 	double q_max = 0, q_min = DBL_MAX;
200 	int q_index = -1;
201 
202 	/* the selection needs to be squared for the DFT */
203 	assert(args->selection.w == args->selection.h);
204 	size = args->selection.w;
205 	sqsize = size * size;
206 
207 	if (args->process_all_frames)
208 		nb_frames = (float) args->seq->number;
209 	else
210 		nb_frames = (float) args->seq->selnum;
211 
212 	if (args->seq->regparam[args->layer]) {
213 		siril_log_message(
214 				_("Recomputing already existing registration for this layer\n"));
215 		current_regdata = args->seq->regparam[args->layer];
216 		/* we reset all values as we may register different images */
217 		memset(current_regdata, 0, args->seq->number * sizeof(regdata));
218 	} else {
219 		current_regdata = calloc(args->seq->number, sizeof(regdata));
220 		if (current_regdata == NULL) {
221 			PRINT_ALLOC_ERR;
222 			return -2;
223 		}
224 		args->seq->regparam[args->layer] = current_regdata;
225 	}
226 
227 	/* loading reference frame */
228 	ref_image = sequence_find_refimage(args->seq);
229 
230 	set_progress_bar_data(
231 			_("Register DFT: loading and processing reference frame"),
232 			PROGRESS_NONE);
233 	ret = seq_read_frame_part(args->seq, args->layer, ref_image, &fit_ref,
234 			&args->selection, FALSE, -1);
235 
236 	if (ret) {
237 		siril_log_message(
238 				_("Register: could not load first image to register, aborting.\n"));
239 		args->seq->regparam[args->layer] = NULL;
240 		free(current_regdata);
241 		clearfits(&fit_ref);
242 		return ret;
243 	}
244 
245 	ref = fftwf_malloc(sizeof(fftwf_complex) * sqsize);
246 	in = fftwf_malloc(sizeof(fftwf_complex) * sqsize);
247 	out = fftwf_malloc(sizeof(fftwf_complex) * sqsize);
248 	convol = fftwf_malloc(sizeof(fftwf_complex) * sqsize);
249 
250 	gchar* wisdomFile = g_build_filename(g_get_user_cache_dir(), "siril_fftw.wisdom", NULL);
251 
252 	// test for available wisdom
253 	p = fftwf_plan_dft_2d(size, size, ref, out, FFTW_FORWARD, FFTW_WISDOM_ONLY);
254 	if (!p) {
255 		// no wisdom available, load wisdom from file
256 		fftwf_import_wisdom_from_filename(wisdomFile);
257 		// test again for wisdom
258 		p = fftwf_plan_dft_2d(size, size, ref, out, FFTW_FORWARD, FFTW_WISDOM_ONLY);
259 		if (!p) {
260 			// build plan with FFTW_MEASURE
261 			p = fftwf_plan_dft_2d(size, size, ref, out, FFTW_FORWARD, FFTW_MEASURE);
262 			// save the wisdom
263 			fftwf_export_wisdom_to_filename(wisdomFile);
264 		}
265 	}
266 
267 	q = fftwf_plan_dft_2d(size, size, convol, out, FFTW_BACKWARD, FFTW_WISDOM_ONLY | FFTW_DESTROY_INPUT);
268 	if (!q) {
269 		// no wisdom available, load wisdom from file
270 		fftwf_import_wisdom_from_filename(wisdomFile);
271 		// test again for wisdom
272 		q = fftwf_plan_dft_2d(size, size, convol, out, FFTW_BACKWARD, FFTW_WISDOM_ONLY | FFTW_DESTROY_INPUT);
273 		if (!q) {
274 			// build plan with FFTW_MEASURE
275 			q = fftwf_plan_dft_2d(size, size, convol, out, FFTW_BACKWARD, FFTW_MEASURE | FFTW_DESTROY_INPUT);
276 			// save the wisdom
277 			fftwf_export_wisdom_to_filename(wisdomFile);
278 		}
279 	}
280 	g_free(wisdomFile);
281 	fftwf_free(out); // was needed to build the plan, can be freed now
282 	fftwf_free(convol); // was needed to build the plan, can be freed now
283 
284 	// copying image selection into the fftw data
285 	if (fit_ref.type == DATA_USHORT) {
286 		for (j = 0; j < sqsize; j++)
287 			ref[j] = (float)fit_ref.data[j];
288 	}
289 	else if (fit_ref.type == DATA_FLOAT) {
290 		for (j = 0; j < sqsize; j++)
291 			ref[j] = fit_ref.fdata[j];
292 	}
293 
294 	// We don't need fit_ref anymore, we can destroy it.
295 	current_regdata[ref_image].quality = QualityEstimate(&fit_ref, args->layer);
296 	clearfits(&fit_ref);
297 	fftwf_execute_dft(p, ref, in); /* repeat as needed */
298 
299 	fftwf_free(ref); // not needed anymore
300 
301 	set_shifts(args->seq, ref_image, args->layer, 0.0, 0.0, FALSE);
302 
303 	q_min = q_max = current_regdata[ref_image].quality;
304 	q_index = ref_image;
305 
306 	cur_nb = 0.f;
307 
308 #ifdef _OPENMP
309 #pragma omp parallel for num_threads(com.max_thread) schedule(guided) \
310 	if (args->seq->type == SEQ_SER || ((args->seq->type == SEQ_REGULAR || args->seq->type == SEQ_FITSEQ) && fits_is_reentrant()))
311 #endif
312 	for (frame = 0; frame < args->seq->number; ++frame) {
313 		if (abort) continue;
314 		if (args->run_in_thread && !get_thread_run()) {
315 			abort = 1;
316 			continue;
317 		}
318 		if (frame == ref_image) continue;
319 		if (!args->process_all_frames && !args->seq->imgparam[frame].incl)
320 			continue;
321 
322 		fits fit = { 0 };
323 		char tmpmsg[1024], tmpfilename[256];
324 
325 		seq_get_image_filename(args->seq, frame, tmpfilename);
326 		g_snprintf(tmpmsg, 1024, _("Register: processing image %s"), tmpfilename);
327 		set_progress_bar_data(tmpmsg, PROGRESS_NONE);
328 		int thread_id = -1;
329 #ifdef _OPENMP
330 		thread_id = omp_get_thread_num();
331 #endif
332 		if (!seq_read_frame_part(args->seq, args->layer, frame, &fit,
333 						&args->selection, FALSE, thread_id)) {
334 			int x;
335 			fftwf_complex *img = fftwf_malloc(sizeof(fftwf_complex) * sqsize);
336 			fftwf_complex *out2 = fftwf_malloc(sizeof(fftwf_complex) * sqsize);
337 
338 			// copying image selection into the fftw data
339 			if (fit.type == DATA_USHORT) {
340 				for (x = 0; x < sqsize; x++)
341 					img[x] = (float)fit.data[x];
342 			}
343 			if (fit.type == DATA_FLOAT) {
344 				for (x = 0; x < sqsize; x++)
345 					img[x] = fit.fdata[x];
346 			}
347 
348 			current_regdata[frame].quality = QualityEstimate(&fit, args->layer);
349 			// after this call, fit data is dead
350 
351 #ifdef _OPENMP
352 #pragma omp critical
353 #endif
354 			{
355 				double qual = current_regdata[frame].quality;
356 				if (qual > q_max) {
357 					q_max = qual;
358 					q_index = frame;
359 				}
360 				q_min = min(q_min, qual);
361 			}
362 
363 			fftwf_execute_dft(p, img, out2); /* repeat as needed */
364 
365 			fftwf_complex *convol2 = img; // reuse buffer
366 
367 			for (x = 0; x < sqsize; x++) {
368 				convol2[x] = in[x] * conjf(out2[x]);
369 			}
370 
371 			fftwf_execute_dft(q, convol2, out2); /* repeat as needed */
372 
373 			int shift = 0;
374 			for (x = 1; x < sqsize; ++x) {
375 				if (crealf(out2[x]) > crealf(out2[shift])) {
376 					shift = x;
377 					// break or get last value?
378 				}
379 			}
380 			int shifty = shift / size;
381 			int shiftx = shift % size;
382 			if (shifty > size / 2) {
383 				shifty -= size;
384 			}
385 			if (shiftx > size / 2) {
386 				shiftx -= size;
387 			}
388 
389 			set_shifts(args->seq, frame, args->layer, (float)shiftx, (float)shifty,
390 					fit.top_down);
391 
392 			// We don't need fit anymore, we can destroy it.
393 			clearfits(&fit);
394 
395 			/* shiftx and shifty are the x and y values for translation that
396 			 * would make this image aligned with the reference image.
397 			 * WARNING: the y value is counted backwards, since the FITS is
398 			 * stored down from up.
399 			 */
400 #ifdef DEBUG
401 			fprintf(stderr,
402 					"reg: frame %d, shiftx=%f shifty=%f quality=%g\n",
403 					args->seq->imgparam[frame].filenum,
404 					current_regdata[frame].shiftx, current_regdata[frame].shifty,
405 					current_regdata[frame].quality);
406 #endif
407 #ifdef _OPENMP
408 #pragma omp atomic
409 #endif
410 			cur_nb += 1.f;
411 			set_progress_bar_data(NULL, cur_nb / nb_frames);
412 			fftwf_free(img);
413 			fftwf_free(out2);
414 		} else {
415 			//report_fits_error(ret, error_buffer);
416 			args->seq->regparam[args->layer] = NULL;
417 			free(current_regdata);
418 			abort = ret = 1;
419 			continue;
420 		}
421 	}
422 
423 	fftwf_destroy_plan(p);
424 	fftwf_destroy_plan(q);
425 	fftwf_free(in);
426 	if (!ret) {
427 		if (args->x2upscale)
428 			args->seq->upscale_at_stacking = 2.0;
429 		else
430 			args->seq->upscale_at_stacking = 1.0;
431 		normalizeQualityData(args, q_min, q_max);
432 
433 		siril_log_message(_("Registration finished.\n"));
434 		siril_log_color_message(_("Best frame: #%d.\n"), "bold", q_index + 1);
435 	} else {
436 		free(args->seq->regparam[args->layer]);
437 		args->seq->regparam[args->layer] = NULL;
438 	}
439 	return ret;
440 }
441 
442 /* register images: calculate shift in images to be aligned with the reference image;
443  * images are not modified, only shift parameters are saved in regparam in the sequence.
444  * layer is the layer on which the registration will be done, green by default (set in siril_init())
445  */
register_shift_fwhm(struct registration_args * args)446 int register_shift_fwhm(struct registration_args *args) {
447 	int frame, ref_image;
448 	float nb_frames, cur_nb = 0.f;
449 	double reference_xpos, reference_ypos;
450 	double fwhm_min = DBL_MAX;
451 	int fwhm_index = -1;
452 	regdata *current_regdata;
453 
454 	framing_mode framing = ORIGINAL_FRAME;
455 	if (args->follow_star)
456 		framing = FOLLOW_STAR_FRAME;
457 
458 	/* First and longest step: get the minimization data on one star for all
459 	 * images to register, which provides FWHM but also star coordinates */
460 	// TODO: detect that it was already computed, and don't do it again
461 	// -> should be done at a higher level and passed in the args
462 	if (seqpsf(args->seq, args->layer, TRUE, args->process_all_frames, framing, FALSE))
463 		return 1;
464 
465 	// regparam is managed in seqpsf idle function already
466 	current_regdata = args->seq->regparam[args->layer];
467 
468 	if (args->process_all_frames)
469 		nb_frames = (float)args->seq->number;
470 	else nb_frames = (float)args->seq->selnum;
471 
472 	/* loading reference frame */
473 	ref_image = sequence_find_refimage(args->seq);
474 	if (!current_regdata[ref_image].fwhm_data) {
475 		siril_log_message(
476 				_("Registration PSF: failed to compute PSF for reference frame at least\n"));
477 		return -1;
478 	}
479 	reference_xpos = current_regdata[ref_image].fwhm_data->xpos;
480 	reference_ypos = current_regdata[ref_image].fwhm_data->ypos;
481 
482 	fwhm_min = current_regdata[ref_image].fwhm_data->fwhmx;
483 
484 	fwhm_index = ref_image;
485 
486 	/* Second step: align image by aligning star coordinates together */
487 	for (frame = 0; frame < args->seq->number; frame++) {
488 		double tmp;
489 		if (args->run_in_thread && !get_thread_run())
490 			break;
491 		if (!args->process_all_frames && !args->seq->imgparam[frame].incl)
492 			continue;
493 		if (frame == ref_image || !current_regdata[frame].fwhm_data) {
494 			set_shifts(args->seq, frame, args->layer, 0.0, 0.0, FALSE);
495 			continue;
496 		}
497 		if (current_regdata[frame].fwhm < fwhm_min
498 				&& current_regdata[frame].fwhm > 0.0) {
499 			fwhm_min = current_regdata[frame].fwhm;
500 			fwhm_index = frame;
501 		}
502 		tmp = reference_xpos - current_regdata[frame].fwhm_data->xpos;
503 		current_regdata[frame].shiftx = tmp;
504 		tmp = current_regdata[frame].fwhm_data->ypos - reference_ypos;
505 		current_regdata[frame].shifty = tmp;
506 
507 		fprintf(stderr, "reg: file %d, shiftx=%f shifty=%f\n",
508 				args->seq->imgparam[frame].filenum,
509 				current_regdata[frame].shiftx, current_regdata[frame].shifty);
510 		cur_nb += 1.f;
511 		set_progress_bar_data(NULL, cur_nb / nb_frames);
512 	}
513 
514 	if (args->x2upscale)
515 		args->seq->upscale_at_stacking = 2.0;
516 	else
517 		args->seq->upscale_at_stacking = 1.0;
518 
519 	siril_log_message(_("Registration finished.\n"));
520 	siril_log_color_message(_("Best frame: #%d with fwhm=%.3g.\n"), "bold",
521 			fwhm_index + 1, fwhm_min);
522 	return 0;
523 }
524 
register_ecc(struct registration_args * args)525 int register_ecc(struct registration_args *args) {
526 	int frame, ref_image, failed = 0;
527 	float nb_frames, cur_nb;
528 	regdata *current_regdata;
529 	fits ref = { 0 };
530 	double q_max = 0, q_min = DBL_MAX;
531 	int q_index = -1;
532 	int abort = 0;
533 
534 	if (args->seq->regparam[args->layer]) {
535 		current_regdata = args->seq->regparam[args->layer];
536 		/* we reset all values as we may register different images */
537 		memset(current_regdata, 0, args->seq->number * sizeof(regdata));
538 	} else {
539 		current_regdata = calloc(args->seq->number, sizeof(regdata));
540 		if (current_regdata == NULL) {
541 			PRINT_ALLOC_ERR;
542 			return -2;
543 		}
544 		args->seq->regparam[args->layer] = current_regdata;
545 	}
546 
547 	if (args->process_all_frames)
548 		nb_frames = (float)args->seq->number;
549 	else nb_frames = (float)args->seq->selnum;
550 
551 	/* loading reference frame */
552 	ref_image = sequence_find_refimage(args->seq);
553 
554 	if (seq_read_frame(args->seq, ref_image, &ref, FALSE, -1)) {
555 		siril_log_message(_("Could not load reference image\n"));
556 		args->seq->regparam[args->layer] = NULL;
557 		free(current_regdata);
558 		return 1;
559 	}
560 	current_regdata[ref_image].quality = QualityEstimate(&ref, args->layer);
561 	/* we make sure to free data in the destroyed fit */
562 	clearfits(&ref);
563 	/* Ugly code: as QualityEstimate destroys fit we need to reload it */
564 	seq_read_frame(args->seq, ref_image, &ref, FALSE, -1);
565 	image_find_minmax(&ref);
566 	q_min = q_max = current_regdata[ref_image].quality;
567 	q_index = ref_image;
568 
569 	/* then we compare to other frames */
570 	if (args->process_all_frames)
571 		args->new_total = args->seq->number;
572 	else args->new_total = args->seq->selnum;
573 
574 	cur_nb = 0.f;
575 #ifdef _OPENMP
576 #pragma omp parallel for num_threads(com.max_thread) schedule(guided) \
577 	if (args->seq->type == SEQ_SER || ((args->seq->type == SEQ_REGULAR || args->seq->type == SEQ_FITSEQ) && fits_is_reentrant()))
578 #endif
579 	for (frame = 0; frame < args->seq->number; frame++) {
580 		if (!abort) continue;
581 		if (args->run_in_thread && !get_thread_run()) {
582 			abort = 1;
583 			continue;
584 		}
585 		if (frame == ref_image) continue;
586 		if (!args->process_all_frames && !args->seq->imgparam[frame].incl)
587 			continue;
588 
589 		set_shifts(args->seq, frame, args->layer, 0.0, 0.0, FALSE);
590 		fits im = { 0 };
591 		char tmpmsg[1024], tmpfilename[256];
592 
593 		seq_get_image_filename(args->seq, frame, tmpfilename);
594 		g_snprintf(tmpmsg, 1024, _("Register: processing image %s"),
595 				tmpfilename);
596 		set_progress_bar_data(tmpmsg, PROGRESS_NONE);
597 		int thread_id = -1;
598 #ifdef _OPENMP
599 		thread_id = omp_get_thread_num();
600 #endif
601 		if (!seq_read_frame(args->seq, frame, &im, FALSE, thread_id)) {
602 			reg_ecc reg_param = { 0 };
603 			image_find_minmax(&im);
604 
605 			if (findTransform(&ref, &im, args->layer, &reg_param)) {
606 				siril_log_message(
607 						_("Cannot perform ECC alignment for frame %d\n"),
608 						frame);
609 				/* We exclude this frame */
610 				args->seq->imgparam[frame].incl = FALSE;
611 				current_regdata[frame].quality = 0.0;
612 				args->seq->selnum--;
613 #ifdef _OPENMP
614 #pragma omp atomic
615 #endif
616 				++failed;
617 				clearfits(&im);
618 				continue;
619 			}
620 
621 			current_regdata[frame].quality = QualityEstimate(&im,
622 					args->layer);
623 #ifdef _OPENMP
624 #pragma omp critical
625 #endif
626 			{
627 				double qual = current_regdata[frame].quality;
628 				if (qual > q_max) {
629 					q_max = qual;
630 					q_index = frame;
631 				}
632 				q_min = min(q_min, qual);
633 			}
634 
635 			set_shifts(args->seq, frame, args->layer, -reg_param.dx,
636 					-reg_param.dy, im.top_down);
637 #ifdef _OPENMP
638 #pragma omp atomic
639 #endif
640 			cur_nb += 1.f;
641 			set_progress_bar_data(NULL, cur_nb / nb_frames);
642 			clearfits(&im);
643 		}
644 	}
645 
646 	if (args->x2upscale)
647 		args->seq->upscale_at_stacking = 2.0;
648 	else
649 		args->seq->upscale_at_stacking = 1.0;
650 
651 	normalizeQualityData(args, q_min, q_max);
652 	clearfits(&ref);
653 
654 	siril_log_message(_("Registration finished.\n"));
655 	if (failed) {
656 		gchar *str = ngettext("%d file was ignored and excluded\n", "%d files were ignored and excluded\n", failed);
657 		str = g_strdup_printf(str, failed);
658 		siril_log_color_message(str, "red");
659 		g_free(str);
660 	}
661 	siril_log_color_message(_("Best frame: #%d.\n"), "bold", q_index + 1);
662 
663 	return 0;
664 }
665 
on_comboboxregmethod_changed(GtkComboBox * box,gpointer user_data)666 void on_comboboxregmethod_changed(GtkComboBox *box, gpointer user_data) {
667 	int index = 0;
668 	gchar *text = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT(box));
669 
670 	while (reg_methods[index] && text != NULL) {
671 		if (!strcmp(reg_methods[index]->name, text))
672 			break;
673 		index++;
674 	}
675 	g_free(text);
676 
677 	com.reg_settings = index;
678 	update_reg_interface(TRUE);
679 	writeinitfile();
680 }
681 
682 /* for now, the sequence argument is used only when executing a script */
get_registration_layer(sequence * seq)683 int get_registration_layer(sequence *seq) {
684 	if (!com.script) {
685 		GtkComboBox *registbox = GTK_COMBO_BOX(lookup_widget("comboboxreglayer"));
686 		int reglayer = gtk_combo_box_get_active(registbox);
687 		if (!seq || !seq->regparam || seq->nb_layers < 0 || seq->nb_layers <= reglayer)
688 			return -1;
689 		return reglayer;
690 	} else {
691 		// find first available regdata
692 		if (!seq || !seq->regparam || seq->nb_layers < 0)
693 			return -1;
694 		int i;
695 		for (i = 0; i < seq->nb_layers; i++)
696 			if (seq->regparam[i])
697 				return i;
698 		return -1;
699 	}
700 }
701 
702 /* Selects the "register all" or "register selected" according to the number of
703  * selected images, if argument is false.
704  * Verifies that enough images are selected and an area is selected.
705  */
update_reg_interface(gboolean dont_change_reg_radio)706 void update_reg_interface(gboolean dont_change_reg_radio) {
707 	static GtkWidget *go_register = NULL, *follow = NULL, *cumul_data = NULL;
708 	static GtkLabel *labelreginfo = NULL;
709 	static GtkToggleButton *reg_all = NULL, *reg_sel = NULL;
710 	static GtkNotebook *notebook_reg = NULL;
711 	int nb_images_reg; /* the number of images to register */
712 	struct registration_method *method;
713 	gboolean selection_is_done;
714 
715 	if (!go_register) {
716 		go_register = lookup_widget("goregister_button");
717 		follow = lookup_widget("followStarCheckButton");
718 		reg_all = GTK_TOGGLE_BUTTON(lookup_widget("regallbutton"));
719 		reg_sel = GTK_TOGGLE_BUTTON(lookup_widget("regselbutton"));
720 		labelreginfo = GTK_LABEL(lookup_widget("labelregisterinfo"));
721 		notebook_reg = GTK_NOTEBOOK(lookup_widget("notebook_registration"));
722 		cumul_data = lookup_widget("check_button_comet");
723 	}
724 
725 	if (!dont_change_reg_radio) {
726 		if (com.seq.selnum < com.seq.number)
727 			gtk_toggle_button_set_active(reg_sel, TRUE);
728 		else
729 			gtk_toggle_button_set_active(reg_all, TRUE);
730 	}
731 
732 	selection_is_done = (com.selection.w > 0 && com.selection.h > 0);
733 
734 	/* initialize default */
735 	gtk_notebook_set_current_page(notebook_reg, REG_PAGE_MISC);
736 	gtk_widget_set_visible(cumul_data, FALSE);
737 
738 	/* getting the selected registration method */
739 	method = get_selected_registration_method();
740 
741 	/* number of registered image */
742 	nb_images_reg = gtk_toggle_button_get_active(reg_all) ? com.seq.number : com.seq.selnum;
743 
744 	if (method && nb_images_reg > 1 && (selection_is_done || method->sel == REQUIRES_NO_SELECTION)) {
745 		if (method->method_ptr == &register_star_alignment) {
746 			gtk_notebook_set_current_page(notebook_reg, REG_PAGE_GLOBAL);
747 		} else if (method->method_ptr == &register_comet) {
748 			gtk_notebook_set_current_page(notebook_reg, REG_PAGE_COMET);
749 		} else if (method->method_ptr == &register_3stars) {
750 			gtk_notebook_set_current_page(notebook_reg, REG_PAGE_3_STARS);
751 		}
752 		gtk_widget_set_visible(follow, method->method_ptr == &register_shift_fwhm);
753 		gtk_widget_set_visible(cumul_data, method->method_ptr == &register_comet);
754 		if (method->method_ptr == &register_3stars && com.seq.current != 0)
755 			gtk_label_set_text(labelreginfo, _("Make sure you load the first image"));
756 		else if (gfit.naxes[2] == 1 && gfit.bayer_pattern[0] != '\0')
757 			gtk_label_set_text(labelreginfo, _("Debayer the sequence for registration"));
758 		else gtk_label_set_text(labelreginfo, "");
759 		// the 3 stars method has special GUI requirements
760 		if (method->method_ptr != &register_3stars) {
761 			gtk_widget_set_sensitive(go_register, TRUE);
762 		}
763 	} else {
764 		gtk_widget_set_sensitive(go_register, FALSE);
765 		if (nb_images_reg <= 1 && !selection_is_done) {
766 			if (sequence_is_loaded()) {
767 				if (method && method->sel == REQUIRES_NO_SELECTION) {
768 					gtk_label_set_text(labelreginfo, _("Select images in the sequence"));
769 				} else {
770 					gtk_label_set_text(labelreginfo, _("Select an area in image first, and select images in the sequence"));
771 				}
772 			}
773 			else {
774 				gtk_label_set_text(labelreginfo, _("Load a sequence first"));
775 			}
776 		} else if (nb_images_reg <= 1) {
777 			gtk_label_set_text(labelreginfo, _("Select images in the sequence"));
778 		} else {
779 			gtk_label_set_text(labelreginfo, _("Select an area in image first"));
780 		}
781 	}
782 }
783 
784 /* try to maximize the area within the image size (based on gfit)
785  * hsteps and vsteps are used to resize the selection zone when it is larger than the image
786  * they must be at least 2 */
compute_fitting_selection(rectangle * area,int hsteps,int vsteps,int preserve_square)787 void compute_fitting_selection(rectangle *area, int hsteps, int vsteps, int preserve_square) {
788 	//fprintf(stdout, "function entry: %d,%d,\t%dx%d\n", area->x, area->y, area->w, area->h);
789 	if (area->x >= 0 && area->x + area->w <= gfit.rx && area->y >= 0
790 			&& area->y + area->h <= gfit.ry)
791 		return;
792 
793 	if (area->x < 0) {
794 		area->x++;
795 		if (area->x + area->w > gfit.rx) {
796 			/* reduce area */
797 			area->w -= hsteps;
798 			if (preserve_square) {
799 				area->h -= vsteps;
800 				area->y++;
801 			}
802 		}
803 	} else if (area->x + area->w > gfit.rx) {
804 		area->x--;
805 		if (area->x < 0) {
806 			/* reduce area */
807 			area->x++;
808 			area->w -= hsteps;
809 			if (preserve_square) {
810 				area->h -= vsteps;
811 				area->y++;
812 			}
813 		}
814 	}
815 
816 	if (area->y < 0) {
817 		area->y++;
818 		if (area->y + area->h > gfit.ry) {
819 			/* reduce area */
820 			area->h -= hsteps;
821 			if (preserve_square) {
822 				area->w -= vsteps;
823 				area->x++;
824 			}
825 		}
826 	} else if (area->y + area->h > gfit.ry) {
827 		area->y--;
828 		if (area->y < 0) {
829 			/* reduce area */
830 			area->y++;
831 			area->h -= vsteps;
832 			if (preserve_square) {
833 				area->w -= hsteps;
834 				area->x++;
835 			}
836 		}
837 	}
838 
839 	return compute_fitting_selection(area, hsteps, vsteps, preserve_square);
840 }
841 
get_the_registration_area(struct registration_args * reg_args,struct registration_method * method)842 void get_the_registration_area(struct registration_args *reg_args,
843 		struct registration_method *method) {
844 	int max;
845 	switch (method->sel) {
846 		/* even in the case of REQUIRES_NO_SELECTION selection is needed for MatchSelection of starAlignment */
847 		case REQUIRES_NO_SELECTION:
848 		case REQUIRES_ANY_SELECTION:
849 			memcpy(&reg_args->selection, &com.selection, sizeof(rectangle));
850 			break;
851 		case REQUIRES_SQUARED_SELECTION:
852 			/* Passed arguments are X,Y of the center of the square and the size of
853 			 * the square. */
854 			if (com.selection.w > com.selection.h)
855 				max = com.selection.w;
856 			else
857 				max = com.selection.h;
858 
859 			reg_args->selection.x = com.selection.x + com.selection.w / 2 - max / 2;
860 			reg_args->selection.w = max;
861 			reg_args->selection.y = com.selection.y + com.selection.h / 2 - max / 2;
862 			reg_args->selection.h = max;
863 			compute_fitting_selection(&reg_args->selection, 2, 2, 1);
864 
865 			/* save it back to com.selection do display it properly */
866 			memcpy(&com.selection, &reg_args->selection, sizeof(rectangle));
867 			fprintf(stdout, "final area: %d,%d,\t%dx%d\n", reg_args->selection.x,
868 					reg_args->selection.y, reg_args->selection.w,
869 					reg_args->selection.h);
870 			redraw(com.cvport, REMAP_NONE);
871 			break;
872 	}
873 }
874 
875 /* callback for 'Go register' button, GTK thread */
on_seqregister_button_clicked(GtkButton * button,gpointer user_data)876 void on_seqregister_button_clicked(GtkButton *button, gpointer user_data) {
877 	struct registration_args *reg_args;
878 	struct registration_method *method;
879 	char *msg;
880 	GtkToggleButton *regall, *follow, *matchSel, *no_translate, *x2upscale,
881 			*cumul;
882 	GtkComboBox *cbbt_layers;
883 	GtkComboBoxText *ComboBoxRegInter;
884 	GtkSpinButton *minpairs;
885 
886 	if (!reserve_thread()) {	// reentrant from here
887 		PRINT_ANOTHER_THREAD_RUNNING;
888 		return;
889 	}
890 
891 	if (!com.seq.regparam) {
892 		fprintf(stderr, "regparam should have been created before\n");
893 		// means that a call to seq_check_basic_data() or
894 		// check_or_allocate_regparam() is missing somewhere else
895 		unreserve_thread();
896 		return;
897 	}
898 
899 	method = get_selected_registration_method();
900 
901 	if (com.selection.w <= 0 && com.selection.h <= 0
902 			&& method->sel != REQUIRES_NO_SELECTION) {
903 		msg = siril_log_message(
904 				_("All prerequisites are not filled for registration. Select a rectangle first.\n"));
905 		siril_message_dialog( GTK_MESSAGE_WARNING, _("Warning"), msg);
906 		unreserve_thread();
907 		return;
908 	}
909 
910 	reg_args = calloc(1, sizeof(struct registration_args));
911 
912 	control_window_switch_to_tab(OUTPUT_LOGS);
913 
914 	/* filling the arguments for registration */
915 	regall = GTK_TOGGLE_BUTTON(lookup_widget("regallbutton"));
916 	follow = GTK_TOGGLE_BUTTON(lookup_widget("followStarCheckButton"));
917 	matchSel = GTK_TOGGLE_BUTTON(lookup_widget("checkStarSelect"));
918 	no_translate = GTK_TOGGLE_BUTTON(lookup_widget("regTranslationOnly"));
919 	x2upscale = GTK_TOGGLE_BUTTON(lookup_widget("upscaleCheckButton"));
920 	cbbt_layers = GTK_COMBO_BOX(lookup_widget("comboboxreglayer"));
921 	ComboBoxRegInter = GTK_COMBO_BOX_TEXT(lookup_widget("ComboBoxRegInter"));
922 	cumul = GTK_TOGGLE_BUTTON(lookup_widget("check_button_comet"));
923 	minpairs = GTK_SPIN_BUTTON(lookup_widget("spinbut_minpairs"));
924 
925 	reg_args->func = method->method_ptr;
926 	reg_args->seq = &com.seq;
927 	reg_args->reference_image = sequence_find_refimage(&com.seq);
928 	reg_args->process_all_frames = gtk_toggle_button_get_active(regall);
929 	reg_args->follow_star = gtk_toggle_button_get_active(follow);
930 	reg_args->matchSelection = gtk_toggle_button_get_active(matchSel);
931 	reg_args->translation_only = gtk_toggle_button_get_active(no_translate);
932 	reg_args->x2upscale = gtk_toggle_button_get_active(x2upscale);
933 	reg_args->cumul = gtk_toggle_button_get_active(cumul);
934 	reg_args->prefix = gtk_entry_get_text(GTK_ENTRY(lookup_widget("regseqname_entry")));
935 	reg_args->min_pairs = gtk_spin_button_get_value_as_int(minpairs);
936 
937 	/* We check that available disk space is enough when:
938 	 * - activating the subpixel alignment, which requires generating a new
939 	 *   sequence with bigger images
940 	 * - using global star registration with rotation enabled, also generating a
941 	 *   new sequence */
942 	if (reg_args->x2upscale ||
943 			(method->method_ptr == register_star_alignment &&
944 			 !reg_args->translation_only)) {
945 		// first, remove the files that we are about to create
946 		remove_prefixed_sequence_files(reg_args->seq, reg_args->prefix);
947 
948 		int nb_frames = reg_args->process_all_frames ? reg_args->seq->number : reg_args->seq->selnum;
949 		gint64 size = seq_compute_size(reg_args->seq, nb_frames, get_data_type(reg_args->seq->bitpix));
950 		if (reg_args->x2upscale)
951 			size *= 4;
952 		if (test_available_space(size) > 0) {
953 			free(reg_args);
954 			unreserve_thread();
955 			return;
956 		}
957 	} else if (method->method_ptr == register_comet) {
958 		pointf velocity = get_velocity();
959 		if ((velocity.x == 0.0 && velocity.y == 0.0)
960 				|| isinf(velocity.x) || isinf(velocity.y)) {
961 			msg = siril_log_color_message(_("The object is not moving, please check your registration data.\n"), "red");
962 			siril_message_dialog( GTK_MESSAGE_WARNING, _("Warning"), msg);
963 			free(reg_args);
964 			unreserve_thread();
965 			return;
966 		}
967 	}
968 	/* getting the selected registration layer from the combo box. The value is the index
969 	 * of the selected line, and they are in the same order than layers so there should be
970 	 * an exact matching between the two */
971 	reg_args->layer = gtk_combo_box_get_active(cbbt_layers);
972 	reg_args->interpolation = gtk_combo_box_get_active(GTK_COMBO_BOX(ComboBoxRegInter));
973 	get_the_registration_area(reg_args, method);	// sets selection
974 	reg_args->run_in_thread = TRUE;
975 	reg_args->load_new_sequence = FALSE; // only TRUE for global registration. Will be updated in this case
976 
977 	msg = siril_log_color_message(_("Registration: processing using method: %s\n"),
978 			"green", method->name);
979 	msg[strlen(msg) - 1] = '\0';
980 	set_cursor_waiting(TRUE);
981 	set_progress_bar_data(msg, PROGRESS_RESET);
982 
983 	start_in_reserved_thread(register_thread_func, reg_args);
984 }
985 
986 // worker thread function for the registration
register_thread_func(gpointer p)987 gpointer register_thread_func(gpointer p) {
988 	struct registration_args *args = (struct registration_args *) p;
989 	int retval;
990 
991 	args->retval = args->func(args);
992 
993 	if (args->seq->reference_image == -1) {
994 		// set new reference image: should we do it all the time?
995 		// also done in generated sequence in global.c
996 		args->seq->reference_image = sequence_find_refimage(args->seq);
997 	}
998 	writeseqfile(args->seq);
999 	retval = args->retval;
1000 	if (!siril_add_idle(end_register_idle, args)) {
1001 		free_sequence(args->seq, TRUE);
1002 		free(args);
1003 	}
1004 	return GINT_TO_POINTER(retval);
1005 }
1006 
1007 // end of registration, GTK thread. Executed when started from the GUI and in
1008 // the graphical command line but not from a script (headless mode)
end_register_idle(gpointer p)1009 static gboolean end_register_idle(gpointer p) {
1010 	struct registration_args *args = (struct registration_args *) p;
1011 	stop_processing_thread();
1012 
1013 	if (!args->retval) {
1014 		if (!args->load_new_sequence) {
1015 			int chan = gtk_combo_box_get_active(GTK_COMBO_BOX(lookup_widget("seqlist_dialog_combo")));
1016 			update_seqlist();
1017 			fill_sequence_list(args->seq, chan, FALSE);
1018 			set_layers_for_registration();	// update display of available reg data
1019 		}
1020 		else {
1021 			check_seq(0);
1022 			update_sequences_list(args->new_seq_name);
1023 		}
1024 	}
1025 	set_progress_bar_data(_("Registration complete."), PROGRESS_DONE);
1026 
1027 	drawPlot();
1028 	update_stack_interface(TRUE);
1029 	adjust_sellabel();
1030 
1031 	set_cursor_waiting(FALSE);
1032 
1033 	free(args);
1034 	return FALSE;
1035 }
1036