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 ®ister_shift_fwhm, REQUIRES_ANY_SELECTION, REGTYPE_DEEPSKY);
109 reg_methods[i++] = new_reg_method(_("Two or Three Stars Registration (deep-sky)"),
110 ®ister_3stars, REQUIRES_NO_SELECTION, REGTYPE_DEEPSKY);
111 reg_methods[i++] = new_reg_method(_("Global Star Alignment (deep-sky)"),
112 ®ister_star_alignment, REQUIRES_NO_SELECTION, REGTYPE_DEEPSKY);
113 reg_methods[i++] = new_reg_method(_("Image Pattern Alignment (planetary - full disk)"),
114 ®ister_shift_dft, REQUIRES_SQUARED_SELECTION, REGTYPE_PLANETARY);
115 reg_methods[i++] = new_reg_method(_("Enhanced Correlation Coefficient (planetary - surfaces)"),
116 ®ister_ecc, REQUIRES_NO_SELECTION, REGTYPE_PLANETARY);
117 reg_methods[i++] = new_reg_method(_("Comet/Asteroid Registration"),
118 ®ister_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, ®_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 == ®ister_star_alignment) {
746 gtk_notebook_set_current_page(notebook_reg, REG_PAGE_GLOBAL);
747 } else if (method->method_ptr == ®ister_comet) {
748 gtk_notebook_set_current_page(notebook_reg, REG_PAGE_COMET);
749 } else if (method->method_ptr == ®ister_3stars) {
750 gtk_notebook_set_current_page(notebook_reg, REG_PAGE_3_STARS);
751 }
752 gtk_widget_set_visible(follow, method->method_ptr == ®ister_shift_fwhm);
753 gtk_widget_set_visible(cumul_data, method->method_ptr == ®ister_comet);
754 if (method->method_ptr == ®ister_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 != ®ister_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(®_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(®_args->selection, 2, 2, 1);
864
865 /* save it back to com.selection do display it properly */
866 memcpy(&com.selection, ®_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