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 <math.h>
25 
26 #include "core/siril.h"
27 #include "core/proto.h"
28 #include "core/processing.h"
29 #include "core/OS_utils.h"
30 #include "algos/star_finder.h"
31 #include "algos/statistics.h"
32 #include "algos/PSF.h"
33 #include "gui/image_display.h"
34 #include "gui/PSF_list.h"
35 #include "gui/progress_and_log.h"
36 #include "gui/utils.h"
37 #include "io/sequence.h"
38 #include "io/ser.h"
39 #include "io/image_format_fits.h"
40 #include "registration/registration.h"
41 #include "registration/matching/atpmatch.h"
42 #include "registration/matching/match.h"
43 #include "registration/matching/misc.h"
44 #include "opencv/opencv.h"
45 
46 #define MAX_STARS_FITTED 2000
47 
48 /* TODO:
49  * check usage of openmp in functions called by these ones (to be disabled)
50  * compact and clarify console output
51  */
52 
53 static void create_output_sequence_for_global_star(struct registration_args *args);
54 static void print_alignment_results(Homography H, int filenum, float FWHMx, float FWHMy, char *units);
55 
star_align_get_current_regdata(struct registration_args * regargs)56 regdata *star_align_get_current_regdata(struct registration_args *regargs) {
57 	regdata *current_regdata;
58 	if (regargs->seq->regparam[regargs->layer]) {
59 		siril_log_message(
60 				_("Recomputing already existing registration for this layer\n"));
61 		current_regdata = regargs->seq->regparam[regargs->layer];
62 		/* we reset all values as we may register different images */
63 		memset(current_regdata, 0, regargs->seq->number * sizeof(regdata));
64 	} else {
65 		current_regdata = calloc(regargs->seq->number, sizeof(regdata));
66 		if (current_regdata == NULL) {
67 			PRINT_ALLOC_ERR;
68 			return NULL;
69 		}
70 		regargs->seq->regparam[regargs->layer] = current_regdata;
71 	}
72 	return current_regdata;
73 }
74 
star_align_prepare_results(struct generic_seq_args * args)75 int star_align_prepare_results(struct generic_seq_args *args) {
76 	struct star_align_data *sadata = args->user;
77 	struct registration_args *regargs = sadata->regargs;
78 
79 	if (!regargs->translation_only) {
80 		// allocate destination sequence data
81 		regargs->imgparam = calloc(args->nb_filtered_images, sizeof(imgdata));
82 		regargs->regparam = calloc(args->nb_filtered_images, sizeof(regdata));
83 		if (!regargs->imgparam  || !regargs->regparam) {
84 			PRINT_ALLOC_ERR;
85 			return 1;
86 		}
87 
88 		if (args->seq->type == SEQ_SER) {
89 			/* copied from seq_prepare_hook with one variation */
90 			args->new_ser = malloc(sizeof(struct ser_struct));
91 			if (!args->new_ser) {
92 				PRINT_ALLOC_ERR;
93 				return 1;
94 			}
95 
96 			char dest[256];
97 			const char *ptr = strrchr(args->seq->seqname, G_DIR_SEPARATOR);
98 			if (ptr)
99 				snprintf(dest, 255, "%s%s.ser", regargs->prefix, ptr + 1);
100 			else
101 				snprintf(dest, 255, "%s%s.ser", regargs->prefix, args->seq->seqname);
102 
103 			/* Here the last argument is NULL because we do not want copy SER file
104 			 * from the original. Indeed in the demosaicing case this would lead to
105 			 * a wrong file (B&W and not in RAW data). Moreover, header informations
106 			 * (like fps, local and UTC time, ...) have no sense now since some frames
107 			 * could be removed from the sequence.
108 			 */
109 			if (ser_create_file(dest, args->new_ser, TRUE, NULL)) {
110 				free(args->new_ser);
111 				args->new_ser = NULL;
112 				return 1;
113 			}
114 
115 			if (seq_prepare_writer(args))
116 				return 1;
117 		}
118 		else if (args->seq->type == SEQ_FITSEQ) {
119 			if (seq_prepare_hook(args))
120 				return 1;
121 		}
122 	}
123 
124 	sadata->success = calloc(args->nb_filtered_images, sizeof(BYTE));
125 	if (!sadata->success) {
126 		PRINT_ALLOC_ERR;
127 		return 1;
128 	}
129 	return 0;
130 }
131 
132 
star_align_prepare_hook(struct generic_seq_args * args)133 static int star_align_prepare_hook(struct generic_seq_args *args) {
134 	struct star_align_data *sadata = args->user;
135 	struct registration_args *regargs = sadata->regargs;
136 	float FWHMx, FWHMy;
137 	char *units;
138 	fits fit = { 0 };
139 	int i, nb_stars = 0;
140 
141 	sadata->current_regdata = star_align_get_current_regdata(regargs);
142 	if (!sadata->current_regdata) return -2;
143 
144 	/* first we're looking for stars in reference image */
145 	if (seq_read_frame(args->seq, regargs->reference_image, &fit, FALSE, -1)) {
146 		siril_log_message(_("Could not load reference image\n"));
147 		args->seq->regparam[regargs->layer] = NULL;
148 		free(sadata->current_regdata);
149 		return 1;
150 	}
151 
152 	siril_log_color_message(_("Reference Image:\n"), "green");
153 
154 	if (regargs->matchSelection && regargs->selection.w > 0 && regargs->selection.h > 0) {
155 		com.stars = peaker(&fit, regargs->layer, &com.starfinder_conf, &nb_stars, &regargs->selection, FALSE);
156 	}
157 	else {
158 		com.stars = peaker(&fit, regargs->layer, &com.starfinder_conf, &nb_stars, NULL, FALSE);
159 	}
160 
161 	siril_log_message(_("Found %d stars in reference, channel #%d\n"), nb_stars, regargs->layer);
162 
163 
164 	if (!com.stars || nb_stars < AT_MATCH_MINPAIRS) {
165 		siril_log_message(
166 				_("There are not enough stars in reference image to perform alignment\n"));
167 		args->seq->regparam[regargs->layer] = NULL;
168 		free(sadata->current_regdata);
169 		clearfits(&fit);
170 		return 1;
171 	}
172 	if (!com.script && &com.seq == args->seq && com.seq.current == regargs->reference_image)
173 		queue_redraw(REMAP_NONE); // draw stars
174 
175 	sadata->ref.x = fit.rx;
176 	sadata->ref.y = fit.ry;
177 
178 	clearfits(&fit);
179 
180 	if (regargs->x2upscale) {
181 		if (regargs->translation_only) {
182 			args->seq->upscale_at_stacking = 2.0;
183 		} else {
184 			sadata->ref.x *= 2.0;
185 			sadata->ref.y *= 2.0;
186 		}
187 	}
188 	else {
189 		if (regargs->translation_only) {
190 			args->seq->upscale_at_stacking = 1.0;
191 		}
192 	}
193 
194 	/* we copy com.stars to refstars in case user take a look to another image of the sequence
195 	 * that would destroy com.stars
196 	 */
197 	i = 0;
198 	sadata->refstars = malloc((MAX_STARS + 1) * sizeof(fitted_PSF *));
199 	if (!sadata->refstars) {
200 		PRINT_ALLOC_ERR;
201 		return 1;
202 	}
203 	while (i < MAX_STARS && com.stars[i]) {
204 		fitted_PSF *tmp = malloc(sizeof(fitted_PSF));
205 		if (!tmp) {
206 			PRINT_ALLOC_ERR;
207 			sadata->refstars[i] = NULL;
208 			return 1;
209 		}
210 		memcpy(tmp, com.stars[i], sizeof(fitted_PSF));
211 		sadata->refstars[i] = tmp;
212 		sadata->refstars[i+1] = NULL;
213 		i++;
214 	}
215 
216 	if (nb_stars > MAX_STARS_FITTED) {
217 		sadata->fitted_stars = MAX_STARS_FITTED;
218 		siril_log_color_message(_("Reference Image: Limiting to %d brightest stars\n"), "green", MAX_STARS_FITTED);
219 	} else {
220 		sadata->fitted_stars = nb_stars;
221 	}
222 	FWHM_average(sadata->refstars, sadata->fitted_stars, &FWHMx, &FWHMy, &units);
223 	siril_log_message(_("FWHMx:%*.2f %s\n"), 12, FWHMx, units);
224 	siril_log_message(_("FWHMy:%*.2f %s\n"), 12, FWHMy, units);
225 	sadata->current_regdata[regargs->reference_image].roundness = FWHMy/FWHMx;
226 	sadata->current_regdata[regargs->reference_image].fwhm = FWHMx;
227 	sadata->current_regdata[regargs->reference_image].weighted_fwhm = FWHMx;
228 
229 	return star_align_prepare_results(args);
230 }
231 
232 /* reads the image, searches for stars in it, tries to match them with
233  * reference stars, computes the homography matrix, applies it on the image,
234  * possibly up-scales the image and stores registration data */
star_align_image_hook(struct generic_seq_args * args,int out_index,int in_index,fits * fit,rectangle * _)235 static int star_align_image_hook(struct generic_seq_args *args, int out_index, int in_index, fits *fit, rectangle *_) {
236 	struct star_align_data *sadata = args->user;
237 	struct registration_args *regargs = sadata->regargs;
238 	int nbpoints, nb_stars = 0;
239 	int retvalue;
240 	int nobj = 0;
241 	int attempt = 1;
242 	float FWHMx, FWHMy;
243 	char *units;
244 	Homography H = { 0 };
245 	int filenum = args->seq->imgparam[in_index].filenum;	// for display purposes
246 
247 	if (regargs->translation_only) {
248 		/* if "translation only", we choose to initialize all frames
249 		 * to exclude status. If registration is ok, the status is
250 		 * set to include */
251 		args->seq->imgparam[out_index].incl = !SEQUENCE_DEFAULT_INCLUDE;
252 	}
253 
254 	if (in_index != regargs->reference_image) {
255 		fitted_PSF **stars;
256 		if (args->seq->type == SEQ_SER || args->seq->type == SEQ_FITSEQ) {
257 			siril_log_color_message(_("Frame %d:\n"), "bold", filenum);
258 		}
259 
260 		if (regargs->matchSelection && regargs->selection.w > 0 && regargs->selection.h > 0) {
261 			stars = peaker(fit, regargs->layer, &com.starfinder_conf, &nb_stars, &regargs->selection, FALSE);
262 		}
263 		else {
264 			stars = peaker(fit, regargs->layer, &com.starfinder_conf, &nb_stars, NULL, FALSE);
265 		}
266 
267 		siril_log_message(_("Found %d stars in image %d, channel #%d\n"), nb_stars, filenum, regargs->layer);
268 
269 		if (!stars || nb_stars < AT_MATCH_MINPAIRS) {
270 			siril_log_message(
271 					_("Not enough stars. Image %d skipped\n"), filenum);
272 			if (stars) free_fitted_stars(stars);
273 			return 1;
274 		}
275 
276 		if (nb_stars > sadata->fitted_stars) {
277 			if (nb_stars > MAX_STARS_FITTED) {
278 				siril_log_color_message(_("Target Image: Limiting to %d brightest stars\n"), "green", MAX_STARS_FITTED);
279 			}
280 			nbpoints = sadata->fitted_stars;
281 		}
282 		else {
283 			nbpoints = nb_stars;
284 		}
285 
286 		/* make a loop with different tries in order to align the two sets of data */
287 		double scale_min = 0.9;
288 		double scale_max = 1.1;
289 		retvalue = 1;
290 		while (retvalue && attempt < NB_OF_MATCHING_TRY){
291 			retvalue = new_star_match(stars, sadata->refstars, nbpoints, nobj, scale_min, scale_max, &H, FALSE);
292 			if (attempt == 1) {
293 				scale_min = -1.0;
294 				scale_max = -1.0;
295 			} else {
296 				nobj += 50;
297 			}
298 			attempt++;
299 		}
300 		if (retvalue) {
301 			siril_log_color_message(_("Cannot perform star matching: try #%d. Image %d skipped\n"),
302 					"red", attempt, filenum);
303 			free_fitted_stars(stars);
304 			return 1;
305 		}
306 		if (H.pair_matched < regargs->min_pairs) {
307 			siril_log_color_message(_("Not enough star pairs (%d): Image %d skipped\n"),
308 					"red", H.pair_matched, filenum);
309 			free_fitted_stars(stars);
310 			return 1;
311 		}
312 
313 		FWHM_average(stars, nbpoints, &FWHMx, &FWHMy, &units);
314 #ifdef _OPENMP
315 #pragma omp critical
316 #endif
317 		print_alignment_results(H, filenum, FWHMx, FWHMy, units);
318 
319 		sadata->current_regdata[in_index].roundness = FWHMy/FWHMx;
320 		sadata->current_regdata[in_index].fwhm =  FWHMx;
321 		sadata->current_regdata[in_index].weighted_fwhm = 2 * FWHMx
322 				* (((double) sadata->fitted_stars) - (double) H.pair_matched)
323 				/ (double) sadata->fitted_stars + FWHMx;
324 
325 		if (!regargs->translation_only) {
326 			if (cvTransformImage(fit, H, regargs->x2upscale, regargs->interpolation)) {
327 				free_fitted_stars(stars);
328 				return 1;
329 			}
330 		}
331 
332 		free_fitted_stars(stars);
333 	}
334 	else {
335 		if (regargs->x2upscale && !regargs->translation_only) {
336 			if (cvResizeGaussian(fit, fit->rx * 2, fit->ry * 2, OPENCV_NEAREST))
337 				return 1;
338 		}
339 	}
340 
341 	if (!regargs->translation_only) {
342 		regargs->imgparam[out_index].filenum = args->seq->imgparam[in_index].filenum;
343 		regargs->imgparam[out_index].incl = SEQUENCE_DEFAULT_INCLUDE;
344 		regargs->regparam[out_index].fwhm = sadata->current_regdata[in_index].fwhm;	// not FWHMx because of the ref frame
345 		regargs->regparam[out_index].weighted_fwhm = sadata->current_regdata[in_index].weighted_fwhm;
346 		regargs->regparam[out_index].roundness = sadata->current_regdata[in_index].roundness;
347 
348 		if (regargs->x2upscale) {
349 			fit->pixel_size_x /= 2;
350 			fit->pixel_size_y /= 2;
351 			regargs->regparam[out_index].fwhm *= 2.0;
352 			regargs->regparam[out_index].weighted_fwhm *= 2.0;
353 		}
354 	} else {
355 		set_shifts(args->seq, in_index, regargs->layer, (float) H.h02,
356 				(float) -H.h12, fit->top_down);
357 		args->seq->imgparam[out_index].incl = SEQUENCE_DEFAULT_INCLUDE;
358 	}
359 	sadata->success[out_index] = 1;
360 	return 0;
361 }
362 
star_align_finalize_hook(struct generic_seq_args * args)363 int star_align_finalize_hook(struct generic_seq_args *args) {
364 	struct star_align_data *sadata = args->user;
365 	struct registration_args *regargs = sadata->regargs;
366 	int i, failed = 0;
367 
368 	// images may have been excluded but selnum wasn't updated
369 	fix_selnum(args->seq, FALSE);
370 
371 	free_fitted_stars(sadata->refstars);
372 
373 	if (!args->retval) {
374 		for (i = 0; i < args->nb_filtered_images; i++)
375 			if (!sadata->success[i])
376 				failed++;
377 		regargs->new_total = args->nb_filtered_images - failed;
378 
379 		if (!regargs->translation_only) {
380 			if (failed) {
381 				// regargs->imgparam and regargs->regparam may have holes caused by images
382 				// that failed to be registered - compact them
383 				int j;
384 				for (i = 0, j = 0; i < regargs->new_total; i++, j++) {
385 					while (!sadata->success[j] && j < args->nb_filtered_images) j++;
386 					g_assert(sadata->success[j]);
387 					if (i != j) {
388 						regargs->imgparam[i] = regargs->imgparam[j];
389 						regargs->regparam[i] = regargs->regparam[j];
390 					}
391 				}
392 			}
393 
394 			seq_finalize_hook(args);
395 		}
396 	} else {
397 		regargs->new_total = 0;
398 		free(args->seq->regparam[regargs->layer]);
399 		args->seq->regparam[regargs->layer] = NULL;
400 
401 		// args->new_ser can be null if stars were not detected in the reference image
402 		// same as seq_finalize_hook but with file deletion
403 		if ((args->force_ser_output || args->seq->type == SEQ_SER) && args->new_ser) {
404 			ser_close_and_delete_file(args->new_ser);
405 			free(args->new_ser);
406 		}
407 		else if ((args->force_fitseq_output || args->seq->type == SEQ_FITSEQ) && args->new_fitseq) {
408 			fitseq_close_and_delete_file(args->new_fitseq);
409 			free(args->new_fitseq);
410 		}
411 	}
412 
413 	if (sadata->success) free(sadata->success);
414 	free(sadata);
415 	args->user = NULL;
416 	clear_stars_list();
417 
418 	if (!args->retval) {
419 		siril_log_message(_("Registration finished.\n"));
420 		gchar *str = ngettext("%d image processed.\n", "%d images processed.\n", args->nb_filtered_images);
421 		str = g_strdup_printf(str, args->nb_filtered_images);
422 		siril_log_color_message(str, "green");
423 		siril_log_color_message(_("Total: %d failed, %d registered.\n"), "green", failed, regargs->new_total);
424 
425 		g_free(str);
426 		if (!regargs->translation_only) {
427 			// explicit sequence creation to copy imgparam and regparam
428 			create_output_sequence_for_global_star(regargs);
429 			// will be loaded in the idle function if (load_new_sequence)
430 			regargs->load_new_sequence = TRUE; // only case where a new sequence must be loaded
431 		}
432 	}
433 	else {
434 		siril_log_message(_("Registration aborted.\n"));
435 	}
436 	return regargs->new_total == 0;
437 	// TODO: args is never freed because we don't call an end function for
438 	// this generic processing function. The register idle is called for
439 	// everything else, but does not know this pointer, and we cannot free
440 	// it here because it's still used in the generic processing function.
441 }
442 
star_align_compute_mem_limits(struct generic_seq_args * args,gboolean for_writer)443 int star_align_compute_mem_limits(struct generic_seq_args *args, gboolean for_writer) {
444 	unsigned int MB_per_orig_image, MB_per_scaled_image, MB_avail;
445 	int limit = compute_nb_images_fit_memory(args->seq, args->upscale_ratio, args->force_float,
446 			&MB_per_orig_image, &MB_per_scaled_image, &MB_avail);
447 	unsigned int required = MB_per_scaled_image;
448 	if (limit > 0) {
449 		/* The registration memory consumption, n is image size and m channel size.
450 		 * First, a threshold is computed for star pixel value, using statistics:
451 		 *	O(m), data is duplicated for median computation if
452 		 *	there are nil values, O(1) otherwise
453 		 * Then, still in peaker(), image is filtered using wavelets, duplicating the
454 		 * image channel to act as input and output of the filter (O(m)) and
455 		 * wavelet_transform then allocates 3 times the size of the channel in float,
456 		 * because we request 3 planes of wavelets, and pave_2d allocates it once more
457 		 * for its working copy, so that makes O(5m as float).
458 		 * Then, the image is rotated and upscaled by the generic function if enabled:
459 		 * cvTransformImage is O(n) in mem for unscaled, O(nscaled)=O(4m) for
460 		 * monochrome scaled and O(2nscaled)=O(21m) for color scaled
461 		 * All this is in addition to the image being already loaded.
462 		 *
463 		 * Since these three operations are in sequence, we need room only for the
464 		 * biggest. In case of color rotated scaled image, it's the rotation that takes
465 		 * more memory, with O(8n)=O(24m) in total. In all other cases it's the
466 		 * wavelets with O(n+5mfloat).
467 		 */
468 		if (args->has_output && args->seq->nb_layers == 3 && args->upscale_ratio == 2.0)
469 			required = MB_per_scaled_image * 2;
470 		else {
471 			unsigned int float_multiplier =
472 				get_data_type(args->seq->bitpix) == DATA_FLOAT ? 1 : 2;
473 			unsigned int MB_per_float_image = MB_per_orig_image * float_multiplier;
474 			unsigned int MB_per_float_channel = args->seq->nb_layers == 1 ?
475 				MB_per_float_image : MB_per_float_image / 3;
476 			required = MB_per_orig_image + 5 * MB_per_float_channel;
477 		}
478 		int thread_limit = MB_avail / required;
479 		if (thread_limit > com.max_thread)
480 			thread_limit = com.max_thread;
481 
482 		if (for_writer) {
483 			/* we allow the already allocated thread_limit images,
484 			 * plus how many images can be stored in what remains
485 			 * unused by the main processing */
486 			limit = thread_limit + (MB_avail - required * thread_limit) / MB_per_scaled_image;
487 		} else limit = thread_limit;
488 	}
489 
490 	if (limit == 0) {
491 		gchar *mem_per_thread = g_format_size_full(required * BYTES_IN_A_MB, G_FORMAT_SIZE_IEC_UNITS);
492 		gchar *mem_available = g_format_size_full(MB_avail * BYTES_IN_A_MB, G_FORMAT_SIZE_IEC_UNITS);
493 
494 		siril_log_color_message(_("%s: not enough memory to do this operation (%s required per thread, %s considered available)\n"),
495 				"red", args->description, mem_per_thread, mem_available);
496 
497 		g_free(mem_per_thread);
498 		g_free(mem_available);
499 	} else {
500 #ifdef _OPENMP
501 		if (for_writer) {
502 			int max_queue_size = com.max_thread * 3;
503 			if (limit > max_queue_size)
504 				limit = max_queue_size;
505 		}
506 		siril_debug_print("Memory required per thread: %u MB, per image: %u MB, limiting to %d %s\n",
507 				required, MB_per_scaled_image, limit, for_writer ? "images" : "threads");
508 #else
509 		if (!for_writer)
510 			limit = 1;
511 		else if (limit > 3)
512 			limit = 3;
513 #endif
514 	}
515 	return limit;
516 }
517 
register_star_alignment(struct registration_args * regargs)518 int register_star_alignment(struct registration_args *regargs) {
519 	struct generic_seq_args *args = create_default_seqargs(regargs->seq);
520 	if (!regargs->process_all_frames) {
521 		args->filtering_criterion = seq_filter_included;
522 		args->nb_filtered_images = regargs->seq->selnum;
523 	}
524 	args->compute_mem_limits_hook = star_align_compute_mem_limits;
525 	args->prepare_hook = star_align_prepare_hook;
526 	args->image_hook = star_align_image_hook;
527 	args->finalize_hook = star_align_finalize_hook;
528 	args->stop_on_error = FALSE;
529 	args->description = _("Global star registration");
530 	args->has_output = !regargs->translation_only;
531 	args->output_type = get_data_type(args->seq->bitpix);
532 	args->upscale_ratio = regargs->x2upscale ? 2.0 : 1.0;
533 	args->new_seq_prefix = regargs->prefix;
534 	args->load_new_sequence = TRUE;
535 	args->already_in_a_thread = TRUE;
536 
537 	struct star_align_data *sadata = calloc(1, sizeof(struct star_align_data));
538 	if (!sadata) {
539 		free(args);
540 		return -1;
541 	}
542 	sadata->regargs = regargs;
543 	args->user = sadata;
544 
545 	generic_sequence_worker(args);
546 
547 	regargs->retval = args->retval;
548 	free(args);
549 	return regargs->retval;
550 }
551 
create_output_sequence_for_global_star(struct registration_args * args)552 static void create_output_sequence_for_global_star(struct registration_args *args) {
553 	sequence seq = { 0 };
554 	initialize_sequence(&seq, TRUE);
555 
556 	/* we are not interested in the whole path */
557 	gchar *seqname = g_path_get_basename(args->seq->seqname);
558 	char *rseqname = malloc(
559 			strlen(args->prefix) + strlen(seqname) + 5);
560 	sprintf(rseqname, "%s%s.seq", args->prefix, seqname);
561 	g_free(seqname);
562 	g_unlink(rseqname);	// remove previous to overwrite
563 	args->new_seq_name = remove_ext_from_filename(rseqname);
564 	free(rseqname);
565 	seq.seqname = strdup(args->new_seq_name);
566 	seq.number = args->new_total;
567 	seq.selnum = args->new_total;
568 	seq.fixed = args->seq->fixed;
569 	seq.nb_layers = args->seq->nb_layers;
570 	seq.rx = args->seq->rx;
571 	seq.ry = args->seq->ry;
572 	seq.imgparam = args->imgparam;
573 	seq.regparam = calloc(seq.nb_layers, sizeof(regdata*));
574 	seq.regparam[args->layer] = args->regparam;
575 	seq.layers = calloc(seq.nb_layers, sizeof(layer_info));
576 	seq.beg = seq.imgparam[0].filenum;
577 	seq.end = seq.imgparam[seq.number-1].filenum;
578 	seq.type = args->seq->type;
579 	seq.current = -1;
580 	// don't copy from old sequence, it may not be the same image
581 	seq.reference_image = sequence_find_refimage(&seq);
582 	seq.needs_saving = TRUE;
583 	writeseqfile(&seq);
584 	free_sequence(&seq, FALSE);
585 }
586 
print_alignment_results(Homography H,int filenum,float FWHMx,float FWHMy,char * units)587 static void print_alignment_results(Homography H, int filenum, float FWHMx, float FWHMy, char *units) {
588 	double rotation, scale, scaleX, scaleY;
589 	point shift;
590 	double inliers;
591 
592 	/* Matching information */
593 	siril_log_color_message(_("Matching stars in image %d: done\n"), "green", filenum);
594 	gchar *str = ngettext("%d pair match.\n", "%d pair matches.\n", H.pair_matched);
595 	str = g_strdup_printf(str, H.pair_matched);
596 	siril_log_message(str);
597 	g_free(str);
598 	inliers = 1.0 - ((((double) H.pair_matched - (double) H.Inliers)) / (double) H.pair_matched);
599 	siril_log_message(_("Inliers:%*.3f\n"), 11, inliers);
600 
601 	/* Scale */
602 	scaleX = sqrt(H.h00 * H.h00 + H.h01 * H.h01);
603 	scaleY = sqrt(H.h10 * H.h10 + H.h11 * H.h11);
604 	scale = (scaleX + scaleY) * 0.5;
605 	siril_log_message(_("scaleX:%*.3f\n"), 12, scaleX);
606 	siril_log_message(_("scaleY:%*.3f\n"), 12, scaleY);
607 	siril_log_message(_("scale:%*.3f\n"), 13, scale);
608 
609 	/* Rotation */
610 	rotation = atan2(H.h01, H.h00) * 180 / M_PI;
611 	siril_log_message(_("rotation:%+*.3f deg\n"), 9, rotation);
612 
613 	/* Translation */
614 	shift.x = -H.h02;
615 	shift.y = -H.h12;
616 	siril_log_message(_("dx:%+*.2f px\n"), 15, shift.x);
617 	siril_log_message(_("dy:%+*.2f px\n"), 15, shift.y);
618 	siril_log_message(_("FWHMx:%*.2f %s\n"), 12, FWHMx, units);
619 	siril_log_message(_("FWHMy:%*.2f %s\n"), 12, FWHMy, units);
620 }
621 
622