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 <stdint.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <dirent.h>
26 #include <math.h>
27 #include <string.h>
28 #include <gsl/gsl_histogram.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <opencv2/core/version.hpp>
32 #include <glib.h>
33 #include <libgen.h>
34 
35 #include "core/siril.h"
36 #include "core/proto.h"
37 #include "core/arithm.h"
38 #include "core/undo.h"
39 #include "core/initfile.h"
40 #include "core/preprocess.h"
41 #include "core/processing.h"
42 #include "core/sequence_filtering.h"
43 #include "core/OS_utils.h"
44 #include "io/conversion.h"
45 #include "io/image_format_fits.h"
46 #include "io/sequence.h"
47 #include "io/single_image.h"
48 #include "gui/utils.h"
49 #include "gui/callbacks.h"
50 #include "gui/PSF_list.h"
51 #include "gui/histogram.h"
52 #include "gui/plot.h"
53 #include "gui/progress_and_log.h"
54 #include "gui/image_display.h"
55 #include "gui/image_interactions.h"
56 #include "gui/linear_match.h"
57 #include "gui/sequence_list.h"
58 #include "gui/siril_preview.h"
59 #include "gui/script_menu.h"
60 #include "filters/asinh.h"
61 #include "filters/banding.h"
62 #include "filters/clahe.h"
63 #include "filters/cosmetic_correction.h"
64 #include "filters/deconv.h"
65 #include "filters/median.h"
66 #include "filters/fft.h"
67 #include "filters/rgradient.h"
68 #include "filters/saturation.h"
69 #include "filters/scnr.h"
70 #include "filters/wavelets.h"
71 #include "algos/PSF.h"
72 #include "algos/star_finder.h"
73 #include "algos/Def_Math.h"
74 #include "algos/Def_Wavelet.h"
75 #include "algos/background_extraction.h"
76 #include "algos/demosaicing.h"
77 #include "algos/extraction.h"
78 #include "algos/colors.h"
79 #include "algos/quality.h"
80 #include "algos/noise.h"
81 #include "algos/statistics.h"
82 #include "algos/sorting.h"
83 #include "algos/siril_wcs.h"
84 #include "algos/geometry.h"
85 #include "opencv/opencv.h"
86 #include "stacking/stacking.h"
87 #include "stacking/sum.h"
88 #include "registration/registration.h"
89 #include "registration/matching/match.h"
90 #include "algos/fix_xtrans_af.h"
91 #include "algos/annotate.h"
92 
93 #include "command.h"
94 #include "command_def.h"
95 #include "command_list.h"
96 #include "command_line_processor.h"
97 
98 #define PRINT_NOT_FOR_SEQUENCE siril_log_message(_("This command cannot be applied on a sequence.\n"))
99 #define PRINT_NOT_FOR_SINGLE siril_log_message(_("This command can only be used when a sequence is loaded.\n"))
100 
101 char *word[MAX_COMMAND_WORDS];	// NULL terminated
102 
process_load(int nb)103 int process_load(int nb){
104 	char filename[256];
105 
106 	strncpy(filename, word[1], 250);
107 	filename[250] = '\0';
108 
109 	for (int i = 1; i < nb - 1; ++i) {
110 		strcat(filename, " ");
111 		strcat(filename, word[i + 1]);
112 	}
113 	expand_home_in_filename(filename, 256);
114 	int retval = open_single_image(filename);
115 	return (retval < 0);
116 }
117 
process_satu(int nb)118 int process_satu(int nb){
119 	if (get_thread_run()) {
120 		PRINT_ANOTHER_THREAD_RUNNING;
121 		return 1;
122 	}
123 	if (!single_image_is_loaded()) {
124 		PRINT_NOT_FOR_SEQUENCE;
125 		return 1;
126 	}
127 
128 	struct enhance_saturation_data *args = malloc(sizeof(struct enhance_saturation_data));
129 
130 	args->coeff = g_ascii_strtod(word[1], NULL);
131 
132 	args->input = &gfit;
133 	args->output = &gfit;
134 	args->h_min = 0.0;
135 	args->h_max = 360.0;
136 	args->background_factor = 1.0;
137 
138 	enhance_saturation(args);
139 
140 	adjust_cutoff_from_updated_gfit();
141 	redraw(com.cvport, REMAP_ALL);
142 	redraw_previews();
143 
144 	return 0;
145 }
146 
process_save(int nb)147 int process_save(int nb){
148 	if (sequence_is_loaded() && !single_image_is_loaded()) {
149 		gfit.hi = com.seq.layers[RLAYER].hi;
150 		gfit.lo = com.seq.layers[RLAYER].lo;
151 	}
152 	else if (single_image_is_loaded()) {
153 		gfit.hi = com.uniq->layers[RLAYER].hi;
154 		gfit.lo = com.uniq->layers[RLAYER].lo;
155 	} else {
156 		return 1;
157 	}
158 
159 	gchar *filename = g_strdup(word[1]);
160 	set_cursor_waiting(TRUE);
161 	int retval = savefits(filename, &(gfit));
162 	set_precision_switch();
163 	set_cursor_waiting(FALSE);
164 	g_free(filename);
165 	return retval;
166 }
167 
process_savebmp(int nb)168 int process_savebmp(int nb){
169 	if (!single_image_is_loaded()) {
170 		PRINT_NOT_FOR_SEQUENCE;
171 		return 1;
172 	}
173 
174 	gchar *filename = g_strdup_printf("%s.bmp", word[1]);
175 
176 	set_cursor_waiting(TRUE);
177 	savebmp(filename, &(gfit));
178 	set_cursor_waiting(FALSE);
179 	g_free(filename);
180 	return 0;
181 }
182 
183 #ifdef HAVE_LIBJPEG
process_savejpg(int nb)184 int process_savejpg(int nb){
185 	if (!single_image_is_loaded()) {
186 		PRINT_NOT_FOR_SEQUENCE;
187 		return 1;
188 	}
189 
190 	int quality = 100;
191 
192 	if ((nb == 3) && g_ascii_strtoull(word[2], NULL, 10) <= 100 && g_ascii_strtoull(word[2], NULL, 10) > 0)
193 		quality = g_ascii_strtoull(word[2], NULL, 10);
194 
195 	gchar *filename = g_strdup_printf("%s.jpg", word[1]);
196 
197 	set_cursor_waiting(TRUE);
198 	savejpg(filename, &gfit, quality);
199 	set_cursor_waiting(FALSE);
200 	g_free(filename);
201 	return 0;
202 }
203 #endif
204 
205 #ifdef HAVE_LIBPNG
process_savepng(int nb)206 int process_savepng(int nb){
207 
208 	if (!single_image_is_loaded()) {
209 		PRINT_NOT_FOR_SEQUENCE;
210 		return 1;
211 	}
212 
213 	gchar *filename = g_strdup_printf("%s.png", word[1]);
214 
215 	set_cursor_waiting(TRUE);
216 	uint32_t bytes_per_sample = gfit.orig_bitpix != BYTE_IMG ? 2 : 1;
217 	savepng(filename, &gfit, bytes_per_sample, gfit.naxes[2] == 3);
218 	set_cursor_waiting(FALSE);
219 	g_free(filename);
220 	return 0;
221 }
222 #endif
223 
224 #ifdef HAVE_LIBTIFF
process_savetif(int nb)225 int process_savetif(int nb){
226 	uint16_t bitspersample = 16;
227 
228 	if (!single_image_is_loaded()) {
229 		PRINT_NOT_FOR_SEQUENCE;
230 		return 1;
231 	}
232 
233 	if (strcasecmp(word[0], "savetif8") == 0)
234 		bitspersample = 8;
235 	else if (strcasecmp(word[0], "savetif32") == 0)
236 		bitspersample = 32;
237 	gchar *filename = g_strdup_printf("%s.tif", word[1]);
238 	set_cursor_waiting(TRUE);
239 	savetif(filename, &gfit, bitspersample);
240 	set_cursor_waiting(FALSE);
241 	g_free(filename);
242 	return 0;
243 }
244 #endif
245 
process_savepnm(int nb)246 int process_savepnm(int nb){
247 	if (!single_image_is_loaded()) {
248 		PRINT_NOT_FOR_SEQUENCE;
249 		return 1;
250 	}
251 
252 	saveNetPBM(word[1], &gfit);
253 	return 0;
254 }
255 
process_imoper(int nb)256 int process_imoper(int nb){
257 	fits fit = { 0 };
258 	if (!single_image_is_loaded()) return 1;
259 	if (readfits(word[1], &fit, NULL, !com.pref.force_to_16bit)) return -1;
260 
261 	image_operator oper;
262 	switch (word[0][1]) {
263 		case 'a':
264 		case 'A':
265 			oper = OPER_ADD;
266 			break;
267 		case 's':
268 		case 'S':
269 			oper = OPER_SUB;
270 			break;
271 		case 'm':
272 		case 'M':
273 			oper = OPER_MUL;
274 			break;
275 		case 'd':
276 		case 'D':
277 			oper = OPER_DIV;
278 			break;
279 		default:
280 			siril_log_color_message(_("Could not understand the requested operator\n"), "red");
281 			clearfits(&fit);
282 			return 1;
283 	}
284 	int retval = imoper(&gfit, &fit, oper, !com.pref.force_to_16bit);
285 
286 	clearfits(&fit);
287 	adjust_cutoff_from_updated_gfit();
288 	redraw(com.cvport, REMAP_ALL);
289 	redraw_previews();
290 	return retval;
291 }
292 
process_addmax(int nb)293 int process_addmax(int nb){
294 	fits fit = { 0 };
295 
296 	if (!single_image_is_loaded()) {
297 		PRINT_NOT_FOR_SEQUENCE;
298 		return 1;
299 	}
300 
301 	if (readfits(word[1], &fit, NULL, gfit.type == DATA_FLOAT))
302 		return -1;
303 	if (addmax(&gfit, &fit) == 0) {
304 		adjust_cutoff_from_updated_gfit();
305 		redraw(com.cvport, REMAP_ALL);
306 		redraw_previews();
307 	}
308 	clearfits(&fit);
309 	return 0;
310 }
311 
process_fdiv(int nb)312 int process_fdiv(int nb){
313 	// combines an image division and a scalar multiplication.
314 	fits fit = { 0 };
315 	if (!single_image_is_loaded()) {
316 		PRINT_NOT_FOR_SEQUENCE;
317 		return 1;
318 	}
319 
320 	float norm = g_ascii_strtod(word[2], NULL);
321 	if (readfits(word[1], &fit, NULL, !com.pref.force_to_16bit)) return -1;
322 	siril_fdiv(&gfit, &fit, norm, TRUE);
323 
324 	clearfits(&fit);
325 	adjust_cutoff_from_updated_gfit();
326 	redraw(com.cvport, REMAP_ALL);
327 	redraw_previews();
328 	return 0;
329 }
330 
process_fmul(int nb)331 int process_fmul(int nb){
332 	if (!single_image_is_loaded()) {
333 		PRINT_NOT_FOR_SEQUENCE;
334 		return 1;
335 	}
336 
337 	float coeff = g_ascii_strtod(word[1], NULL);
338 	if (coeff <= 0.f) {
339 		siril_log_message(_("Multiplying by a coefficient less than or equal to 0 is not possible.\n"));
340 		return 1;
341 	}
342 	soper(&gfit, coeff, OPER_MUL, TRUE);
343 
344 	adjust_cutoff_from_updated_gfit();
345 	redraw(com.cvport, REMAP_ALL);
346 	redraw_previews();
347 	return 0;
348 }
349 
process_entropy(int nb)350 int process_entropy(int nb){
351 	rectangle area;
352 	float e;
353 
354 	if (!single_image_is_loaded()) {
355 		PRINT_NOT_FOR_SEQUENCE;
356 		return 1;
357 	}
358 
359 	if (com.selection.w > 0 && com.selection.h > 0) {
360 		memcpy(&area, &com.selection, sizeof(rectangle));
361 		e = entropy(&gfit, com.cvport, &area, NULL);
362 	}
363 	else {
364 		e = entropy(&gfit, com.cvport, NULL, NULL);
365 	}
366 	siril_log_message(_("Entropy: %.3f\n"), e);
367 	return 0;
368 }
369 
process_gauss(int nb)370 int process_gauss(int nb){
371 	if (!single_image_is_loaded()) {
372 		PRINT_NOT_FOR_SEQUENCE;
373 		return 1;
374 	}
375 
376 	unsharp(&(gfit), g_ascii_strtod(word[1], NULL), 0.0, TRUE);
377 	adjust_cutoff_from_updated_gfit();
378 	redraw(com.cvport, REMAP_ALL);
379 	redraw_previews();
380 	return 0;
381 }
382 
process_grey_flat(int nb)383 int process_grey_flat(int nb) {
384 	if (!single_image_is_loaded()) {
385 		PRINT_NOT_FOR_SEQUENCE;
386 		return 1;
387 	}
388 
389 	compute_grey_flat(&gfit);
390 	adjust_cutoff_from_updated_gfit();
391 	redraw(com.cvport, REMAP_ALL);
392 	redraw_previews();
393 
394 	return 0;
395 }
396 
process_rl(int nb)397 int process_rl(int nb) {
398 	double sigma, corner;
399 	int iter;
400 
401 	if (!single_image_is_loaded()) {
402 		PRINT_NOT_FOR_SEQUENCE;
403 		return 1;
404 	}
405 
406 	if (!com.script)
407 		control_window_switch_to_tab(OUTPUT_LOGS);
408 	sigma = g_ascii_strtod(word[1], NULL);
409 	corner = g_ascii_strtod(word[2], NULL);
410 	iter = g_ascii_strtoull(word[3], NULL, 10);
411 
412 	if (sigma < 0.4 || sigma > 2.0) {
413 		siril_log_message(_("Sigma must be between [0.4, 2.0]\n"));
414 		return 1;
415 	}
416 
417 	if (corner < -0.5 || corner > 0.5) {
418 		siril_log_message(_("Corner radius boost must be between [0.5, 0.5]\n"));
419 		return 1;
420 	}
421 
422 	if (iter <= 0) {
423 		siril_log_message(_("Number of iterations must be > 0.\n"));
424 		return 1;
425 	}
426 
427 	if (get_thread_run()) {
428 		PRINT_ANOTHER_THREAD_RUNNING;
429 		return 1;
430 	}
431 
432 	struct deconv_data *args = malloc(sizeof(struct deconv_data));
433 
434 	args->fit = &gfit;
435 	if (args->fit->type == DATA_USHORT) {
436 		args->clip = (args->fit->maxi <= 0) ? USHRT_MAX_DOUBLE : args->fit->maxi;
437 	} else {
438 		args->clip = (args->fit->maxi <= 0) ? USHRT_MAX_DOUBLE : args->fit->maxi * USHRT_MAX_DOUBLE;
439 	}
440 	args->auto_contrast_threshold = TRUE;
441 	args->sigma = sigma;
442 	args->corner_radius = corner;
443 	args->iterations = (size_t)iter;
444 	args->auto_limit = TRUE;
445 
446 	set_cursor_waiting(TRUE);
447 
448 	start_in_new_thread(RTdeconv, args);
449 
450 	return 0;
451 }
452 
process_unsharp(int nb)453 int process_unsharp(int nb) {
454 	if (!single_image_is_loaded()) {
455 		PRINT_NOT_FOR_SEQUENCE;
456 		return 1;
457 	}
458 
459 	unsharp(&(gfit), g_ascii_strtod(word[1], NULL), g_ascii_strtod(word[2], NULL), TRUE);
460 	adjust_cutoff_from_updated_gfit();
461 	redraw(com.cvport, REMAP_ALL);
462 	redraw_previews();
463 	return 0;
464 }
465 
process_crop(int nb)466 int process_crop(int nb) {
467 	if (!single_image_is_loaded()) {
468 		PRINT_NOT_FOR_SEQUENCE;
469 		return 1;
470 	}
471 	if (is_preview_active()) {
472 		siril_log_message(_("It is impossible to crop the image when a filter with preview session is active. "
473 						"Please consider to close the filter dialog first.\n"));
474 		return 1;
475 	}
476 
477 
478 	rectangle area;
479 	if ((!com.selection.h) || (!com.selection.w)) {
480 		if (nb == 5) {
481 			if (g_ascii_strtoull(word[1], NULL, 10) < 0 || g_ascii_strtoull(word[2], NULL, 10) < 0) {
482 				siril_log_message(_("Crop: x and y must be positive values.\n"));
483 				return 1;
484 			}
485 			if (g_ascii_strtoull(word[3], NULL, 10) <= 0 || g_ascii_strtoull(word[4], NULL, 10) <= 0) {
486 				siril_log_message(_("Crop: width and height must be greater than 0.\n"));
487 				return 1;
488 			}
489 			if (g_ascii_strtoull(word[1], NULL, 10) + g_ascii_strtoull(word[3], NULL, 10) > gfit.rx || g_ascii_strtoull(word[2], NULL, 10) + g_ascii_strtoull(word[4], NULL, 10) > gfit.ry) {
490 				siril_log_message(_("Crop: width and height, respectively, must be less than %d and %d.\n"), gfit.rx,gfit.ry);
491 				return 1;
492 			}
493 			area.x = g_ascii_strtoull(word[1], NULL, 10);
494 			area.y = g_ascii_strtoull(word[2], NULL, 10);
495 			area.w = g_ascii_strtoull(word[3], NULL, 10);
496 			area.h = g_ascii_strtoull(word[4], NULL, 10);
497 		}
498 		else {
499 			siril_log_message(_("Crop: select a region or provide x, y, width, height\n"));
500 			return 1;
501 		}
502 	} else {
503 		memcpy(&area, &com.selection, sizeof(rectangle));
504 	}
505 
506 	crop(&gfit, &area);
507 	delete_selected_area();
508 	reset_display_offset();
509 	adjust_cutoff_from_updated_gfit();
510 	redraw(com.cvport, REMAP_ALL);
511 	redraw_previews();
512 
513 	return 0;
514 }
515 
process_cd(int nb)516 int process_cd(int nb) {
517 	char filename[256];
518 	int retval;
519 
520 	g_strlcpy(filename, word[1], 250);
521 
522 	expand_home_in_filename(filename, 256);
523 	retval = siril_change_dir(filename, NULL);
524 	if (!retval) {
525 		writeinitfile();
526 		if (!com.script) {
527 			set_GUI_CWD();
528 		}
529 	}
530 	return retval;
531 }
532 
process_wrecons(int nb)533 int process_wrecons(int nb) {
534 	int i;
535 	float coef[7];
536 	char *File_Name_Transform[3] = { "r_rawdata.wave", "g_rawdata.wave",
537 			"b_rawdata.wave" }, *dir[3];
538 	const char *tmpdir;
539 	int nb_chan;
540 
541 	if (!single_image_is_loaded()) {
542 		PRINT_NOT_FOR_SEQUENCE;
543 		return 1;
544 	}
545 
546 	nb_chan = gfit.naxes[2];
547 
548 	g_assert(nb_chan == 1 || nb_chan == 3);
549 
550 	tmpdir = g_get_tmp_dir();
551 
552 	for (i = 0; i < nb - 1; ++i) {
553 		coef[i] = g_ascii_strtod(word[i + 1], NULL);
554 	}
555 
556 	for (i = 0; i < nb_chan; i++) {
557 		dir[i] = g_build_filename(tmpdir, File_Name_Transform[i], NULL);
558 		if (gfit.type == DATA_USHORT) {
559 			wavelet_reconstruct_file(dir[i], coef, gfit.pdata[i]);
560 		} else if (gfit.type == DATA_FLOAT) {
561 			wavelet_reconstruct_file_float(dir[i], coef, gfit.fpdata[i]);
562 		}
563 		else return 1;
564 		g_free(dir[i]);
565 	}
566 
567 	adjust_cutoff_from_updated_gfit();
568 	redraw(com.cvport, REMAP_ALL);
569 	redraw_previews();
570 	return 0;
571 }
572 
process_wavelet(int nb)573 int process_wavelet(int nb) {
574 	char *File_Name_Transform[3] = { "r_rawdata.wave", "g_rawdata.wave",
575 			"b_rawdata.wave" }, *dir[3];
576 	const char* tmpdir;
577 	int Type_Transform, Nbr_Plan, maxplan, mins, chan, nb_chan;
578 
579 	if (!single_image_is_loaded()) {
580 		PRINT_NOT_FOR_SEQUENCE;
581 		return 1;
582 	}
583 
584 	tmpdir = g_get_tmp_dir();
585 
586 	Nbr_Plan = g_ascii_strtoull(word[1], NULL, 10);
587 	Type_Transform = g_ascii_strtoull(word[2], NULL, 10);
588 
589 	nb_chan = gfit.naxes[2];
590 	g_assert(nb_chan <= 3);
591 
592 	mins = min (gfit.rx, gfit.ry);
593 	maxplan = log(mins) / log(2) - 2;
594 
595 	if ( Nbr_Plan > maxplan ){
596 		siril_log_message(_("Wavelet: maximum number of plans for this image size is %d\n"),
597 				maxplan);
598 		return 1;
599 	}
600 
601 	if(Type_Transform != TO_PAVE_LINEAR && Type_Transform != TO_PAVE_BSPLINE){
602 		siril_log_message(_("Wavelet: type must be %d or %d\n"), TO_PAVE_LINEAR, TO_PAVE_BSPLINE);
603 		return 1;
604 	}
605 
606 	if (gfit.type == DATA_USHORT) {
607 		float *Imag = f_vector_alloc(gfit.rx * gfit.ry);
608 		if (!Imag) {
609 			PRINT_ALLOC_ERR;
610 			return 1;
611 		}
612 
613 		for (chan = 0; chan < nb_chan; chan++) {
614 			dir[chan] = g_build_filename(tmpdir, File_Name_Transform[chan], NULL);
615 			wavelet_transform_file(Imag, gfit.ry, gfit.rx, dir[chan],
616 					Type_Transform, Nbr_Plan, gfit.pdata[chan]);
617 			g_free(dir[chan]);
618 		}
619 
620 		free(Imag);
621 	} else if (gfit.type == DATA_FLOAT) {
622 		for (chan = 0; chan < nb_chan; chan++) {
623 			dir[chan] = g_build_filename(tmpdir, File_Name_Transform[chan], NULL);
624 			wavelet_transform_file_float(gfit.fpdata[chan], gfit.ry, gfit.rx, dir[chan],
625 					Type_Transform, Nbr_Plan);
626 			g_free(dir[chan]);
627 		}
628 	}
629 	else return 1;
630 	return 0;
631 }
632 
process_log(int nb)633 int process_log(int nb){
634 	if (!single_image_is_loaded()) {
635 		PRINT_NOT_FOR_SEQUENCE;
636 		return 1;
637 	}
638 
639 	loglut(&gfit);
640 	adjust_cutoff_from_updated_gfit();
641 	redraw(com.cvport, REMAP_ALL);
642 	redraw_previews();
643 	return 0;
644 }
645 
process_linear_match(int nb)646 int process_linear_match(int nb) {
647 	if (!single_image_is_loaded()) {
648 		PRINT_NOT_FOR_SEQUENCE;
649 		return 1;
650 	}
651 	fits ref = { 0 };
652 	double a[3] = { 0.0 }, b[3] = { 0.0 };
653 	double low = g_ascii_strtod(word[2], NULL);
654 	double high = g_ascii_strtod(word[3], NULL);
655 	if (readfits(word[1], &ref, NULL, gfit.type == DATA_FLOAT))
656 		return 1;
657 	set_cursor_waiting(TRUE);
658 	if (!find_linear_coeff(&gfit, &ref, low, high, a, b, NULL)) {
659 		apply_linear_to_fits(&gfit, a, b);
660 
661 		adjust_cutoff_from_updated_gfit();
662 		redraw(com.cvport, REMAP_ALL);
663 		redraw_previews();
664 	}
665 	clearfits(&ref);
666 	set_cursor_waiting(FALSE);
667 	return 0;
668 }
669 
process_asinh(int nb)670 int process_asinh(int nb) {
671 	if (!single_image_is_loaded()) {
672 		PRINT_NOT_FOR_SEQUENCE;
673 		return 1;
674 	}
675 
676 	double beta = g_ascii_strtod(word[1], NULL);
677 
678 	asinhlut(&gfit, beta, 0, FALSE);
679 	adjust_cutoff_from_updated_gfit();
680 	redraw(com.cvport, REMAP_ALL);
681 	redraw_previews();
682 	return 0;
683 }
684 
process_clahe(int nb)685 int process_clahe(int nb) {
686 	double clip_limit;
687 	int size;
688 
689 	if (CV_MAJOR_VERSION < 3) {
690 		siril_log_message(_("Your version of opencv is "
691 				"too old for this feature. Please upgrade your system."));
692 		return 1;
693 	}
694 
695 	if (!single_image_is_loaded()) {
696 		PRINT_NOT_FOR_SEQUENCE;
697 		return 1;
698 	}
699 
700 	if (!com.script)
701 		control_window_switch_to_tab(OUTPUT_LOGS);
702 	clip_limit = g_ascii_strtod(word[1], NULL);
703 
704 	if (clip_limit <= 0.0) {
705 		siril_log_message(_("Clip limit must be > 0.\n"));
706 		return 1;
707 	}
708 
709 	size = g_ascii_strtoull(word[2], NULL, 10);
710 
711 	if (size <= 0.0) {
712 		siril_log_message(_("Tile size must be > 0.\n"));
713 		return 1;
714 	}
715 
716 	if (get_thread_run()) {
717 		PRINT_ANOTHER_THREAD_RUNNING;
718 		return 1;
719 	}
720 
721 	struct CLAHE_data *args = malloc(sizeof(struct CLAHE_data));
722 
723 	args->fit = &gfit;
724 	args->clip = clip_limit;
725 	args->tileSize = size;
726 
727 	set_cursor_waiting(TRUE);
728 
729 	start_in_new_thread(clahe, args);
730 	adjust_cutoff_from_updated_gfit();
731 	redraw(com.cvport, REMAP_ALL);
732 	redraw_previews();
733 
734 	return 0;
735 }
736 
737 #ifndef _WIN32
process_ls(int nb)738 int process_ls(int nb){
739 	struct dirent **list;
740 	gchar *path = NULL;
741 
742 	/* If a path is given in argument */
743 	if (nb > 1) {
744 		if (word[1][0] != '\0') {
745 			/* Absolute path */
746 			if (word[1][0] == G_DIR_SEPARATOR || word[1][0] == '~') {
747 				char filename[256];
748 
749 				g_strlcpy(filename, word[1], 250);
750 				filename[250] = '\0';
751 				expand_home_in_filename(filename, 256);
752 				path = g_build_filename(filename, NULL);
753 			}
754 			/* Relative path */
755 			else {
756 				path = g_build_filename(com.wd, word[1], NULL);
757 			}
758 		}
759 		/* Should not happen */
760 		else {
761 			printf("Cannot list files in %s\n", word[1]);
762 			return 1;
763 		}
764 	}
765 	/* No paths are given in argument */
766 	else {
767 		if (!com.wd) {
768 			siril_log_message(_("Cannot list files, set working directory first.\n"));
769 			return 1;
770 		}
771 		path = g_strdup(com.wd);
772 	}
773 	if (path == NULL) {
774 		siril_log_message(_("Siril cannot open the directory.\n"));
775 		return 1;
776 	}
777 
778 	int i, n;
779 
780 	n = scandir(path, &list, 0, alphasort);
781 	if (n < 0) {
782 		perror("scandir");
783 		siril_log_message(_("Siril cannot open the directory.\n"));
784 		g_free(path);
785 		return 1;
786 	}
787 
788 	/* List the entries */
789 	for (i = 0; i < n; ++i) {
790 		GStatBuf entrystat;
791 		gchar *filename;
792 		const char *ext;
793 		if (list[i]->d_name[0] == '.')
794 			continue; /* no hidden files */
795 
796 		filename = g_build_filename(path, list[i]->d_name, NULL);
797 
798 		if (g_lstat(filename, &entrystat)) {
799 			perror("stat");
800 			g_free(filename);
801 			break;
802 		}
803 		g_free(filename);
804 		if (S_ISLNK(entrystat.st_mode)) {
805 			siril_log_color_message(_("Link: %s\n"), "bold", list[i]->d_name);
806 			continue;
807 		}
808 		if (S_ISDIR(entrystat.st_mode)) {
809 			siril_log_color_message(_("Directory: %s\n"), "green",
810 					list[i]->d_name);
811 			continue;
812 		}
813 		ext = get_filename_ext(list[i]->d_name);
814 		if (!ext)
815 			continue;
816 		image_type type = get_type_for_extension(ext);
817 		if (type != TYPEUNDEF) {
818 			if (type == TYPEAVI || type == TYPESER)
819 				siril_log_color_message(_("Sequence: %s\n"), "salmon",
820 						list[i]->d_name);
821 			else if (type == TYPEFITS)
822 				siril_log_color_message(_("Image: %s\n"), "plum", list[i]->d_name);
823 			else
824 				siril_log_color_message(_("Image: %s\n"), "red", list[i]->d_name);
825 		} else if (!strncmp(ext, "seq", 3))
826 			siril_log_color_message(_("Sequence: %s\n"), "blue", list[i]->d_name);
827 	}
828 	for (i = 0; i < n; i++)
829 		free(list[i]);
830 	free(list);
831 	siril_log_message(_("********* END OF THE LIST *********\n"));
832 	g_free(path);
833 
834 	return 0;
835 }
836 #endif
837 
process_merge(int nb)838 int process_merge(int nb) {
839 	int retval = 0, nb_seq = nb-2;
840 	if (!com.wd) {
841 		siril_log_message(_("Merge: no working directory set.\n"));
842 		set_cursor_waiting(FALSE);
843 		return 1;
844 	}
845 	char *dest_dir = strdup(com.wd);
846 	sequence **seqs = calloc(nb_seq, sizeof(sequence *));
847 	GList *list = NULL;
848 	for (int i = 0; i < nb_seq; i++) {
849 		char *seqpath1 = strdup(word[i + 1]), *seqpath2 = strdup(word[i + 1]);
850 		char *dir = g_path_get_dirname(seqpath1);
851 		char *seqname = g_path_get_basename(seqpath2);
852 #ifdef _WIN32
853 		gchar **token = g_strsplit(dir, "/", -1);
854 		g_free(dir);
855 		dir = g_strjoinv(G_DIR_SEPARATOR_S, token);
856 		g_strfreev(token);
857 #endif
858 		if (dir[0] != '\0' && !(dir[0] == '.' && dir[1] == '\0'))
859 			siril_change_dir(dir, NULL);
860 		if (!(seqs[i] = load_sequence(seqname, NULL))) {
861 			siril_log_message(_("Could not open sequence `%s' for merging\n"), word[i + 1]);
862 			retval = 1;
863 			free(seqpath1); free(seqpath2);	g_free(seqname); g_free(dir);
864 			goto merge_clean_up;
865 		}
866 		g_free(seqname);
867 		if (seq_check_basic_data(seqs[i], FALSE) < 0) {
868 			siril_log_message(_("Sequence `%s' is invalid, could not merge\n"), word[i + 1]);
869 			retval = 1;
870 			free(seqpath1); free(seqpath2); g_free(dir);
871 			goto merge_clean_up;
872 		}
873 
874 		if (i != 0 && (seqs[i]->rx != seqs[0]->rx ||
875 					seqs[i]->ry != seqs[0]->ry ||
876 					seqs[i]->nb_layers != seqs[0]->nb_layers ||
877 					seqs[i]->bitpix != seqs[0]->bitpix ||
878 					seqs[i]->type != seqs[0]->type)) {
879 			siril_log_message(_("All sequences must be the same format for merging. Sequence `%s' is different\n"), word[i + 1]);
880 			retval = 1;
881 			free(seqpath1); free(seqpath2); g_free(dir);
882 			goto merge_clean_up;
883 		}
884 
885 		if (seqs[i]->type == SEQ_REGULAR) {
886 			// we need to build the list of files
887 			char filename[256];
888 			for (int image = 0; image < seqs[i]->number; image++) {
889 				fit_sequence_get_image_filename(seqs[i], image, filename, TRUE);
890 				list = g_list_append(list, g_build_filename(dir, filename, NULL));
891 			}
892 		}
893 		free(seqpath1); free(seqpath2); g_free(dir);
894 		siril_change_dir(dest_dir, NULL);	// they're all relative to this one
895 	}
896 
897 	char *outseq_name;
898 	struct ser_struct out_ser;
899 	struct _convert_data *args;
900 	fitseq out_fitseq;
901 	switch (seqs[0]->type) {
902 		case SEQ_REGULAR:
903 			// use the conversion, it makes symbolic links or copies as a fallback
904 			args = malloc(sizeof(struct _convert_data));
905 			args->start = 0;
906 			args->total = 0; // init to get it from glist_to_array()
907 			args->list = glist_to_array(list, &args->total);
908 			args->destroot = format_basename(word[nb - 1], FALSE);
909 			args->input_has_a_seq = FALSE;
910 			args->input_has_a_film = FALSE;
911 			args->debayer = FALSE;
912 			args->multiple_output = FALSE;
913 			args->output_type = SEQ_REGULAR;
914 			args->make_link = TRUE;
915 			gettimeofday(&(args->t_start), NULL);
916 			start_in_new_thread(convert_thread_worker, args);
917 			break;
918 
919 		case SEQ_SER:
920 			if (g_str_has_suffix(word[nb - 1], ".ser"))
921 				outseq_name = g_strdup(word[nb - 1]);
922 			else outseq_name = g_strdup_printf("%s.ser", word[nb - 1]);
923 			if (ser_create_file(outseq_name, &out_ser, TRUE, seqs[0]->ser_file)) {
924 				siril_log_message(_("Failed to create the output SER file `%s'\n"), word[nb - 1]);
925 				retval = 1;
926 				goto merge_clean_up;
927 			}
928 			free(outseq_name);
929 			seqwriter_set_max_active_blocks(2);
930 			int written_frames = 0;
931 			for (int i = 0; i < nb_seq; i++) {
932 				for (unsigned int frame = 0; frame < seqs[i]->number; frame++) {
933 					seqwriter_wait_for_memory();
934 					fits *fit = calloc(1, sizeof(fits));
935 					if (ser_read_frame(seqs[i]->ser_file, frame, fit, FALSE, com.pref.debayer.open_debayer)) {
936 						siril_log_message(_("Failed to read frame %d from input sequence `%s'\n"), frame, word[i + 1]);
937 						retval = 1;
938 						seqwriter_release_memory();
939 						ser_close_and_delete_file(&out_ser);
940 						goto merge_clean_up;
941 					}
942 
943 					if (ser_write_frame_from_fit(&out_ser, fit, written_frames)) {
944 						siril_log_message(_("Failed to write frame %d in merged sequence\n"), written_frames);
945 						retval = 1;
946 						seqwriter_release_memory();
947 						ser_close_and_delete_file(&out_ser);
948 						goto merge_clean_up;
949 					}
950 					written_frames++;
951 				}
952 			}
953 			if (ser_write_and_close(&out_ser)) {
954 				siril_log_message(_("Error while finalizing the merged sequence\n"));
955 				retval = 1;
956 			}
957 			break;
958 
959 		case SEQ_FITSEQ:
960 			if (g_str_has_suffix(word[nb - 1], com.pref.ext))
961 				outseq_name = g_strdup(word[nb - 1]);
962 			else outseq_name = g_strdup_printf("%s%s", word[nb - 1], com.pref.ext);
963 			if (fitseq_create_file(outseq_name, &out_fitseq, -1)) {
964 				siril_log_message(_("Failed to create the output SER file `%s'\n"), word[nb - 1]);
965 				retval = 1;
966 				goto merge_clean_up;
967 			}
968 			free(outseq_name);
969 			seqwriter_set_max_active_blocks(2);
970 			written_frames = 0;
971 			for (int i = 0; i < nb_seq; i++) {
972 				for (unsigned int frame = 0; frame < seqs[i]->number; frame++) {
973 					seqwriter_wait_for_memory();
974 					fits *fit = calloc(1, sizeof(fits));
975 					if (fitseq_read_frame(seqs[i]->fitseq_file, frame, fit, FALSE, -1)) {
976 						siril_log_message(_("Failed to read frame %d from input sequence `%s'\n"), frame, word[i + 1]);
977 						retval = 1;
978 						seqwriter_release_memory();
979 						fitseq_close_and_delete_file(&out_fitseq);
980 						goto merge_clean_up;
981 					}
982 
983 					if (fitseq_write_image(&out_fitseq, fit, written_frames)) {
984 						siril_log_message(_("Failed to write frame %d in merged sequence\n"), written_frames);
985 						retval = 1;
986 						seqwriter_release_memory();
987 						fitseq_close_and_delete_file(&out_fitseq);
988 						goto merge_clean_up;
989 					}
990 					written_frames++;
991 				}
992 			}
993 			if (fitseq_close_file(&out_fitseq)) {
994 				siril_log_message(_("Error while finalizing the merged sequence\n"));
995 				retval = 1;
996 			}
997 			break;
998 		default:
999 			siril_log_message(_("This type of sequence cannot be created by Siril, aborting the merge\n"));
1000 			retval = 1;
1001 	}
1002 
1003 merge_clean_up:
1004 	for (int i = 0; i < nb_seq; i++) {
1005 		if (seqs[i])
1006 			free_sequence(seqs[i], TRUE);
1007 	}
1008 	free(seqs);
1009 	siril_change_dir(dest_dir, NULL);
1010 	free(dest_dir);
1011 	return retval;
1012 }
1013 
process_mirrorx(int nb)1014 int	process_mirrorx(int nb){
1015 	if (!single_image_is_loaded()) {
1016 		PRINT_NOT_FOR_SEQUENCE;
1017 		return 1;
1018 	}
1019 
1020 	mirrorx(&gfit, TRUE);
1021 	redraw(com.cvport, REMAP_ALL);
1022 	redraw_previews();
1023 	return 0;
1024 }
1025 
process_mirrory(int nb)1026 int	process_mirrory(int nb){
1027 	if (!single_image_is_loaded()) {
1028 		PRINT_NOT_FOR_SEQUENCE;
1029 		return 1;
1030 	}
1031 
1032 	mirrory(&gfit, TRUE);
1033 	redraw(com.cvport, REMAP_ALL);
1034 	redraw_previews();
1035 	return 0;
1036 }
1037 
process_mtf(int nb)1038 int process_mtf(int nb) {
1039 	float lo = g_ascii_strtod(word[1], NULL);
1040 	float mid = g_ascii_strtod(word[2], NULL);
1041 	float hi = g_ascii_strtod(word[3], NULL);
1042 
1043 	mtf_with_parameters(&gfit, lo, mid, hi);
1044 
1045 	adjust_cutoff_from_updated_gfit();
1046 	redraw(com.cvport, REMAP_ALL);
1047 	redraw_previews();
1048 	return 0;
1049 }
1050 
process_resample(int nb)1051 int process_resample(int nb) {
1052 	if (!single_image_is_loaded()) {
1053 		PRINT_NOT_FOR_SEQUENCE;
1054 		return 1;
1055 	}
1056 
1057 	double factor = g_ascii_strtod(word[1], NULL);
1058 	if (factor > 5.0) {
1059 		siril_log_message(_("The scaling factor must be less than 5.0\n"));
1060 		return 1;
1061 	}
1062 	int toX = round_to_int(factor * gfit.rx);
1063 	int toY = round_to_int(factor * gfit.ry);
1064 
1065 	set_cursor_waiting(TRUE);
1066 	verbose_resize_gaussian(&gfit, toX, toY, OPENCV_AREA);
1067 
1068 	redraw(com.cvport, REMAP_ALL);
1069 	redraw_previews();
1070 	set_cursor_waiting(FALSE);
1071 	return 0;
1072 }
1073 
process_rgradient(int nb)1074 int process_rgradient(int nb) {
1075 	if (get_thread_run()) {
1076 		PRINT_ANOTHER_THREAD_RUNNING;
1077 		return 1;
1078 	}
1079 
1080 	if (!single_image_is_loaded()) {
1081 		PRINT_NOT_FOR_SEQUENCE;
1082 		return 1;
1083 	}
1084 
1085 	struct rgradient_filter_data *args = malloc(sizeof(struct rgradient_filter_data));
1086 	args->xc = g_ascii_strtod(word[1], NULL);
1087 	args->yc = g_ascii_strtod(word[2], NULL);
1088 	args->dR = g_ascii_strtod(word[3], NULL);
1089 	args->da = g_ascii_strtod(word[4], NULL);
1090 	args->fit = &gfit;
1091 
1092 	if ((args->xc >= args->fit->rx) || (args->yc >= args->fit->ry)) {
1093 		siril_log_message(_("The coordinates cannot be greater than the size of the image. "
1094 				"Please change their values and retry.\n"));
1095 	} else {
1096 
1097 		set_cursor_waiting(TRUE);
1098 
1099 		start_in_new_thread(rgradient_filter, args);
1100 	}
1101 	return 0;
1102 }
1103 
process_rotate(int nb)1104 int process_rotate(int nb) {
1105 	double degree;
1106 	int crop = 1;
1107 
1108 	if (!single_image_is_loaded()) {
1109 		PRINT_NOT_FOR_SEQUENCE;
1110 		return 1;
1111 	}
1112 
1113 	set_cursor_waiting(TRUE);
1114 
1115 	degree = g_ascii_strtod(word[1], NULL);
1116 
1117 	/* check for options */
1118 	if (word[2] && (!strcmp(word[2], "-nocrop"))) {
1119 		crop = 0;
1120 	}
1121 
1122 	verbose_rotate_image(&gfit, degree, OPENCV_AREA, crop);
1123 	redraw(com.cvport, REMAP_ALL);
1124 	redraw_previews();
1125 	set_cursor_waiting(FALSE);
1126 	return 0;
1127 }
1128 
process_rotatepi(int nb)1129 int process_rotatepi(int nb){
1130 	if (!single_image_is_loaded()) {
1131 		PRINT_NOT_FOR_SEQUENCE;
1132 		return 1;
1133 	}
1134 
1135 	verbose_rotate_image(&gfit, 180.0, OPENCV_AREA, 1);
1136 
1137 	redraw(com.cvport, REMAP_ALL);
1138 	redraw_previews();
1139 	return 0;
1140 }
1141 
process_set_mag(int nb)1142 int process_set_mag(int nb) {
1143 	if (!single_image_is_loaded()) {
1144 		PRINT_NOT_FOR_SEQUENCE;
1145 		return 1;
1146 	}
1147 
1148 	int layer = match_drawing_area_widget(com.vport[com.cvport], FALSE);
1149 	double mag = g_ascii_strtod(word[1], NULL);
1150 
1151 	if (layer != -1) {
1152 
1153 		if (com.selection.w > 300 || com.selection.h > 300){
1154 			siril_log_message(_("Current selection is too large. To determine the PSF, please make a selection around a single star.\n"));
1155 			return 1;
1156 		}
1157 		if (com.selection.w <= 0 || com.selection.h <= 0){
1158 			siril_log_message(_("Select an area first\n"));
1159 			return 1;
1160 		}
1161 		fitted_PSF *result = psf_get_minimisation(&gfit, layer, &com.selection, TRUE, TRUE, TRUE);
1162 		if (result) {
1163 			com.magOffset = mag - result->mag;
1164 			siril_log_message(_("Relative magnitude: %.3lf, "
1165 					"True reduced magnitude: %.3lf, "
1166 					"Offset: %.3lf\n"), result->mag, mag, com.magOffset);
1167 			free(result);
1168 		}
1169 	}
1170 	return 0;
1171 }
1172 
process_set_ref(int nb)1173 int process_set_ref(int nb) {
1174 	sequence *seq = load_sequence(word[1], NULL);
1175 	if (!seq)
1176 		return 1;
1177 
1178 	int n = g_ascii_strtoull(word[2], NULL, 10) - 1;
1179 	if (n < 0 || n > seq->number) {
1180 		siril_log_message(_("The reference image must be set between 1 and %d\n"), seq->number);
1181 		return 1;
1182 	}
1183 
1184 	seq->reference_image = n;
1185 	// a reference image should not be excluded to avoid confusion
1186 	if (!seq->imgparam[seq->current].incl) {
1187 		seq->imgparam[seq->current].incl = TRUE;
1188 	}
1189 
1190 	writeseqfile(seq);
1191 
1192 	return 0;
1193 }
1194 
process_unset_mag(int nb)1195 int process_unset_mag(int nb) {
1196 	com.magOffset = 0.0;
1197 	return 0;
1198 }
1199 
process_set_mag_seq(int nb)1200 int process_set_mag_seq(int nb) {
1201 	if (!sequence_is_loaded()) {
1202 		PRINT_NOT_FOR_SINGLE;
1203 		return 1;
1204 	}
1205 	double mag = g_ascii_strtod(word[1], NULL);
1206 	int i;
1207 	for (i = 0; i < MAX_SEQPSF && com.seq.photometry[i]; i++);
1208 	com.seq.reference_star = i - 1;
1209 	if (i == 0) {
1210 		siril_log_message(_("Run a PSF for the sequence first (see seqpsf)\n"));
1211 		return 1;
1212 	}
1213 	com.seq.reference_mag = mag;
1214 	siril_log_message(_("Reference magnitude has been set for star %d to %f and will be computed for each image\n"), i - 1, mag);
1215 	drawPlot();
1216 	return 0;
1217 }
1218 
process_set_ext(int nb)1219 int process_set_ext(int nb) {
1220 	if (word[1]) {
1221 		GString *str = NULL;
1222 
1223 		if ((g_ascii_strncasecmp(word[1], "fit", 3))
1224 				&& (g_ascii_strncasecmp(word[1], "fts", 3))
1225 				&& (g_ascii_strncasecmp(word[1], "fits", 4))) {
1226 			siril_log_message(_("FITS extension unknown: %s\n"), word[1]);
1227 		}
1228 
1229 		free(com.pref.ext);
1230 		str = g_string_new(".");
1231 		str = g_string_append(str, word[1]);
1232 		str = g_string_ascii_down(str);
1233 		com.pref.ext = g_string_free(str, FALSE);
1234 		writeinitfile();
1235 	}
1236 
1237 	return 0;
1238 }
1239 
process_set_findstar(int nb)1240 int process_set_findstar(int nb) {
1241 	double sigma = g_ascii_strtod(word[1], NULL);
1242 	double roundness = g_ascii_strtod(word[2], NULL);
1243 	int retval = 0;
1244 
1245 	if (sigma >= 0.05 && roundness >= 0 && roundness <= 0.9) {
1246 		com.starfinder_conf.sigma = sigma;
1247 		com.starfinder_conf.roundness = roundness;
1248 	} else {
1249 		siril_log_message(_("Wrong parameter values. Sigma must be >= 0.05 and roundness between 0 and 0.9.\n"));
1250 		retval = 1;
1251 	}
1252 	return retval;
1253 }
1254 
process_unset_mag_seq(int nb)1255 int process_unset_mag_seq(int nb) {
1256 	if (!sequence_is_loaded()) {
1257 		PRINT_NOT_FOR_SINGLE;
1258 		return 1;
1259 	}
1260 	com.seq.reference_star = -1;
1261 	com.seq.reference_mag = -1001.0;
1262 	siril_log_message(_("Reference magnitude unset for sequence\n"));
1263 	drawPlot();
1264 	return 0;
1265 }
1266 
process_psf(int nb)1267 int process_psf(int nb){
1268 	if (!single_image_is_loaded()) {
1269 		PRINT_NOT_FOR_SEQUENCE;
1270 		return 1;
1271 	}
1272 
1273 	int layer = match_drawing_area_widget(com.vport[com.cvport], FALSE);
1274 	if (layer != -1) {
1275 
1276 		if (com.selection.w > 300 || com.selection.h > 300){
1277 			siril_log_message(_("Current selection is too large. To determine the PSF, please make a selection around a single star.\n"));
1278 			return 1;
1279 		}
1280 		if (com.selection.w <= 0 || com.selection.h <= 0){
1281 			siril_log_message(_("Select an area first\n"));
1282 			return 1;
1283 		}
1284 		fitted_PSF *result = psf_get_minimisation(&gfit, layer, &com.selection, TRUE, TRUE, TRUE);
1285 		if (result) {
1286 			psf_display_result(result, &com.selection);
1287 			free(result);
1288 		}
1289 	}
1290 	return 0;
1291 }
1292 
process_seq_psf(int nb)1293 int process_seq_psf(int nb) {
1294 	if (get_thread_run()) {
1295 		PRINT_ANOTHER_THREAD_RUNNING;
1296 		return 1;
1297 	}
1298 	if (com.selection.w > 300 || com.selection.h > 300){
1299 		siril_log_message(_("Current selection is too large. To determine the PSF, please make a selection around a single star.\n"));
1300 		return 1;
1301 	}
1302 	if (com.selection.w <= 0 || com.selection.h <= 0){
1303 		siril_log_message(_("Select an area first\n"));
1304 		return 1;
1305 	}
1306 
1307 	int layer = match_drawing_area_widget(com.vport[com.cvport], FALSE);
1308 	if (sequence_is_loaded() && layer != -1) {
1309 		framing_mode framing = REGISTERED_FRAME;
1310 		if (framing == REGISTERED_FRAME && !com.seq.regparam[layer])
1311 			framing = ORIGINAL_FRAME;
1312 		if (framing == ORIGINAL_FRAME) {
1313 			GtkToggleButton *follow = GTK_TOGGLE_BUTTON(lookup_widget("followStarCheckButton"));
1314 			if (gtk_toggle_button_get_active(follow))
1315 				framing = FOLLOW_STAR_FRAME;
1316 		}
1317 		siril_log_message(_("Running the PSF on the loaded sequence, layer %d\n"), layer);
1318 		seqpsf(&com.seq, layer, FALSE, FALSE, framing, TRUE);
1319 		return 0;
1320 	}
1321 	else {
1322 		PRINT_NOT_FOR_SINGLE;
1323 		return 1;
1324 	}
1325 }
1326 
process_seq_crop(int nb)1327 int process_seq_crop(int nb) {
1328 	if (!sequence_is_loaded()) {
1329 		PRINT_NOT_FOR_SINGLE;
1330 		return 1;
1331 	}
1332 	if (get_thread_run()) {
1333 		PRINT_ANOTHER_THREAD_RUNNING;
1334 		return 1;
1335 	}
1336 
1337 	rectangle area;
1338 
1339 	int startoptargs = 5;
1340 
1341 	if ((!com.selection.h) || (!com.selection.w)) {
1342 		if (nb >= startoptargs) {
1343 			if (g_ascii_strtoull(word[1], NULL, 10) < 0 || g_ascii_strtoull(word[2], NULL, 10) < 0) {
1344 				siril_log_message(_("Crop: x and y must be positive values.\n"));
1345 				return 1;
1346 			}
1347 			if (g_ascii_strtoull(word[3], NULL, 10) <= 0 || g_ascii_strtoull(word[4], NULL, 10) <= 0) {
1348 				siril_log_message(_("Crop: width and height must be greater than 0.\n"));
1349 				return 1;
1350 			}
1351 			area.x = g_ascii_strtoull(word[1], NULL, 10);
1352 			area.y = g_ascii_strtoull(word[2], NULL, 10);
1353 			area.w = g_ascii_strtoull(word[3], NULL, 10);
1354 			area.h = g_ascii_strtoull(word[4], NULL, 10);
1355 		} else {
1356 			siril_log_message(_("Crop: select a region or provide x, y, width, height\n"));
1357 			return 1;
1358 		}
1359 	} else {
1360 		memcpy(&area, &com.selection, sizeof(rectangle));
1361 	}
1362 
1363 
1364 	if (g_ascii_strtoull(word[3], NULL, 10) > com.seq.rx || g_ascii_strtoull(word[4], NULL, 10) > com.seq.ry) {
1365 		siril_log_message(_("Crop: width and height, respectively, must be less than %d and %d.\n"),
1366 				com.seq.rx, com.seq.ry);
1367 		return 1;
1368 	}
1369 
1370 	struct crop_sequence_data *args = malloc(sizeof(struct crop_sequence_data));
1371 
1372 	args->seq = &com.seq;
1373 	args->area = area;
1374 	args->prefix = "cropped_";
1375 
1376 	if (nb > startoptargs) {
1377 		for (int i = startoptargs; i < nb; i++) {
1378 			if (word[i]) {
1379 				if (g_str_has_prefix(word[i], "-prefix=")) {
1380 					char *current = word[i], *value;
1381 					value = current + 8;
1382 					if (value[0] == '\0') {
1383 						siril_log_message(_("Missing argument to %s, aborting.\n"), current);
1384 						return 1;
1385 					}
1386 					args->prefix = strdup(value);
1387 				}
1388 			}
1389 		}
1390 	}
1391 
1392 	set_cursor_waiting(TRUE);
1393 
1394 	crop_sequence(args);
1395 	return 0;
1396 }
1397 
process_bg(int nb)1398 int process_bg(int nb){
1399 	if (!(single_image_is_loaded() || sequence_is_loaded())) return 1;
1400 	WORD us_bg;
1401 
1402 	for (int layer = 0; layer < gfit.naxes[2]; layer++) {
1403 		double bg = background(&gfit, layer, &com.selection, TRUE);
1404 		if (gfit.type == DATA_USHORT) {
1405 			us_bg = round_to_WORD(bg);
1406 			bg = bg / get_normalized_value(&gfit);
1407 		} else if (gfit.type == DATA_FLOAT) {
1408 			us_bg = float_to_ushort_range(bg);
1409 		} else return 1;
1410 		siril_log_message(_("Background value (channel: #%d): %d (%.3e)\n"), layer, us_bg, bg);
1411 	}
1412 	return 0;
1413 }
1414 
process_bgnoise(int nb)1415 int process_bgnoise(int nb){
1416 	if (get_thread_run()) {
1417 		PRINT_ANOTHER_THREAD_RUNNING;
1418 		return 1;
1419 	}
1420 
1421 	if (!(single_image_is_loaded() || sequence_is_loaded())) return 1;
1422 
1423 	struct noise_data *args = malloc(sizeof(struct noise_data));
1424 
1425 	if (!com.script) {
1426 		control_window_switch_to_tab(OUTPUT_LOGS);
1427 	}
1428 
1429 	args->fit = &gfit;
1430 	args->verbose = TRUE;
1431 	args->use_idle = TRUE;
1432 	memset(args->bgnoise, 0.0, sizeof(double[3]));
1433 	set_cursor_waiting(TRUE);
1434 
1435 	start_in_new_thread(noise, args);
1436 	return 0;
1437 }
1438 
process_histo(int nb)1439 int process_histo(int nb){
1440 	GError *error = NULL;
1441 	int nlayer = g_ascii_strtoull(word[1], NULL, 10);
1442 	const gchar* clayer;
1443 
1444 	if (!single_image_is_loaded()) {
1445 		PRINT_NOT_FOR_SEQUENCE;
1446 		return 1;
1447 	}
1448 
1449 	if (nlayer>3 || nlayer <0)
1450 		return 1;
1451 	gsl_histogram *histo = computeHisto(&gfit, nlayer);
1452 	if (!isrgb(&gfit))
1453 		clayer = "bw";		//if B&W
1454 	else
1455 		clayer = vport_number_to_name(nlayer);
1456 	gchar *filename = g_strdup_printf("histo_%s.dat", clayer);
1457 
1458 	GFile *file = g_file_new_for_path(filename);
1459 	g_free(filename);
1460 
1461 	GOutputStream *output_stream = (GOutputStream*) g_file_replace(file, NULL, FALSE,
1462 			G_FILE_CREATE_NONE, NULL, &error);
1463 
1464 	if (output_stream == NULL) {
1465 		if (error != NULL) {
1466 			g_warning("%s\n", error->message);
1467 			g_clear_error(&error);
1468 			fprintf(stderr, "Cannot save histo\n");
1469 		}
1470 		g_object_unref(file);
1471 		return 1;
1472 	}
1473 	for (size_t i = 0; i < USHRT_MAX + 1; i++) {
1474 		gchar *buffer = g_strdup_printf("%zu %d\n", i, (int) gsl_histogram_get (histo, i));
1475 
1476 		if (!g_output_stream_write_all(output_stream, buffer, strlen(buffer), NULL, NULL, &error)) {
1477 			g_warning("%s\n", error->message);
1478 			g_free(buffer);
1479 			g_clear_error(&error);
1480 			g_object_unref(output_stream);
1481 			g_object_unref(file);
1482 			return 1;
1483 		}
1484 		g_free(buffer);
1485 	}
1486 
1487 	siril_log_message(_("The file %s has been created for the %s layer.\n"), g_file_peek_path(file), clayer);
1488 
1489 	g_object_unref(output_stream);
1490 	g_object_unref(file);
1491 	gsl_histogram_free(histo);
1492 	return 0;
1493 }
1494 
process_thresh(int nb)1495 int process_thresh(int nb){
1496 	int lo, hi;
1497 
1498 	if (!single_image_is_loaded()) {
1499 		PRINT_NOT_FOR_SEQUENCE;
1500 		return 1;
1501 	}
1502 	int maxlevel = (gfit.orig_bitpix == BYTE_IMG) ? UCHAR_MAX : USHRT_MAX;
1503 	lo = g_ascii_strtoull(word[1], NULL, 10);
1504 	if (lo < 0 || lo > maxlevel) {
1505 		siril_log_message(_("replacement value is out of range (0 - %d)\n"), maxlevel);
1506 		return 1;
1507 	}
1508 	hi = g_ascii_strtoull(word[2], NULL, 10);
1509 	if (hi < 0 || hi > maxlevel) {
1510 		siril_log_message(_("replacement value is out of range (0 - %d)\n"), maxlevel);
1511 		return 1;
1512 	}
1513 	if (lo >= hi) {
1514 		siril_log_message(_("lo must be strictly smaller than hi\n"));
1515 		return 1;
1516 	}
1517 	threshlo(&gfit, lo);
1518 	threshhi(&gfit, hi);
1519 	adjust_cutoff_from_updated_gfit();
1520 	redraw(com.cvport, REMAP_ALL);
1521 	redraw_previews();
1522 	return 0;
1523 }
1524 
process_threshlo(int nb)1525 int process_threshlo(int nb){
1526 	int lo;
1527 
1528 	if (!single_image_is_loaded()) {
1529 		PRINT_NOT_FOR_SEQUENCE;
1530 		return 1;
1531 	}
1532 	int maxlevel = (gfit.orig_bitpix == BYTE_IMG) ? UCHAR_MAX : USHRT_MAX;
1533 	lo = g_ascii_strtoull(word[1], NULL, 10);
1534 	if (lo < 0 || lo > maxlevel) {
1535 		siril_log_message(_("replacement value is out of range (0 - %d)\n"), maxlevel);
1536 		return 1;
1537 	}
1538 	threshlo(&gfit, lo);
1539 	adjust_cutoff_from_updated_gfit();
1540 	redraw(com.cvport, REMAP_ALL);
1541 	redraw_previews();
1542 	return 0;
1543 }
1544 
process_threshhi(int nb)1545 int process_threshhi(int nb){
1546 	int hi;
1547 
1548 	if (!single_image_is_loaded()) {
1549 		PRINT_NOT_FOR_SEQUENCE;
1550 		return 1;
1551 	}
1552 	int maxlevel = (gfit.orig_bitpix == BYTE_IMG) ? UCHAR_MAX : USHRT_MAX;
1553 	hi = g_ascii_strtoull(word[1], NULL, 10);
1554 	if (hi < 0 || hi > maxlevel) {
1555 		siril_log_message(_("replacement value is out of range (0 - %d)\n"), maxlevel);
1556 		return 1;
1557 	}
1558 	threshhi(&gfit, hi);
1559 	adjust_cutoff_from_updated_gfit();
1560 	redraw(com.cvport, REMAP_ALL);
1561 	redraw_previews();
1562 	return 0;
1563 }
1564 
process_neg(int nb)1565 int process_neg(int nb) {
1566 	set_cursor_waiting(TRUE);
1567 	pos_to_neg(&gfit);
1568 	update_gfit_histogram_if_needed();
1569 	invalidate_stats_from_fit(&gfit);
1570 	redraw(com.cvport, REMAP_ALL);
1571 	redraw_previews();
1572 	set_cursor_waiting(FALSE);
1573 	return 0;
1574 }
1575 
process_nozero(int nb)1576 int process_nozero(int nb){
1577 	int level;
1578 
1579 	if (!single_image_is_loaded()) {
1580 		PRINT_NOT_FOR_SEQUENCE;
1581 		return 1;
1582 	}
1583 
1584 	level = g_ascii_strtoull(word[1], NULL, 10);
1585 	int maxlevel = (gfit.orig_bitpix == BYTE_IMG) ? UCHAR_MAX : USHRT_MAX;
1586 	if (level < 0 || level > maxlevel) {
1587 		siril_log_message(_("replacement value is out of range (0 - %d)\n"), maxlevel);
1588 		return 1;
1589 	}
1590 	nozero(&gfit, (WORD)level);
1591 	adjust_cutoff_from_updated_gfit();
1592 	redraw(com.cvport, REMAP_ALL);
1593 	redraw_previews();
1594 	return 0;
1595 }
1596 
process_ddp(int nb)1597 int process_ddp(int nb){
1598 	// combines an image division and a scalar multiplication.
1599 	float coeff, sigma;
1600 	unsigned level;
1601 
1602 	if (!single_image_is_loaded()) {
1603 		PRINT_NOT_FOR_SEQUENCE;
1604 		return 1;
1605 	}
1606 
1607 	level = g_ascii_strtoull(word[1], NULL, 10);
1608 	coeff = g_ascii_strtod(word[2], NULL);
1609 	sigma = g_ascii_strtod(word[3], NULL);
1610 	ddp(&gfit, level, coeff, sigma);
1611 	adjust_cutoff_from_updated_gfit();
1612 	redraw(com.cvport, REMAP_ALL);
1613 	redraw_previews();
1614 	return 0;
1615 }
1616 
process_new(int nb)1617 int process_new(int nb){
1618 	int width, height, layers;
1619 
1620 	width = g_ascii_strtod(word[1], NULL);
1621 	height = g_ascii_strtod(word[2], NULL);
1622 	layers = g_ascii_strtoull(word[3], NULL, 10);
1623 	if (layers != 1 && layers != 3) {
1624 		siril_log_message(_("Number of layers MUST be 1 or 3\n"));
1625 		return 1;
1626 	}
1627 	if (!height || !width) return 1;
1628 
1629 	close_single_image();
1630 	close_sequence(FALSE);
1631 
1632 	fits *fit = &gfit;
1633 	if (new_fit_image(&fit, width, height, layers, DATA_FLOAT))
1634 		return 1;
1635 	memset(gfit.fdata, 0, width * height * layers * sizeof(float));
1636 
1637 	com.seq.current = UNRELATED_IMAGE;
1638 	com.uniq = calloc(1, sizeof(single));
1639 	com.uniq->filename = strdup(_("new empty image"));
1640 	com.uniq->fileexist = FALSE;
1641 	com.uniq->nb_layers = gfit.naxes[2];
1642 	com.uniq->layers = calloc(com.uniq->nb_layers, sizeof(layer_info));
1643 	com.uniq->fit = &gfit;
1644 
1645 	open_single_image_from_gfit();
1646 	return 0;
1647 }
1648 
process_visu(int nb)1649 int process_visu(int nb){
1650 	int low, high;
1651 
1652 	if (!single_image_is_loaded()) {
1653 		PRINT_NOT_FOR_SEQUENCE;
1654 		return 1;
1655 	}
1656 
1657 	low = g_ascii_strtoull(word[1], NULL, 10);
1658 	high = g_ascii_strtoull(word[2], NULL, 10);
1659 	if ((high > USHRT_MAX) || (low < 0)) {
1660 		siril_log_message(_("Values must be positive and less than %d.\n"), USHRT_MAX);
1661 		return 1;
1662 	}
1663 	visu(&gfit, low, high);
1664 	return 0;
1665 }
1666 
process_fill2(int nb)1667 int process_fill2(int nb){
1668 	int level = g_ascii_strtoull(word[1], NULL, 10);
1669 	rectangle area;
1670 
1671 	if (!single_image_is_loaded()) {
1672 		PRINT_NOT_FOR_SEQUENCE;
1673 		return 1;
1674 	}
1675 
1676 	if ((!com.selection.h) || (!com.selection.w)) {
1677 		if (nb == 6) {
1678 			area.x = g_ascii_strtoull(word[2], NULL, 10);
1679 			area.y = g_ascii_strtoull(word[3], NULL, 10);
1680 			area.w = g_ascii_strtoull(word[4], NULL, 10);
1681 			area.h = g_ascii_strtoull(word[5], NULL, 10);
1682 			if ((area.w + area.x > gfit.rx) || (area.h + area.y > gfit.ry)) {
1683 				siril_log_message(_("Wrong parameters.\n"));
1684 				return 1;
1685 			}
1686 		}
1687 		else {
1688 			siril_log_message(_("Fill2: select a region or provide x, y, width, height\n"));
1689 			return 1;
1690 		}
1691 	} else {
1692 		memcpy(&area, &com.selection, sizeof(rectangle));
1693 	}
1694 	int retval = fill(&gfit, level, &area);
1695 	if (retval) {
1696 		siril_log_message(_("Wrong parameters.\n"));
1697 		return 1;
1698 	}
1699 	area.x = gfit.rx - area.x - area.w;
1700 	area.y = gfit.ry - area.y - area.h;
1701 	fill(&gfit, level, &area);
1702 	redraw(com.cvport, REMAP_ALL);
1703 	return 0;
1704 }
1705 
process_findstar(int nb)1706 int process_findstar(int nb){
1707 	int nbstars = 0;
1708 
1709 	int layer = com.cvport == RGB_VPORT ? GLAYER : com.cvport;
1710 
1711 	delete_selected_area();
1712 	com.stars = peaker(&gfit, layer, &com.starfinder_conf, &nbstars, NULL, TRUE);
1713 	siril_log_message(_("Found %d stars in image, channel #%d\n"), nbstars, layer);
1714 	if (com.stars)
1715 		refresh_star_list(com.stars);
1716 	return 0;
1717 }
1718 
process_findhot(int nb)1719 int process_findhot(int nb){
1720 	GError *error = NULL;
1721 	long icold, ihot;
1722 	gchar type;
1723 
1724 	if (!single_image_is_loaded()) {
1725 		PRINT_NOT_FOR_SEQUENCE;
1726 		return 1;
1727 	}
1728 
1729 	if (gfit.naxes[2] != 1) {
1730 		siril_log_message(_("find_hot must be applied on an one-channel master-dark frame"));
1731 		return 1;
1732 	}
1733 	double sig[2];
1734 	sig[0] = g_ascii_strtod(word[2], NULL);
1735 	sig[1] = g_ascii_strtod(word[3], NULL);
1736 
1737 	deviant_pixel *dev = find_deviant_pixels(&gfit, sig, &icold, &ihot, FALSE);
1738 	siril_log_message(_("%ld cold and %ld hot pixels\n"), icold, ihot);
1739 
1740 	gchar *filename = g_strdup_printf("%s.lst", word[1]);
1741 	GFile *file = g_file_new_for_path(filename);
1742 	g_free(filename);
1743 
1744 	GOutputStream *output_stream = (GOutputStream*) g_file_replace(file, NULL, FALSE,
1745 			G_FILE_CREATE_NONE, NULL, &error);
1746 
1747 	if (output_stream == NULL) {
1748 		if (error != NULL) {
1749 			g_warning("%s\n", error->message);
1750 			g_clear_error(&error);
1751 			fprintf(stderr, "Cannot open file: %s\n", filename);
1752 		}
1753 		g_object_unref(file);
1754 		return 1;
1755 	}
1756 
1757 	for (int i = 0; i < icold + ihot; i++) {
1758 		int y = gfit.ry - (int) dev[i].p.y - 1;  /* FITS is stored bottom to top */
1759 		if (dev[i].type == HOT_PIXEL)
1760 			type = 'H';
1761 		else
1762 			type = 'C';
1763 		gchar *buffer = g_strdup_printf("P %d %d %c\n", (int) dev[i].p.x, y, type);
1764 		if (!g_output_stream_write_all(output_stream, buffer, strlen(buffer), NULL, NULL, &error)) {
1765 			g_warning("%s\n", error->message);
1766 			g_free(buffer);
1767 			g_clear_error(&error);
1768 			g_object_unref(output_stream);
1769 			g_object_unref(file);
1770 			return 1;
1771 		}
1772 		g_free(buffer);
1773 	}
1774 
1775 	free(dev);
1776 	g_object_unref(output_stream);
1777 	g_object_unref(file);
1778 
1779 	return 0;
1780 }
1781 
process_fix_xtrans(int nb)1782 int process_fix_xtrans(int nb) {
1783 	if (!(single_image_is_loaded() || sequence_is_loaded())) return 1;
1784 
1785 	fix_xtrans_ac(&gfit);
1786 	adjust_cutoff_from_updated_gfit();
1787 	redraw(com.cvport, REMAP_ALL);
1788 	return 0;
1789 }
1790 
process_cosme(int nb)1791 int process_cosme(int nb) {
1792 	GError *error = NULL;
1793 	deviant_pixel dev;
1794 	gchar *filename;
1795 	double dirty;
1796 	int is_cfa, i = 0, retval = 0;
1797 	int nb_tokens;
1798 	gchar *line;
1799 	char type;
1800 
1801 	if (!single_image_is_loaded()) {
1802 		PRINT_NOT_FOR_SEQUENCE;
1803 		return 1;
1804 	}
1805 
1806 	if (!g_str_has_suffix(word[1], ".lst")) {
1807 		filename = g_strdup_printf("%s.lst", word[1]);
1808 	} else {
1809 		filename = g_strdup(word[1]);
1810 	}
1811 	GFile *file = g_file_new_for_path(filename);
1812 	g_free(filename);
1813 
1814 	GInputStream *input_stream = (GInputStream *)g_file_read(file, NULL, &error);
1815 
1816 	if (input_stream == NULL) {
1817 		if (error != NULL) {
1818 			g_clear_error(&error);
1819 			siril_log_message(_("File [%s] does not exist\n"), filename);
1820 		}
1821 
1822 		g_object_unref(file);
1823 		return 1;
1824 	}
1825 
1826 	is_cfa = (word[0][5] == '_') ? 1 : 0;
1827 
1828 	GDataInputStream *data_input = g_data_input_stream_new(input_stream);
1829 	while ((line = g_data_input_stream_read_line_utf8(data_input, NULL,
1830 				NULL, NULL))) {
1831 		++i;
1832 		switch (line[0]) {
1833 		case '#': // comments.
1834 			g_free(line);
1835 			continue;
1836 			break;
1837 		case 'P':
1838 			nb_tokens = sscanf(line + 2, "%lf %lf %c", &dev.p.x, &dev.p.y, &type);
1839 			if (nb_tokens != 2 && nb_tokens != 3) {
1840 				fprintf(stderr, "cosmetic correction: "
1841 						"cosme file format error at line %d: %s", i, line);
1842 				retval = 1;
1843 				g_free(line);
1844 				continue;
1845 			}
1846 			if (nb_tokens == 2)	{
1847 				type = 'H';
1848 			}
1849 			if (type == 'H')
1850 				dev.type = HOT_PIXEL;
1851 			else
1852 				dev.type = COLD_PIXEL;
1853 			dev.p.y = gfit.ry - dev.p.y - 1;  /* FITS are stored bottom to top */
1854 			cosmeticCorrOnePoint(&gfit, dev, is_cfa);
1855 			break;
1856 		case 'L':
1857 			nb_tokens = sscanf(line + 2, "%lf %lf %c", &dev.p.y, &dirty, &type);
1858 			if (nb_tokens != 2 && nb_tokens != 3) {
1859 				fprintf(stderr, "cosmetic correction: "
1860 						"cosme file format error at line %d: %s\n", i, line);
1861 				retval = 1;
1862 				g_free(line);
1863 				continue;
1864 			}
1865 			dev.type = HOT_PIXEL; // we force it
1866 			dev.p.y = gfit.ry - dev.p.y - 1; /* FITS are stored bottom to top */
1867 			cosmeticCorrOneLine(&gfit, dev, is_cfa);
1868 			break;
1869 		case 'C':
1870 			nb_tokens = sscanf(line + 2, "%lf %lf %c", &dev.p.y, &dirty, &type);
1871 			if (nb_tokens != 2 && nb_tokens != 3) {
1872 				fprintf(stderr, "cosmetic correction: "
1873 						"cosme file format error at line %d: %s\n", i, line);
1874 				retval = 1;
1875 				g_free(line);
1876 				continue;
1877 			}
1878 			point center = {gfit.rx / 2.0, gfit.ry / 2.0};
1879 			dev.type = HOT_PIXEL; // we force it
1880 			dev.p.y = gfit.rx - dev.p.y - 1; /* FITS are stored bottom to top */
1881 			cvRotateImage(&gfit, center, 90.0, -1, OPENCV_AREA);
1882 			cosmeticCorrOneLine(&gfit, dev, is_cfa);
1883 			cvRotateImage(&gfit, center, -90.0, -1, OPENCV_AREA);
1884 
1885 			break;
1886 		default:
1887 			fprintf(stderr, _("cosmetic correction: "
1888 					"cosme file format error at line %d: %s\n"), i, line);
1889 			retval = 1;
1890 		}
1891 		g_free(line);
1892 	}
1893 
1894 	g_object_unref(input_stream);
1895 	g_object_unref(file);
1896 	if (retval)
1897 		siril_log_color_message(_("There were some errors, please check your input file.\n"), "salmon");
1898 
1899 	invalidate_stats_from_fit(&gfit);
1900 	adjust_cutoff_from_updated_gfit();
1901 	redraw(com.cvport, REMAP_ALL);
1902 	redraw_previews();
1903 	return 0;
1904 }
1905 
process_fmedian(int nb)1906 int process_fmedian(int nb){
1907 	if (get_thread_run()) {
1908 		PRINT_ANOTHER_THREAD_RUNNING;
1909 		return 1;
1910 	}
1911 
1912 	if (!single_image_is_loaded()) {
1913 		PRINT_NOT_FOR_SEQUENCE;
1914 		return 1;
1915 	}
1916 
1917 	struct median_filter_data *args = malloc(sizeof(struct median_filter_data));
1918 	args->ksize = g_ascii_strtoull(word[1], NULL, 10);
1919 	args->amount = g_ascii_strtod(word[2], NULL);
1920 	args->iterations = 1;
1921 
1922 	if (!(args->ksize & 1) || args->ksize < 2 || args->ksize > 15) {
1923 		siril_log_message(_("The size of the kernel MUST be odd and in the range [3, 15].\n"));
1924 		free(args);
1925 		return 1;
1926 	}
1927 	if (args->amount < 0.0 || args->amount > 1.0) {
1928 		siril_log_message(_("Modulation value MUST be between 0 and 1\n"));
1929 		free(args);
1930 		return 1;
1931 	}
1932 	args->fit = &gfit;
1933 
1934 	set_cursor_waiting(TRUE);
1935 
1936 	start_in_new_thread(median_filter, args);
1937 
1938 	return 0;
1939 }
1940 
1941 /* The name of this command should be COG in english but this choice
1942  * was done to be consistent with IRIS
1943  */
process_cdg(int nb)1944 int process_cdg(int nb) {
1945 	float x_avg, y_avg;
1946 
1947 	if (!single_image_is_loaded()) {
1948 		PRINT_NOT_FOR_SEQUENCE;
1949 		return 1;
1950 	}
1951 
1952 	if (!FindCentre(&gfit, &x_avg, &y_avg)) {
1953 		siril_log_message(_("Center of gravity coordinates are (%.3lf, %.3lf)\n"), x_avg, y_avg);
1954 		return 0;
1955 	}
1956 	return 1;
1957 }
1958 
process_clear(int nb)1959 int process_clear(int nb) {
1960 	if (com.script) return 0;
1961 	GtkTextView *text = GTK_TEXT_VIEW(lookup_widget("output"));
1962 	GtkTextBuffer *tbuf = gtk_text_view_get_buffer(text);
1963 	GtkTextIter start_iter, end_iter;
1964 	gtk_text_buffer_get_start_iter(tbuf, &start_iter);
1965 	gtk_text_buffer_get_end_iter(tbuf, &end_iter);
1966 	gtk_text_buffer_delete(tbuf, &start_iter, &end_iter);
1967 	return 0;
1968 }
1969 
process_clearstar(int nb)1970 int process_clearstar(int nb){
1971 	clear_stars_list();
1972 	adjust_cutoff_from_updated_gfit();
1973 	redraw(com.cvport, REMAP_NONE);
1974 	redraw_previews();
1975 	return 0;
1976 }
1977 
process_close(int nb)1978 int process_close(int nb) {
1979 	close_sequence(FALSE);
1980 	close_single_image();
1981 	if (!com.script) {
1982 		update_MenuItem();
1983 		reset_plot(); // reset all plots
1984 		close_tab();	//close Green and Blue Tab if a 1-layer sequence is loaded
1985 
1986 	}
1987 	return 0;
1988 }
1989 
process_fill(int nb)1990 int process_fill(int nb){
1991 	int level;
1992 	rectangle area;
1993 
1994 	if (!single_image_is_loaded()) {
1995 		PRINT_NOT_FOR_SEQUENCE;
1996 		return 1;
1997 	}
1998 
1999 	if ((!com.selection.h) || (!com.selection.w)) {
2000 		if (nb == 6) {
2001 			area.x = g_ascii_strtoull(word[2], NULL, 10);
2002 			area.y = g_ascii_strtoull(word[3], NULL, 10);
2003 			area.w = g_ascii_strtoull(word[4], NULL, 10);
2004 			area.h = g_ascii_strtoull(word[5], NULL, 10);
2005 			if ((area.w + area.x > gfit.rx) || (area.h + area.y > gfit.ry)) {
2006 				siril_log_message(_("Wrong parameters.\n"));
2007 				return 1;
2008 			}
2009 		}
2010 		else {
2011 			area.w = gfit.rx; area.h = gfit.ry;
2012 			area.x = 0; area.y = 0;
2013 		}
2014 	} else {
2015 		memcpy(&area, &com.selection, sizeof(rectangle));
2016 	}
2017 	level = g_ascii_strtoull(word[1], NULL, 10);
2018 	int retval = fill(&gfit, level, &area);
2019 	if (retval) {
2020 		siril_log_message(_("Wrong parameters.\n"));
2021 		return 1;
2022 	}
2023 	redraw(com.cvport, REMAP_ALL);
2024 	return 0;
2025 }
2026 
process_offset(int nb)2027 int process_offset(int nb){
2028 	int level;
2029 
2030 	if (!single_image_is_loaded()) {
2031 		PRINT_NOT_FOR_SEQUENCE;
2032 		return 1;
2033 	}
2034 
2035 	level = g_ascii_strtod(word[1], NULL);
2036 	off(&gfit, level);
2037 	adjust_cutoff_from_updated_gfit();
2038 	redraw(com.cvport, REMAP_ALL);
2039 	redraw_previews();
2040 	return 0;
2041 }
2042 
2043 /* The version in command line is a minimal version
2044  * Only neutral type are available (no amount needed),
2045  * then we always preserve the lightness */
process_scnr(int nb)2046 int process_scnr(int nb){
2047 	if (get_thread_run()) {
2048 		PRINT_ANOTHER_THREAD_RUNNING;
2049 		return 1;
2050 	}
2051 
2052 	if (!single_image_is_loaded()) {
2053 		PRINT_NOT_FOR_SEQUENCE;
2054 		return 1;
2055 	}
2056 	if (gfit.naxes[2] == 1) return 1;
2057 
2058 	struct scnr_data *args = malloc(sizeof(struct scnr_data));
2059 
2060 	args->type = g_ascii_strtoull(word[1], NULL, 10);
2061 	args->fit = &gfit;
2062 	args->amount = 0.0;
2063 	args->preserve = TRUE;
2064 
2065 	set_cursor_waiting(TRUE);
2066 
2067 	start_in_new_thread(scnr, args);
2068 
2069 	return 0;
2070 }
2071 
process_fft(int nb)2072 int process_fft(int nb){
2073 	if (get_thread_run()) {
2074 		PRINT_ANOTHER_THREAD_RUNNING;
2075 		return 1;
2076 	}
2077 
2078 	if (sequence_is_loaded()) {
2079 		PRINT_NOT_FOR_SEQUENCE;
2080 		return 1;
2081 	}
2082 
2083 	if (!single_image_is_loaded()) {
2084 		PRINT_NOT_FOR_SEQUENCE;
2085 		return 1;
2086 	}
2087 
2088 	struct fft_data *args = malloc(sizeof(struct fft_data));
2089 
2090 	args->fit = &gfit;
2091 	args->type = strdup(word[0]);
2092 	args->modulus = strdup(word[1]);
2093 	args->phase = strdup(word[2]);
2094 	args->type_order = 0;
2095 
2096 	set_cursor_waiting(TRUE);
2097 
2098 	start_in_new_thread(fourier_transform, args);
2099 
2100 	return 0;
2101 }
2102 
process_fixbanding(int nb)2103 int process_fixbanding(int nb) {
2104 	if (get_thread_run()) {
2105 		PRINT_ANOTHER_THREAD_RUNNING;
2106 		return 1;
2107 	}
2108 
2109 	if (!single_image_is_loaded()) {
2110 		PRINT_NOT_FOR_SEQUENCE;
2111 		return 1;
2112 	}
2113 
2114 	struct banding_data *args = malloc(sizeof(struct banding_data));
2115 
2116 	args->amount = g_ascii_strtod(word[1], NULL);
2117 	args->sigma = g_ascii_strtod(word[2], NULL);
2118 	args->protect_highlights = TRUE;
2119 	args->fit = &gfit;
2120 
2121 	set_cursor_waiting(TRUE);
2122 
2123 	start_in_new_thread(BandingEngineThreaded, args);
2124 
2125 	return 0;
2126 }
2127 
2128 
process_subsky(int nb)2129 int process_subsky(int nb) {
2130 	gboolean is_sequence;
2131 	sequence *seq = NULL;
2132 	int degree = 0;
2133 
2134 	if (get_thread_run()) {
2135 		PRINT_ANOTHER_THREAD_RUNNING;
2136 		return 1;
2137 	}
2138 
2139 	is_sequence = (word[0][2] == 'q');
2140 
2141 	if (is_sequence) {
2142 		seq = load_sequence(word[1], NULL);
2143 		if (!seq)
2144 			return 1;
2145 		degree = g_ascii_strtoull(word[2], NULL, 10);
2146 	} else {
2147 		if (!single_image_is_loaded()) return 1;
2148 		degree = g_ascii_strtoull(word[1], NULL, 10);
2149 	}
2150 
2151 	if (degree < 1 || degree > 4) {
2152 		siril_log_message(_("Polynomial degree order must be within the [1, 4] range.\n"));
2153 		return 1;
2154 	}
2155 
2156 	set_cursor_waiting(TRUE);
2157 
2158 	if (is_sequence) {
2159 		struct background_data *args = malloc(sizeof(struct background_data));
2160 
2161 		args->seq = seq;
2162 		args->nb_of_samples = 20;
2163 		args->tolerance = 1.0;
2164 		args->correction = 0; //subtraction
2165 		args->seqEntry = "bkg_";
2166 		args->degree = (poly_order) (degree - 1);
2167 		args->dither = TRUE;
2168 
2169 		int startoptargs = 3;
2170 		if (nb > startoptargs) {
2171 			for (int i = startoptargs; i < nb; i++) {
2172 				if (word[i]) {
2173 					if (g_str_has_prefix(word[i], "-prefix=")) {
2174 						char *current = word[i], *value;
2175 						value = current + 8;
2176 						if (value[0] == '\0') {
2177 							siril_log_message(_("Missing argument to %s, aborting.\n"), current);
2178 							return 1;
2179 						}
2180 						args->seqEntry = strdup(value);
2181 					}
2182 				}
2183 			}
2184 		}
2185 
2186 
2187 		apply_background_extraction_to_sequence(args);
2188 	} else {
2189 		generate_background_samples(20, 1.0);
2190 		remove_gradient_from_image(0, (poly_order) (degree - 1));
2191 		free_background_sample_list(com.grad_samples);
2192 		com.grad_samples = NULL;
2193 
2194 		adjust_cutoff_from_updated_gfit();
2195 		redraw(com.cvport, REMAP_ALL);
2196 	}
2197 	set_cursor_waiting(FALSE);
2198 
2199 	return 0;
2200 }
2201 
2202 
process_findcosme(int nb)2203 int process_findcosme(int nb) {
2204 	gboolean is_sequence;
2205 	sequence *seq = NULL;
2206 	int i = 0;
2207 
2208 	if (get_thread_run()) {
2209 		PRINT_ANOTHER_THREAD_RUNNING;
2210 		return 1;
2211 	}
2212 
2213 	is_sequence = (word[0][0] == 's');
2214 
2215 	if (is_sequence) {
2216 		seq = load_sequence(word[1], NULL);
2217 		if (!seq)
2218 			return 1;
2219 		i++;
2220 	} else {
2221 		if (!single_image_is_loaded()) return 1;
2222 	}
2223 
2224 	struct cosmetic_data *args = malloc(sizeof(struct cosmetic_data));
2225 
2226 	args->seq = seq;
2227 	args->sigma[0] = g_ascii_strtod(word[1 + i], NULL);
2228 	args->sigma[1] = g_ascii_strtod(word[2 + i], NULL);
2229 	args->is_cfa = (word[0][10] == '_' || word[0][13] == '_');	// find_cosme_cfa or seqfind_cosme_cfa
2230 	args->amount = 1.0;
2231 	args->fit = &gfit;
2232 
2233 	set_cursor_waiting(TRUE);
2234 
2235 	if (is_sequence) {
2236 		args->seqEntry = "cc_";
2237 		args->multithread = FALSE;
2238 
2239 		int startoptargs = i + 3;
2240 		int nb_command_max = i + 4;
2241 		if (nb > startoptargs) {
2242 			for (int j = startoptargs; j < nb_command_max; j++) {
2243 				if (word[j]) {
2244 					if (g_str_has_prefix(word[j], "-prefix=")) {
2245 						char *current = word[j], *value;
2246 						value = current + 8;
2247 						if (value[0] == '\0') {
2248 							siril_log_message(_("Missing argument to %s, aborting.\n"), current);
2249 							return 1;
2250 						}
2251 						args->seqEntry = strdup(value);
2252 					}
2253 				}
2254 			}
2255 		}
2256 		apply_cosmetic_to_sequence(args);
2257 	} else {
2258 		args->multithread = TRUE;
2259 		start_in_new_thread(autoDetectThreaded, args);
2260 	}
2261 
2262 	return 0;
2263 }
2264 
select_unselect(gboolean select)2265 int select_unselect(gboolean select) {
2266 	if (!sequence_is_loaded()) {
2267 		siril_log_message(_("Use this command to select images in a sequence, load a sequence first.\n"));
2268 		return 1;
2269 	}
2270 	int from = g_ascii_strtoull(word[1], NULL, 10);
2271 	int to = g_ascii_strtoull(word[2], NULL, 10);
2272 	if (from < 0 || from >= com.seq.number) {
2273 		siril_log_message(_("The first argument must be between 0 and the number of images minus one.\n"));
2274 		return 1;
2275 	}
2276 	gboolean current_updated = FALSE;
2277 	for (int i = from; i <= to; i++) {
2278 		if (i >= com.seq.number) break;
2279 		if (com.seq.imgparam[i].incl != select) {
2280 			com.seq.imgparam[i].incl = select;
2281 			if (!com.headless)
2282 				sequence_list_change_selection_index(i, i);
2283 			if (select)
2284 				com.seq.selnum++;
2285 			else	com.seq.selnum--;
2286 			if (i == com.seq.current)
2287 				current_updated = TRUE;
2288 		}
2289 		if (!select && com.seq.reference_image == i) {
2290 			com.seq.reference_image = -1;
2291 			if (!com.headless) {
2292 				sequence_list_change_reference();
2293 				adjust_refimage(com.seq.current);
2294 			}
2295 		}
2296 	}
2297 
2298 	if (!com.headless) {
2299 		if (current_updated) {
2300 			redraw(com.cvport, REMAP_NONE);
2301 			drawPlot();
2302 			adjust_sellabel();
2303 		}
2304 		update_reg_interface(FALSE);
2305 		adjust_sellabel();
2306 	}
2307 	writeseqfile(&com.seq);
2308 	siril_log_message(_("Selection update finished, %d images are selected in the sequence\n"), com.seq.selnum);
2309 
2310 	return 0;
2311 }
2312 
process_select(int nb)2313 int process_select(int nb){
2314 	return select_unselect(TRUE);
2315 }
2316 
process_unselect(int nb)2317 int process_unselect(int nb){
2318 	return select_unselect(FALSE);
2319 }
2320 
process_split(int nb)2321 int process_split(int nb){
2322 	if (!single_image_is_loaded()) {
2323 		PRINT_NOT_FOR_SEQUENCE;
2324 		return 1;
2325 	}
2326 
2327 	if (!isrgb(&gfit)) {
2328 		siril_log_message(_("Siril cannot split layers. Make sure your image is in RGB mode.\n"));
2329 		return 1;
2330 	}
2331 
2332 	if (get_thread_run()) {
2333 		PRINT_ANOTHER_THREAD_RUNNING;
2334 		return 1;
2335 	}
2336 
2337 	struct extract_channels_data *args = malloc(sizeof(struct extract_channels_data));
2338 	if (!args) {
2339 		PRINT_ALLOC_ERR;
2340 		return 1;
2341 	}
2342 
2343 	args->type = 0;
2344 	args->str_type = _("RGB");
2345 
2346 	args->channel[0] = g_strdup_printf("%s%s", word[1], com.pref.ext);
2347 	args->channel[1] = g_strdup_printf("%s%s", word[2], com.pref.ext);
2348 	args->channel[2] = g_strdup_printf("%s%s", word[3], com.pref.ext);
2349 
2350 	args->fit = calloc(1, sizeof(fits));
2351 	set_cursor_waiting(TRUE);
2352 	if (copyfits(&gfit, args->fit, CP_ALLOC | CP_COPYA | CP_FORMAT, -1)) {
2353 		siril_log_message(_("Could not copy the input image, aborting.\n"));
2354 		free(args->fit);
2355 		free(args->channel[0]);
2356 		free(args->channel[1]);
2357 		free(args->channel[2]);
2358 		free(args);
2359 		return 1;
2360 	}
2361 	copy_fits_metadata(&gfit, args->fit);
2362 	start_in_new_thread(extract_channels, args);
2363 	return 0;
2364 }
2365 
process_split_cfa(int nb)2366 int process_split_cfa(int nb) {
2367 	if (isrgb(&gfit)) {
2368 		siril_log_message(_("Siril cannot split CFA channel. Make sure your image is in CFA mode.\n"));
2369 		return 1;
2370 	}
2371 	char *filename = NULL;
2372 	int ret = 1;
2373 
2374 	fits f_cfa0 = { 0 }, f_cfa1 = { 0 }, f_cfa2 = { 0 }, f_cfa3 = { 0 };
2375 
2376 	if (sequence_is_loaded() && !single_image_is_loaded()) {
2377 		filename = g_path_get_basename(com.seq.seqname);
2378 	}
2379 	else {
2380 		if (com.uniq->filename != NULL) {
2381 			char *tmp = remove_ext_from_filename(com.uniq->filename);
2382 			filename = g_path_get_basename(tmp);
2383 			free(tmp);
2384 		}
2385 	}
2386 
2387 	gchar *cfa0 = g_strdup_printf("CFA0_%s%s", filename, com.pref.ext);
2388 	gchar *cfa1 = g_strdup_printf("CFA1_%s%s", filename, com.pref.ext);
2389 	gchar *cfa2 = g_strdup_printf("CFA2_%s%s", filename, com.pref.ext);
2390 	gchar *cfa3 = g_strdup_printf("CFA3_%s%s", filename, com.pref.ext);
2391 
2392 	if (gfit.type == DATA_USHORT) {
2393 		if (!(ret = split_cfa_ushort(&gfit, &f_cfa0, &f_cfa1, &f_cfa2, &f_cfa3))) {
2394 			ret = save1fits16(cfa0, &f_cfa0, 0) ||
2395 				save1fits16(cfa1, &f_cfa1, 0) ||
2396 				save1fits16(cfa2, &f_cfa2, 0) ||
2397 				save1fits16(cfa3, &f_cfa3, 0);
2398 		}
2399 	}
2400 	else if (gfit.type == DATA_FLOAT) {
2401 		if (!(ret = split_cfa_float(&gfit, &f_cfa0, &f_cfa1, &f_cfa2, &f_cfa3))) {
2402 			ret = save1fits32(cfa0, &f_cfa0, 0) ||
2403 				save1fits32(cfa1, &f_cfa1, 0) ||
2404 				save1fits32(cfa2, &f_cfa2, 0) ||
2405 				save1fits32(cfa3, &f_cfa3, 0);
2406 		}
2407 	}
2408 
2409 	g_free(cfa0); g_free(cfa1);
2410 	g_free(cfa2); g_free(cfa3);
2411 	clearfits(&f_cfa0); clearfits(&f_cfa1);
2412 	clearfits(&f_cfa2); clearfits(&f_cfa3);
2413 	free(filename);
2414 	return ret;
2415 }
2416 
process_extractGreen(int nb)2417 int process_extractGreen(int nb) {
2418 	if (isrgb(&gfit)) {
2419 		siril_log_message(_("Siril cannot split CFA channel. Make sure your image is in CFA mode.\n"));
2420 		return 1;
2421 	}
2422 	char *filename = NULL;
2423 	int ret = 1;
2424 
2425 	fits f_green = { 0 };
2426 
2427 	if (sequence_is_loaded() && !single_image_is_loaded()) {
2428 		filename = g_path_get_basename(com.seq.seqname);
2429 	}
2430 	else {
2431 		if (com.uniq->filename != NULL) {
2432 			char *tmp = remove_ext_from_filename(com.uniq->filename);
2433 			filename = g_path_get_basename(tmp);
2434 			free(tmp);
2435 		}
2436 	}
2437 
2438 	sensor_pattern pattern = get_bayer_pattern(&gfit);
2439 
2440 	gchar *green = g_strdup_printf("Green_%s%s", filename, com.pref.ext);
2441 	if (gfit.type == DATA_USHORT) {
2442 		if (!(ret = extractGreen_ushort(&gfit, &f_green, pattern))) {
2443 			ret = save1fits16(green, &f_green, 0);
2444 		}
2445 	}
2446 	else if (gfit.type == DATA_FLOAT) {
2447 		if (!(ret = extractGreen_float(&gfit, &f_green, pattern))) {
2448 			ret = save1fits32(green, &f_green, 0);
2449 		}
2450 	} else return 1;
2451 
2452 	g_free(green);
2453 	clearfits(&f_green);
2454 	free(filename);
2455 	return ret;
2456 
2457 }
2458 
process_extractHa(int nb)2459 int process_extractHa(int nb) {
2460 	if (isrgb(&gfit)) {
2461 		siril_log_message(_("Siril cannot split CFA channel. Make sure your image is in CFA mode.\n"));
2462 		return 1;
2463 	}
2464 	char *filename = NULL;
2465 	int ret = 1;
2466 
2467 	fits f_Ha = { 0 };
2468 
2469 	if (sequence_is_loaded() && !single_image_is_loaded()) {
2470 		filename = g_path_get_basename(com.seq.seqname);
2471 	}
2472 	else {
2473 		if (com.uniq->filename != NULL) {
2474 			char *tmp = remove_ext_from_filename(com.uniq->filename);
2475 			filename = g_path_get_basename(tmp);
2476 			free(tmp);
2477 		}
2478 	}
2479 
2480 	sensor_pattern pattern = get_bayer_pattern(&gfit);
2481 
2482 	gchar *Ha = g_strdup_printf("Ha_%s%s", filename, com.pref.ext);
2483 	if (gfit.type == DATA_USHORT) {
2484 		if (!(ret = extractHa_ushort(&gfit, &f_Ha, pattern))) {
2485 			ret = save1fits16(Ha, &f_Ha, 0);
2486 		}
2487 	}
2488 	else if (gfit.type == DATA_FLOAT) {
2489 		if (!(ret = extractHa_float(&gfit, &f_Ha, pattern))) {
2490 			ret = save1fits32(Ha, &f_Ha, 0);
2491 		}
2492 	} else return 1;
2493 
2494 	g_free(Ha);
2495 	clearfits(&f_Ha);
2496 	free(filename);
2497 	return ret;
2498 }
2499 
process_extractHaOIII(int nb)2500 int process_extractHaOIII(int nb) {
2501 	if (isrgb(&gfit)) {
2502 		siril_log_message(_("Siril cannot split CFA channel. Make sure your image is in CFA mode.\n"));
2503 		return 1;
2504 	}
2505 	char *filename = NULL;
2506 	int ret = 1;
2507 
2508 	fits f_Ha = { 0 }, f_OIII = { 0 };
2509 
2510 	if (sequence_is_loaded() && !single_image_is_loaded()) {
2511 		filename = g_path_get_basename(com.seq.seqname);
2512 	}
2513 	else {
2514 		if (com.uniq->filename != NULL) {
2515 			char *tmp = remove_ext_from_filename(com.uniq->filename);
2516 			filename = g_path_get_basename(tmp);
2517 			free(tmp);
2518 		}
2519 	}
2520 
2521 	sensor_pattern pattern = get_bayer_pattern(&gfit);
2522 
2523 	gchar *Ha = g_strdup_printf("Ha_%s%s", filename, com.pref.ext);
2524 	gchar *OIII = g_strdup_printf("OIII_%s%s", filename, com.pref.ext);
2525 	if (gfit.type == DATA_USHORT) {
2526 		if (!(ret = extractHaOIII_ushort(&gfit, &f_Ha, &f_OIII, pattern))) {
2527 			ret = save1fits16(Ha, &f_Ha, 0) ||
2528 					save1fits16(OIII, &f_OIII, 0);
2529 		}
2530 	}
2531 	else if (gfit.type == DATA_FLOAT) {
2532 		if (!(ret = extractHaOIII_float(&gfit, &f_Ha, &f_OIII, pattern))) {
2533 			ret = save1fits32(Ha, &f_Ha, 0) ||
2534 					save1fits16(OIII, &f_OIII, 0);
2535 		}
2536 	} else return 1;
2537 
2538 	g_free(Ha);
2539 	g_free(OIII);
2540 	clearfits(&f_Ha);
2541 	clearfits(&f_OIII);
2542 	free(filename);
2543 	return ret;
2544 }
2545 
process_seq_mtf(int nb)2546 int process_seq_mtf(int nb) {
2547 	if (get_thread_run()) {
2548 		PRINT_ANOTHER_THREAD_RUNNING;
2549 		return 1;
2550 	}
2551 	sequence *seq = load_sequence(word[1], NULL);
2552 	if (!seq)
2553 		return 1;
2554 
2555 	struct mtf_data *args = malloc(sizeof(struct mtf_data));
2556 
2557 	args->seq = seq;
2558 	args->fit = &gfit;
2559 	args->seqEntry = "mtf_";
2560 	args->lo = g_ascii_strtod(word[2], NULL);
2561 	args->mid = g_ascii_strtod(word[3], NULL);
2562 	args->hi = g_ascii_strtod(word[4], NULL);
2563 
2564 	int startoptargs = 5;
2565 	if (nb > startoptargs) {
2566 		for (int i = startoptargs; i < nb; i++) {
2567 			if (word[i]) {
2568 				if (g_str_has_prefix(word[i], "-prefix=")) {
2569 					char *current = word[i], *value;
2570 					value = current + 8;
2571 					if (value[0] == '\0') {
2572 						siril_log_message(_("Missing argument to %s, aborting.\n"), current);
2573 						return 1;
2574 					}
2575 					args->seqEntry = strdup(value);
2576 				}
2577 			}
2578 		}
2579 	}
2580 
2581 	set_cursor_waiting(TRUE);
2582 
2583 	apply_mtf_to_sequence(args);
2584 
2585 	return 0;
2586 }
2587 
process_seq_split_cfa(int nb)2588 int process_seq_split_cfa(int nb) {
2589 	if (get_thread_run()) {
2590 		PRINT_ANOTHER_THREAD_RUNNING;
2591 		return 1;
2592 	}
2593 
2594 	sequence *seq = load_sequence(word[1], NULL);
2595 	if (!seq)
2596 		return 1;
2597 
2598 	if (seq->nb_layers > 1) {
2599 		siril_log_message(_("Siril cannot split CFA channel. Make sure your image is in CFA mode.\n"));
2600 		return 1;
2601 	}
2602 
2603 	struct split_cfa_data *args = calloc(1, sizeof(struct split_cfa_data));
2604 
2605 	args->seq = seq;
2606 	args->fit = &gfit;
2607 	args->seqEntry = "CFA_"; // propose to default to "CFA" for consistency of output names with single image split_cfa
2608 
2609 	int startoptargs = 2;
2610 	if (nb > startoptargs) {
2611 		for (int i = startoptargs; i < nb; i++) {
2612 			if (word[i]) {
2613 				if (g_str_has_prefix(word[i], "-prefix=")) {
2614 					char *current = word[i], *value;
2615 					value = current + 8;
2616 					if (value[0] == '\0') {
2617 						siril_log_message(_("Missing argument to %s, aborting.\n"), current);
2618 						return 1;
2619 					}
2620 					args->seqEntry = strdup(value);
2621 				}
2622 			}
2623 		}
2624 	}
2625 
2626 	set_cursor_waiting(TRUE);
2627 
2628 	apply_split_cfa_to_sequence(args);
2629 
2630 	return 0;
2631 }
2632 
process_seq_extractHa(int nb)2633 int process_seq_extractHa(int nb) {
2634 	if (get_thread_run()) {
2635 		PRINT_ANOTHER_THREAD_RUNNING;
2636 		return 1;
2637 	}
2638 
2639 	sequence *seq = load_sequence(word[1], NULL);
2640 	if (!seq)
2641 		return 1;
2642 
2643 	if (seq->nb_layers > 1) {
2644 		siril_log_message(_("Siril cannot split CFA channel. Make sure your image is in CFA mode.\n"));
2645 		return 1;
2646 	}
2647 
2648 	struct split_cfa_data *args = calloc(1, sizeof(struct split_cfa_data));
2649 
2650 	args->seq = seq;
2651 	args->seqEntry = "Ha_";
2652 
2653 	int startoptargs = 2;
2654 	if (nb > startoptargs) {
2655 		for (int i = startoptargs; i < nb; i++) {
2656 			if (word[i]) {
2657 				if (g_str_has_prefix(word[i], "-prefix=")) {
2658 					char *current = word[i], *value;
2659 					value = current + 8;
2660 					if (value[0] == '\0') {
2661 						siril_log_message(_("Missing argument to %s, aborting.\n"), current);
2662 						return 1;
2663 					}
2664 					args->seqEntry = strdup(value);
2665 				}
2666 			}
2667 		}
2668 	}
2669 
2670 	set_cursor_waiting(TRUE);
2671 
2672 	apply_extractHa_to_sequence(args);
2673 
2674 	return 0;
2675 }
2676 
process_seq_extractGreen(int nb)2677 int process_seq_extractGreen(int nb) {
2678 	if (get_thread_run()) {
2679 		PRINT_ANOTHER_THREAD_RUNNING;
2680 		return 1;
2681 	}
2682 
2683 	sequence *seq = load_sequence(word[1], NULL);
2684 	if (!seq)
2685 		return 1;
2686 
2687 	if (seq->nb_layers > 1) {
2688 		siril_log_message(_("Siril cannot split CFA channel. Make sure your image is in CFA mode.\n"));
2689 		return 1;
2690 	}
2691 
2692 	struct split_cfa_data *args = calloc(1, sizeof(struct split_cfa_data));
2693 
2694 	args->seq = seq;
2695 	args->seqEntry = "Green_";
2696 
2697 	int startoptargs = 2;
2698 	if (nb > startoptargs) {
2699 		for (int i = startoptargs; i < nb; i++) {
2700 			if (word[i]) {
2701 				if (g_str_has_prefix(word[i], "-prefix=")) {
2702 					char *current = word[i], *value;
2703 					value = current + 8;
2704 					if (value[0] == '\0') {
2705 						siril_log_message(_("Missing argument to %s, aborting.\n"), current);
2706 						return 1;
2707 					}
2708 					args->seqEntry = strdup(value);
2709 				}
2710 			}
2711 		}
2712 	}
2713 
2714 	set_cursor_waiting(TRUE);
2715 
2716 	apply_extractGreen_to_sequence(args);
2717 
2718 	return 0;
2719 }
2720 
process_seq_extractHaOIII(int nb)2721 int process_seq_extractHaOIII(int nb) {
2722 	if (get_thread_run()) {
2723 		PRINT_ANOTHER_THREAD_RUNNING;
2724 		return 1;
2725 	}
2726 
2727 	sequence *seq = load_sequence(word[1], NULL);
2728 	if (!seq)
2729 		return 1;
2730 
2731 	if (seq->nb_layers > 1) {
2732 		siril_log_message(_("Siril cannot split CFA channel. Make sure your image is in CFA mode.\n"));
2733 		return 1;
2734 	}
2735 
2736 	struct split_cfa_data *args = calloc(1, sizeof(struct split_cfa_data));
2737 
2738 	args->seq = seq;
2739 	args->seqEntry = ""; // not used
2740 
2741 	set_cursor_waiting(TRUE);
2742 
2743 	apply_extractHaOIII_to_sequence(args);
2744 
2745 	return 0;
2746 }
2747 
process_stat(int nb)2748 int process_stat(int nb){
2749 	int nplane;
2750 	int layer;
2751 	char layername[6];
2752 
2753 	nplane = gfit.naxes[2];
2754 
2755 	for (layer = 0; layer < nplane; layer++) {
2756 		imstats* stat = statistics(NULL, -1, &gfit, layer, &com.selection, STATS_MAIN, TRUE);
2757 		if (!stat) {
2758 			siril_log_message(_("Error: statistics computation failed.\n"));
2759 			return 1;
2760 		}
2761 
2762 		switch (layer) {
2763 			case 0:
2764 				if (nplane == 1)
2765 					strcpy(layername, "B&W");
2766 				else
2767 					strcpy(layername, "Red");
2768 				break;
2769 			case 1:
2770 				strcpy(layername, "Green");
2771 				break;
2772 			case 2:
2773 				strcpy(layername, "Blue");
2774 				break;
2775 		}
2776 
2777 		if (gfit.type == DATA_USHORT) {
2778 			siril_log_message(
2779 					_("%s layer: Mean: %0.1lf, Median: %0.1lf, Sigma: %0.1lf, "
2780 							"AvgDev: %0.1lf, Min: %0.1lf, Max: %0.1lf\n"),
2781 					layername, stat->mean, stat->median, stat->sigma,
2782 					stat->avgDev, stat->min, stat->max);
2783 		} else {
2784 			siril_log_message(
2785 					_("%s layer: Mean: %0.1lf, Median: %0.1lf, Sigma: %0.1lf, "
2786 							"AvgDev: %0.1lf, Min: %0.1lf, Max: %0.1lf\n"),
2787 					layername, stat->mean * USHRT_MAX_DOUBLE,
2788 					stat->median * USHRT_MAX_DOUBLE,
2789 					stat->sigma * USHRT_MAX_DOUBLE,
2790 					stat->avgDev * USHRT_MAX_DOUBLE,
2791 					stat->min * USHRT_MAX_DOUBLE, stat->max * USHRT_MAX_DOUBLE);
2792 		}
2793 		free_stats(stat);
2794 	}
2795 	return 0;
2796 }
2797 
process_seq_stat(int nb)2798 int process_seq_stat(int nb) {
2799 	if (get_thread_run()) {
2800 		PRINT_ANOTHER_THREAD_RUNNING;
2801 		return 1;
2802 	}
2803 
2804 	sequence *seq = load_sequence(word[1], NULL);
2805 	if (!seq)
2806 		return 1;
2807 
2808 	struct stat_data *args = calloc(1, sizeof(struct stat_data));
2809 
2810 	args->seq = seq;
2811 	args->seqEntry = ""; // not used
2812 	args->csv_name = g_strdup(word[2]);
2813 
2814 	if (word[3] && !g_strcmp0(word[3], "main")) {
2815 		args->option = STATS_MAIN;
2816 	} else {
2817 		args->option = STATS_BASIC;
2818 	}
2819 
2820 	set_cursor_waiting(TRUE);
2821 
2822 	apply_stats_to_sequence(args);
2823 
2824 	return 0;
2825 }
2826 
process_convertraw(int nb)2827 int process_convertraw(int nb) {
2828 	GDir *dir;
2829 	GError *error = NULL;
2830 	const gchar *file;
2831 	GList *list = NULL;
2832 	int idx = 1;
2833 	gchar *destroot = g_strdup(word[1]);
2834 	sequence_type output = SEQ_REGULAR;
2835 	gboolean debayer = FALSE;
2836 
2837 	if (get_thread_run()) {
2838 		PRINT_ANOTHER_THREAD_RUNNING;
2839 		return 1;
2840 	}
2841 
2842 	if (!com.wd) {
2843 		siril_log_message(_("Conversion: no working directory set.\n"));
2844 		return 1;
2845 	}
2846 
2847 	for (int i = 2; i < nb; i++) {
2848 		char *current = word[i], *value;
2849 		if (!strcmp(current, "-debayer")) {
2850 			debayer = TRUE;
2851 		} else if (!strcmp(current, "-fitseq")) {
2852 			output = SEQ_FITSEQ;
2853 			if (!g_str_has_suffix(destroot, com.pref.ext))
2854 				str_append(&destroot, com.pref.ext);
2855 		} else if (!strcmp(current, "-ser")) {
2856 			output = SEQ_SER;
2857 			if (!g_str_has_suffix(destroot, ".ser"))
2858 				str_append(&destroot, ".ser");
2859 		} else if (g_str_has_prefix(current, "-start=")) {
2860 			value = current + 7;
2861 			idx = (g_ascii_strtoull(value, NULL, 10) <= 0 || g_ascii_strtoull(value, NULL, 10) >= INDEX_MAX) ? 1 : g_ascii_strtoull(value, NULL, 10);
2862 		} else if (g_str_has_prefix(current, "-out=")) {
2863 			value = current + 5;
2864 			if (value[0] == '\0') {
2865 				siril_log_message(_("Missing argument to %s, aborting.\n"), current);
2866 				return 1;
2867 			}
2868 			if (!g_file_test(value, G_FILE_TEST_EXISTS)) {
2869 				if (g_mkdir_with_parents(value, 0755) < 0) {
2870 					siril_log_color_message(_("Cannot create output folder: %s\n"), "red", value);
2871 					return 1;
2872 				}
2873 			}
2874 			gchar *filename = g_build_filename(value, destroot, NULL);
2875 			g_free(destroot);
2876 			destroot = filename;
2877 		}
2878 	}
2879 
2880 	if ((dir = g_dir_open(com.wd, 0, &error)) == NULL){
2881 		siril_log_message(_("Conversion: error opening working directory %s.\n"), com.wd);
2882 		fprintf (stderr, "Conversion: %s\n", error->message);
2883 		g_clear_error(&error);
2884 		return 1;
2885 	}
2886 
2887 	int count = 0;
2888 	while ((file = g_dir_read_name(dir)) != NULL) {
2889 		const char *ext = get_filename_ext(file);
2890 		if (!ext)
2891 			continue;
2892 		image_type type = get_type_for_extension(ext);
2893 		if (type == TYPERAW) {
2894 			if (output == SEQ_SER && !g_ascii_strcasecmp(ext, "raf") && !debayer) {
2895 				siril_log_message(_("FujiFilm XTRANS sensors are not supported by SER v2 (CFA-style) standard. You may use FITS sequences instead."));
2896 				g_list_free_full(list, g_free);
2897 				return 1;
2898 			}
2899 			list = g_list_append(list, g_build_filename(com.wd, file, NULL));
2900 			count++;
2901 		}
2902 	}
2903 	g_dir_close(dir);
2904 	if (!count) {
2905 		siril_log_message(_("No RAW files were found for conversion\n"));
2906 		g_list_free_full(list, g_free);
2907 		return 1;
2908 	}
2909 	/* sort list */
2910 	list = g_list_sort(list, (GCompareFunc) strcompare);
2911 	/* convert the list to an array for parallel processing */
2912 	char **files_to_convert = glist_to_array(list, &count);
2913 
2914 	siril_log_color_message(_("Conversion: processing %d RAW files...\n"), "green", count);
2915 
2916 	set_cursor_waiting(TRUE);
2917 	if (!com.script)
2918 		control_window_switch_to_tab(OUTPUT_LOGS);
2919 
2920 	struct _convert_data *args = malloc(sizeof(struct _convert_data));
2921 	args->start = idx;
2922 	args->list = files_to_convert;
2923 	args->total = count;
2924 	if (output == SEQ_REGULAR)
2925 		args->destroot = format_basename(destroot, TRUE);
2926 	else
2927 		args->destroot = destroot;
2928 	args->input_has_a_seq = FALSE;
2929 	args->input_has_a_film = FALSE;
2930 	args->debayer = debayer;
2931 	args->output_type = output;
2932 	args->multiple_output = FALSE;
2933 	args->make_link = FALSE;
2934 	gettimeofday(&(args->t_start), NULL);
2935 	start_in_new_thread(convert_thread_worker, args);
2936 	return 0;
2937 }
2938 
process_link(int nb)2939 int process_link(int nb) {
2940 	GDir *dir;
2941 	GError *error = NULL;
2942 	const gchar *file;
2943 	GList *list = NULL;
2944 	int idx = 1;
2945 	gchar *destroot = g_strdup(word[1]);
2946 
2947 	if (get_thread_run()) {
2948 		PRINT_ANOTHER_THREAD_RUNNING;
2949 		return 1;
2950 	}
2951 
2952 	for (int i = 2; i < nb; i++) {
2953 		char *current = word[i], *value;
2954 		if (g_str_has_prefix(current, "-start=")) {
2955 			value = current + 7;
2956 			idx = (g_ascii_strtoull(value, NULL, 10) <= 0 ||
2957 					g_ascii_strtoull(value, NULL, 10) >= INDEX_MAX) ?
2958 				1 : g_ascii_strtoull(value, NULL, 10);
2959 		} else if (g_str_has_prefix(current, "-out=")) {
2960 			value = current + 5;
2961 			if (value[0] == '\0') {
2962 				siril_log_message(_("Missing argument to %s, aborting.\n"), current);
2963 				return 1;
2964 			}
2965 			if (!g_file_test(value, G_FILE_TEST_EXISTS)) {
2966 				if (g_mkdir_with_parents(value, 0755) < 0) {
2967 					siril_log_color_message(_("Cannot create output folder: %s\n"), "red", value);
2968 					return 1;
2969 				}
2970 			}
2971 			gchar *filename = g_build_filename(value, destroot, NULL);
2972 			g_free(destroot);
2973 			destroot = filename;
2974 		}
2975 	}
2976 
2977 	if ((dir = g_dir_open(com.wd, 0, &error)) == NULL){
2978 		siril_log_message(_("Link: error opening working directory %s.\n"), com.wd);
2979 		fprintf (stderr, "Link: %s\n", error->message);
2980 		g_clear_error(&error);
2981 		set_cursor_waiting(FALSE);
2982 		return 1;
2983 	}
2984 
2985 	int count = 0;
2986 	while ((file = g_dir_read_name(dir)) != NULL) {
2987 		const char *ext = get_filename_ext(file);
2988 		if (!ext)
2989 			continue;
2990 		image_type type = get_type_for_extension(ext);
2991 		if (type == TYPEFITS) {
2992 			list = g_list_append(list, g_build_filename(com.wd, file, NULL));
2993 			count++;
2994 		}
2995 	}
2996 	g_dir_close(dir);
2997 	if (!count) {
2998 		siril_log_message(_("No FITS files were found for link\n"));
2999 		return 1;
3000 	}
3001 	/* sort list */
3002 	list = g_list_sort(list, (GCompareFunc) strcompare);
3003 	/* convert the list to an array for parallel processing */
3004 	char **files_to_link = glist_to_array(list, &count);
3005 
3006 	gchar *str = ngettext("Link: processing %d FITS file...\n", "Link: processing %d FITS files...\n", count);
3007 	str = g_strdup_printf(str, count);
3008 	siril_log_color_message(str, "green");
3009 	g_free(str);
3010 
3011 	set_cursor_waiting(TRUE);
3012 	if (!com.script)
3013 		control_window_switch_to_tab(OUTPUT_LOGS);
3014 
3015 	if (!com.wd) {
3016 		siril_log_message(_("Link: no working directory set.\n"));
3017 		set_cursor_waiting(FALSE);
3018 		return 1;
3019 	}
3020 
3021 	struct _convert_data *args = malloc(sizeof(struct _convert_data));
3022 	args->start = idx;
3023 	args->list = files_to_link;
3024 	args->total = count;
3025 	args->destroot = format_basename(destroot, TRUE);
3026 	args->input_has_a_seq = FALSE;
3027 	args->input_has_a_film = FALSE;
3028 	args->debayer = FALSE;
3029 	args->multiple_output = FALSE;
3030 	args->output_type = SEQ_REGULAR; // fallback if symlink does not work
3031 	args->make_link = TRUE;
3032 	gettimeofday(&(args->t_start), NULL);
3033 	start_in_new_thread(convert_thread_worker, args);
3034 
3035 	return 0;
3036 }
3037 
process_convert(int nb)3038 int process_convert(int nb) {
3039 	GDir *dir;
3040 	GError *error = NULL;
3041 	const gchar *file;
3042 	GList *list = NULL;
3043 	int idx = 1;
3044 	gboolean debayer = FALSE;
3045 	gboolean make_link = TRUE;
3046 	sequence_type output = SEQ_REGULAR;
3047 	gchar *destroot = g_strdup(word[1]);
3048 
3049 	if (get_thread_run()) {
3050 		PRINT_ANOTHER_THREAD_RUNNING;
3051 		return 1;
3052 	}
3053 
3054 	for (int i = 2; i < nb; i++) {
3055 		char *current = word[i], *value;
3056 		if (!strcmp(current, "-debayer")) {
3057 			debayer = TRUE;
3058 			make_link = FALSE;
3059 		} else if (!strcmp(current, "-fitseq")) {
3060 			output = SEQ_FITSEQ;
3061 			if (!g_str_has_suffix(destroot, com.pref.ext))
3062 				str_append(&destroot, com.pref.ext);
3063 		} else if (!strcmp(current, "-ser")) {
3064 			output = SEQ_SER;
3065 			if (!g_str_has_suffix(destroot, ".ser"))
3066 				str_append(&destroot, ".ser");
3067 		} else if (g_str_has_prefix(current, "-start=")) {
3068 			value = current + 7;
3069 			idx = (g_ascii_strtoull(value, NULL, 10) <= 0 || g_ascii_strtoull(value, NULL, 10) >= INDEX_MAX) ?
3070 				1 : g_ascii_strtoull(value, NULL, 10);
3071 		} else if (g_str_has_prefix(current, "-out=")) {
3072 			value = current + 5;
3073 			if (value[0] == '\0') {
3074 				siril_log_message(_("Missing argument to %s, aborting.\n"), current);
3075 				return 1;
3076 			}
3077 			if (!g_file_test(value, G_FILE_TEST_EXISTS)) {
3078 				if (g_mkdir_with_parents(value, 0755) < 0) {
3079 					siril_log_color_message(_("Cannot create output folder: %s\n"), "red", value);
3080 					return 1;
3081 				}
3082 			}
3083 			gchar *filename = g_build_filename(value, destroot, NULL);
3084 			g_free(destroot);
3085 			destroot = filename;
3086 		}
3087 	}
3088 
3089 	if ((dir = g_dir_open(com.wd, 0, &error)) == NULL){
3090 		siril_log_message(_("Convert: error opening working directory %s.\n"), com.wd);
3091 		fprintf (stderr, "Convert: %s\n", error->message);
3092 		g_clear_error(&error);
3093 		set_cursor_waiting(FALSE);
3094 		return 1;
3095 	}
3096 
3097 	int count = 0;
3098 	while ((file = g_dir_read_name(dir)) != NULL) {
3099 		const char *ext = get_filename_ext(file);
3100 		if (!ext)
3101 			continue;
3102 		image_type type = get_type_for_extension(ext);
3103 		if (type != TYPEUNDEF && type != TYPEAVI && type != TYPESER) {
3104 			list = g_list_append(list, g_build_filename(com.wd, file, NULL));
3105 			count++;
3106 		}
3107 	}
3108 	g_dir_close(dir);
3109 	if (!count) {
3110 		siril_log_message(_("No files were found for convert\n"));
3111 		return 1;
3112 	}
3113 	/* sort list */
3114 	list = g_list_sort(list, (GCompareFunc) strcompare);
3115 	/* convert the list to an array for parallel processing */
3116 	char **files_to_link = glist_to_array(list, &count);
3117 
3118 	gchar *str = ngettext("Convert: processing %d FITS file...\n", "Convert: processing %d FITS files...\n", count);
3119 	str = g_strdup_printf(str, count);
3120 	siril_log_color_message(str, "green");
3121 	g_free(str);
3122 
3123 	set_cursor_waiting(TRUE);
3124 	if (!com.script)
3125 		control_window_switch_to_tab(OUTPUT_LOGS);
3126 
3127 	if (!com.wd) {
3128 		siril_log_message(_("Convert: no working directory set.\n"));
3129 		set_cursor_waiting(FALSE);
3130 		return 1;
3131 	}
3132 
3133 	struct _convert_data *args = malloc(sizeof(struct _convert_data));
3134 	args->start = idx;
3135 	args->list = files_to_link;
3136 	args->total = count;
3137 	if (output == SEQ_REGULAR)
3138 		args->destroot = format_basename(destroot, TRUE);
3139 	else
3140 		args->destroot = destroot;
3141 	args->input_has_a_seq = FALSE;
3142 	args->input_has_a_film = FALSE;
3143 	args->debayer = debayer;
3144 	args->multiple_output = FALSE;
3145 	args->output_type = output;
3146 	args->make_link = make_link;
3147 	gettimeofday(&(args->t_start), NULL);
3148 	start_in_new_thread(convert_thread_worker, args);
3149 
3150 	return 0;
3151 }
3152 
process_register(int nb)3153 int process_register(int nb) {
3154 	struct registration_args *reg_args;
3155 	struct registration_method *method;
3156 	char *msg;
3157 	int i;
3158 
3159 	if (get_thread_run()) {
3160 		PRINT_ANOTHER_THREAD_RUNNING;
3161 		return 1;
3162 	}
3163 	sequence *seq = load_sequence(word[1], NULL);
3164 	if (!seq)
3165 		return 1;
3166 
3167 	/* getting the selected registration method */
3168 	method = malloc(sizeof(struct registration_method));
3169 	method->name = strdup(_("Global Star Alignment (deep-sky)"));
3170 	method->method_ptr = &register_star_alignment;
3171 	method->sel = REQUIRES_NO_SELECTION;
3172 	method->type = REGTYPE_DEEPSKY;
3173 
3174 	reg_args = calloc(1, sizeof(struct registration_args));
3175 
3176 	if (!com.script)
3177 		control_window_switch_to_tab(OUTPUT_LOGS);
3178 
3179 	/* filling the arguments for registration */
3180 	reg_args->func = method->method_ptr;
3181 	reg_args->seq = seq;
3182 	reg_args->reference_image = sequence_find_refimage(seq);
3183 	reg_args->process_all_frames = TRUE;
3184 	reg_args->follow_star = FALSE;
3185 	reg_args->matchSelection = FALSE;
3186 	reg_args->translation_only = FALSE;
3187 	reg_args->x2upscale = FALSE;
3188 	reg_args->prefix = "r_";
3189 	reg_args->min_pairs = AT_MATCH_MINPAIRS;
3190 
3191 	/* check for options */
3192 	for (i = 2; i < nb; i++) {
3193 		if (word[i]) {
3194 			if (!strcmp(word[i], "-drizzle")) {
3195 				reg_args->x2upscale = TRUE;
3196 			} else if (!strcmp(word[i], "-norot")) {
3197 				reg_args->translation_only = TRUE;
3198 			} else if (g_str_has_prefix(word[i], "-prefix=")) {
3199 				char *current = word[i], *value;
3200 				value = current + 8;
3201 				if (value[0] == '\0') {
3202 					siril_log_message(_("Missing argument to %s, aborting.\n"), current);
3203 					return 1;
3204 				}
3205 				reg_args->prefix = strdup(value);
3206 			} else if (g_str_has_prefix(word[i], "-minpairs=")) {
3207 				char *current = word[i], *value;
3208 				value = current + 10;
3209 				if (value[0] == '\0') {
3210 					siril_log_message(_("Missing argument to %s, aborting.\n"), current);
3211 					return 1;
3212 				}
3213 				if (g_ascii_strtoull(value, NULL, 10) < AT_MATCH_MINPAIRS) {
3214 					gchar *str = ngettext("%d smaller than minimum allowable star pairs: %d, aborting.\n", "%d smaller than minimum allowable star pairs: %d, aborting.\n",
3215 							g_ascii_strtoull(value, NULL, 10));
3216 					str = g_strdup_printf(str, g_ascii_strtoull(value, NULL, 10), AT_MATCH_MINPAIRS);
3217 					siril_log_message(str);
3218 					g_free(str);
3219 
3220 					return 1;
3221 				}
3222 				reg_args->min_pairs = g_ascii_strtoull(value, NULL, 10);
3223 			}
3224 		}
3225 	}
3226 
3227 	// testing free space
3228 	if (reg_args->x2upscale ||
3229 			(method->method_ptr == register_star_alignment &&
3230 			 !reg_args->translation_only)) {
3231 		// first, remove the files that we are about to create
3232 		remove_prefixed_sequence_files(reg_args->seq, reg_args->prefix);
3233 
3234 		int nb_frames = reg_args->process_all_frames ? reg_args->seq->number : reg_args->seq->selnum;
3235 		int64_t size = seq_compute_size(reg_args->seq, nb_frames, get_data_type(seq->bitpix));
3236 		if (reg_args->x2upscale)
3237 			size *= 4;
3238 		if (test_available_space(size) > 0) {
3239 			free(reg_args);
3240 			free(method);
3241 			return 1;
3242 		}
3243 	}
3244 
3245 	/* getting the selected registration layer from the combo box. The value is the index
3246 	 * of the selected line, and they are in the same order than layers so there should be
3247 	 * an exact matching between the two */
3248 	reg_args->layer = (reg_args->seq->nb_layers == 3) ? 1 : 0;
3249 	reg_args->interpolation = OPENCV_AREA;
3250 	get_the_registration_area(reg_args, method);	// sets selection
3251 	reg_args->run_in_thread = TRUE;
3252 	reg_args->load_new_sequence = FALSE;	// don't load it for command line execution
3253 
3254 	msg = siril_log_color_message(
3255 			_("Registration: processing using method: %s\n"), "green",
3256 			method->name);
3257 	free(method);
3258 	msg[strlen(msg) - 1] = '\0';
3259 	set_progress_bar_data(msg, PROGRESS_RESET);
3260 
3261 	set_cursor_waiting(TRUE);
3262 
3263 	start_in_new_thread(register_thread_func, reg_args);
3264 	return 0;
3265 }
3266 
3267 // parse normalization and filters from the stack command line, starting at word `first'
parse_stack_command_line(struct stacking_configuration * arg,int first,gboolean norm_allowed,gboolean out_allowed)3268 static int parse_stack_command_line(struct stacking_configuration *arg, int first, gboolean norm_allowed, gboolean out_allowed) {
3269 	while (word[first]) {
3270 		char *current = word[first], *value;
3271 		if (!strcmp(current, "-nonorm") || !strcmp(current, "-no_norm"))
3272 			arg->force_no_norm = TRUE;
3273 		else if (!strcmp(current, "-output_norm")) {
3274 			arg->output_norm = TRUE;
3275 		} else if (!strcmp(current, "-weighted")) {
3276 			if (arg->method != stack_mean_with_rejection) {
3277 				siril_log_message(_("Weighting is allowed only with average stacking, ignoring.\n"));
3278 			} else if (arg->norm == NO_NORM) {
3279 				siril_log_message(_("Weighting is allowed only if normalization has been activated, ignoring.\n"));
3280 			} else{
3281 				arg->apply_weight = TRUE;
3282 			}
3283 		} else if (g_str_has_prefix(current, "-norm=")) {
3284 			if (!norm_allowed) {
3285 				siril_log_message(_("Normalization options are not allowed in this context, ignoring.\n"));
3286 			} else {
3287 				value = current + 6;
3288 				if (!strcmp(value, "add"))
3289 					arg->norm = ADDITIVE;
3290 				else if (!strcmp(value, "addscale"))
3291 					arg->norm = ADDITIVE_SCALING;
3292 				else if (!strcmp(value, "mul"))
3293 					arg->norm = MULTIPLICATIVE;
3294 				else if (!strcmp(value, "mulscale"))
3295 					arg->norm = MULTIPLICATIVE_SCALING;
3296 			}
3297 		} else if (g_str_has_prefix(current, "-filter-fwhm=")) {
3298 			value = strchr(current, '=') + 1;
3299 			if (value[0] != '\0') {
3300 				char *end;
3301 				float val = strtof(value, &end);
3302 				if (end == value) {
3303 					siril_log_message(_("Could not parse argument `%s' to the filter `%s', aborting.\n"), value, current);
3304 					return 1;
3305 				}
3306 				if (*end == '%')
3307 					arg->f_fwhm_p = val;
3308 				else arg->f_fwhm = val;
3309 			} else {
3310 				siril_log_message(_("Missing argument to %s, aborting.\n"), current);
3311 				return 1;
3312 			}
3313 		} else if (g_str_has_prefix(current, "-filter-wfwhm=")) {
3314 			value = strchr(current, '=') + 1;
3315 			if (value[0] != '\0') {
3316 				char *end;
3317 				float val = strtof(value, &end);
3318 				if (end == value) {
3319 					siril_log_message(_("Could not parse argument `%s' to the filter `%s', aborting.\n"), value, current);
3320 					return 1;
3321 				}
3322 				if (*end == '%')
3323 					arg->f_wfwhm_p = val;
3324 				else arg->f_wfwhm = val;
3325 			} else {
3326 				siril_log_message(_("Missing argument to %s, aborting.\n"), current);
3327 				return 1;
3328 			}
3329 		} else if (g_str_has_prefix(current, "-filter-round=") ||
3330 				g_str_has_prefix(current, "-filter-roundness=")) {
3331 			value = strchr(current, '=') + 1;
3332 			if (value[0] != '\0') {
3333 				char *end;
3334 				float val = strtof(value, &end);
3335 				if (end == value) {
3336 					siril_log_message(_("Could not parse argument `%s' to the filter `%s', aborting.\n"), value, current);
3337 					return 1;
3338 				}
3339 				if (*end == '%')
3340 					arg->f_round_p = val;
3341 				else arg->f_round = val;
3342 			} else {
3343 				siril_log_message(_("Missing argument to %s, aborting.\n"), current);
3344 				return 1;
3345 			}
3346 		} else if (g_str_has_prefix(current, "-filter-qual=") ||
3347 				g_str_has_prefix(current, "-filter-quality=")) {
3348 			value = strchr(current, '=') + 1;
3349 			if (value[0] != '\0') {
3350 				char *end;
3351 				float val = strtof(value, &end);
3352 				if (end == value) {
3353 					siril_log_message(_("Could not parse argument `%s' to the filter `%s', aborting.\n"), value, current);
3354 					return 1;
3355 				}
3356 				if (*end == '%')
3357 					arg->f_quality_p = val;
3358 				else arg->f_quality = val;
3359 			} else {
3360 				siril_log_message(_("Missing argument to %s, aborting.\n"), current);
3361 				return 1;
3362 			}
3363 		} else if (g_str_has_prefix(current, "-filter-incl") ||
3364 				g_str_has_prefix(current, "-filter-included")) {
3365 			arg->filter_included = TRUE;
3366 		} else if (g_str_has_prefix(current, "-out=")) {
3367 			if (out_allowed) {
3368 				value = current + 5;
3369 				if (value[0] == '\0') {
3370 					siril_log_message(_("Missing argument to %s, aborting.\n"), current);
3371 					return 1;
3372 				}
3373 				arg->result_file = strdup(value);
3374 			}
3375 			else {
3376 				siril_log_message(_("Output filename option is not allowed in this context, ignoring.\n"));
3377 			}
3378 		}
3379 		else {
3380 			siril_log_message(_("Unexpected argument to stacking `%s', aborting.\n"), current);
3381 			return 1;
3382 		}
3383 		first++;
3384 	}
3385 	return 0;
3386 }
3387 
stack_one_seq(struct stacking_configuration * arg)3388 static int stack_one_seq(struct stacking_configuration *arg) {
3389 	int retval = -1;
3390 	sequence *seq = readseqfile(arg->seqfile);
3391 	if (seq != NULL) {
3392 		struct stacking_args args = { 0 };
3393 		gchar *error = NULL;
3394 		if (seq_check_basic_data(seq, FALSE) == -1) {
3395 			free(seq);
3396 			return 1;
3397 		}
3398 		siril_log_message(_("Stacking sequence %s\n"), seq->seqname);
3399 		args.seq = seq;
3400 		args.ref_image = sequence_find_refimage(seq);
3401 		// the three below: used only if method is average w/ rejection
3402 		if (arg->method == stack_mean_with_rejection && (arg->sig[0] != 0.0 || arg->sig[1] != 0.0)) {
3403 			args.sig[0] = arg->sig[0];
3404 			args.sig[1] = arg->sig[1];
3405 			args.type_of_rejection = arg->type_of_rejection;
3406 		} else {
3407 			args.type_of_rejection = NO_REJEC;
3408 			siril_log_message(_("Not using rejection for stacking\n"));
3409 		}
3410 		args.coeff.offset = NULL;
3411 		args.coeff.mul = NULL;
3412 		args.coeff.scale = NULL;
3413 		if (!arg->force_no_norm &&
3414 				(arg->method == stack_median || arg->method == stack_mean_with_rejection))
3415 			args.normalize = arg->norm;
3416 		else args.normalize = NO_NORM;
3417 		args.method = arg->method;
3418 		args.force_norm = FALSE;
3419 		args.output_norm = arg->output_norm;
3420 		args.reglayer = args.seq->nb_layers == 1 ? 0 : 1;
3421 		args.apply_weight = arg->apply_weight;
3422 
3423 		// manage filters
3424 		if (convert_stack_data_to_filter(arg, &args) ||
3425 				setup_filtered_data(&args)) {
3426 			free_sequence(seq, TRUE);
3427 			return 1;
3428 		}
3429 		args.description = describe_filter(seq, args.filtering_criterion, args.filtering_parameter);
3430 		args.use_32bit_output = evaluate_stacking_should_output_32bits(args.method,
3431 			args.seq, args.nb_images_to_stack, &error);
3432 		if (error) {
3433 			siril_log_color_message(error, "red");
3434 			free_sequence(seq, TRUE);
3435 			return 1;
3436 		}
3437 
3438 		if (!arg->result_file) {
3439 			char filename[256];
3440 			char *suffix = g_str_has_suffix(seq->seqname, "_") ||
3441 				g_str_has_suffix(seq->seqname, "-") ? "" : "_";
3442 			snprintf(filename, 256, "%s%sstacked%s",
3443 					seq->seqname, suffix, com.pref.ext);
3444 			arg->result_file = strdup(filename);
3445 		}
3446 
3447 		main_stack(&args);
3448 
3449 		retval = args.retval;
3450 		clean_end_stacking(&args);
3451 		free_sequence(seq, TRUE);
3452 		free(args.image_indices);
3453 		free(args.description);
3454 
3455 		if (!retval) {
3456 			struct noise_data noise_args = { .fit = &gfit, .verbose = FALSE, .use_idle = FALSE };
3457 			noise(&noise_args);
3458 			if (savefits(arg->result_file, &gfit))
3459 				siril_log_color_message(_("Could not save the stacking result %s\n"),
3460 						"red", arg->result_file);
3461 			//clearfits(&gfit);
3462 			++arg->number_of_loaded_sequences;
3463 		}
3464 		else if (!get_thread_run()) return -1;
3465 
3466 	} else {
3467 		siril_log_message(_("No sequence `%s' found.\n"), arg->seqfile);
3468 	}
3469 	return retval;
3470 }
3471 
stackall_worker(gpointer garg)3472 static gpointer stackall_worker(gpointer garg) {
3473 	GDir *dir;
3474 	GError *error = NULL;
3475 	const gchar *file;
3476 	struct timeval t_end;
3477 	struct stacking_configuration *arg = (struct stacking_configuration *)garg;
3478 	gboolean was_in_script = com.script;
3479 	com.script = TRUE;
3480 
3481 	siril_log_message(_("Looking for sequences in current working directory...\n"));
3482 	if (check_seq(FALSE) || (dir = g_dir_open(com.wd, 0, &error)) == NULL) {
3483 		siril_log_message(_("Error while searching sequences or opening the directory.\n"));
3484 		if (error) {
3485 			fprintf(stderr, "stackall: %s\n", error->message);
3486 			g_clear_error(&error);
3487 		}
3488 		siril_add_idle(end_generic, NULL);
3489 		return NULL;
3490 	}
3491 
3492 	siril_log_message(_("Starting stacking of found sequences...\n"));
3493 	while ((file = g_dir_read_name(dir)) != NULL) {
3494 		if (g_str_has_suffix(file, ".seq")) {
3495 			arg->seqfile = strdup(file);
3496 			stack_one_seq(arg);
3497 
3498 			g_free(arg->result_file);
3499 			arg->result_file = NULL;
3500 			g_free(arg->seqfile);
3501 		}
3502 	}
3503 
3504 	siril_log_message(_("Stacked %d sequences successfully.\n"), arg->number_of_loaded_sequences);
3505 	gettimeofday(&t_end, NULL);
3506 	show_time(arg->t_start, t_end);
3507 	g_dir_close(dir);
3508 	free(arg);
3509 	com.script = was_in_script;
3510 	siril_add_idle(end_generic, NULL);
3511 	return NULL;
3512 }
3513 
process_stackall(int nb)3514 int process_stackall(int nb) {
3515 	struct stacking_configuration *arg;
3516 
3517 	arg = calloc(1, sizeof(struct stacking_configuration));
3518 	arg->f_fwhm = -1.f; arg->f_fwhm_p = -1.f; arg->f_round = -1.f;
3519 	arg->f_round_p = -1.f; arg->f_quality = -1.f; arg->f_quality_p = -1.f;
3520 	arg->filter_included = FALSE; arg->norm = NO_NORM; arg->force_no_norm = FALSE;
3521 	arg->apply_weight = FALSE;
3522 
3523 	// stackall { sum | min | max } [-filter-fwhm=value[%]] [-filter-wfwhm=value[%]] [-filter-round=value[%]] [-filter-quality=value[%]] [-filter-incl[uded]]
3524 	// stackall { med | median } [-nonorm, norm=] [-filter-incl[uded]]
3525 	// stackall { rej | mean } sigma_low sigma_high [-nonorm, norm=] [-filter-fwhm=value[%]] [-filter-round=value[%]] [-filter-quality=value[%]] [-filter-incl[uded]] [-weighted]
3526 	if (!word[1]) {
3527 		arg->method = stack_summing_generic;
3528 	} else {
3529 		int start_arg_opt = 2;
3530 		gboolean allow_norm = FALSE;
3531 		if (!strcmp(word[1], "sum")) {
3532 			arg->method = stack_summing_generic;
3533 		} else if (!strcmp(word[1], "max")) {
3534 			arg->method = stack_addmax;
3535 		} else if (!strcmp(word[1], "min")) {
3536 			arg->method = stack_addmin;
3537 		} else if (!strcmp(word[1], "med") || !strcmp(word[1], "median")) {
3538 			arg->method = stack_median;
3539 			allow_norm = TRUE;
3540 		} else if (!strcmp(word[1], "rej") || !strcmp(word[1], "mean")) {
3541 			int shift = 1;
3542 			if (!strcmp(word[3], "p") || !strcmp(word[3], "percentile")) {
3543 				arg->type_of_rejection = PERCENTILE;
3544 			} else if (!strcmp(word[3], "s") || !strcmp(word[3], "sigma")) {
3545 				arg->type_of_rejection = SIGMA;
3546 			} else if (!strcmp(word[3], "a") || !strcmp(word[3], "mad")) {
3547 				arg->type_of_rejection = MAD;
3548 			} else if (!strcmp(word[3], "m") || !strcmp(word[3], "median")) {
3549 				arg->type_of_rejection = SIGMEDIAN;
3550 			} else if (!strcmp(word[3], "l") || !strcmp(word[3], "linear")) {
3551 				arg->type_of_rejection = LINEARFIT;
3552 			} else if (!strcmp(word[3], "w") || !strcmp(word[3], "winsorized")) {
3553 				arg->type_of_rejection = WINSORIZED;
3554 			} else if (!strcmp(word[3], "g") || !strcmp(word[3], "generalized")) {
3555 				arg->type_of_rejection = GESDT;
3556 			} else {
3557 				arg->type_of_rejection = WINSORIZED;
3558 				shift = 0;
3559 			}
3560 			if (!word[2 + shift] || !word[3 + shift] || (arg->sig[0] = g_ascii_strtod(word[2 + shift], NULL)) < 0.0
3561 					|| (arg->sig[1] = g_ascii_strtod(word[3 + shift], NULL)) < 0.0) {
3562 				siril_log_color_message(_("The average stacking with rejection requires two extra arguments: sigma low and high.\n"), "red");
3563 				goto failure;
3564 			}
3565 			if (((arg->type_of_rejection == GESDT)
3566 					|| (arg->type_of_rejection == PERCENTILE))
3567 					&& (arg->sig[0] > 1.0 || (arg->sig[1] > 1.0))) {
3568 				siril_log_color_message(_("Extra parameters of this rejection algorithm must be between 0 and 1.\n"), "red");
3569 				goto failure;
3570 			}
3571 			arg->method = stack_mean_with_rejection;
3572 			start_arg_opt = 4 + shift;
3573 			allow_norm = TRUE;
3574 		}
3575 		else {
3576 			siril_log_color_message(_("Stacking method type '%s' is invalid\n"), "red", word[2]);
3577 			goto failure;
3578 		}
3579 		if (parse_stack_command_line(arg, start_arg_opt, allow_norm, FALSE))
3580 			goto failure;
3581 	}
3582 	set_cursor_waiting(TRUE);
3583 	if (!com.headless)
3584 		control_window_switch_to_tab(OUTPUT_LOGS);
3585 	gettimeofday(&arg->t_start, NULL);
3586 
3587 	start_in_new_thread(stackall_worker, arg);
3588 	return 0;
3589 
3590 failure:
3591 	g_free(arg->result_file);
3592 	g_free(arg->seqfile);
3593 	free(arg);
3594 	return 1;
3595 }
3596 
stackone_worker(gpointer garg)3597 static gpointer stackone_worker(gpointer garg) {
3598 	int retval = 0;
3599 	struct timeval t_end;
3600 	struct stacking_configuration *arg = (struct stacking_configuration *)garg;
3601 	gboolean was_in_script = com.script;
3602 	com.script = TRUE;
3603 
3604 	retval = stack_one_seq(arg);
3605 
3606 	if (retval) {
3607 		if (retval == ST_ALLOC_ERROR) {
3608 			siril_log_message(_("It looks like there is a memory allocation error, change memory settings and try to fix it.\n"));
3609 		}
3610 	} else {
3611 		siril_log_message(_("Stacked sequence successfully.\n"));
3612 	}
3613 
3614 	gettimeofday(&t_end, NULL);
3615 	show_time(arg->t_start, t_end);
3616 
3617 	g_free(arg->result_file);
3618 	g_free(arg->seqfile);
3619 	free(arg);
3620 	com.script = was_in_script;
3621 	siril_add_idle(end_generic, NULL);
3622 	return GINT_TO_POINTER(retval);
3623 }
3624 
process_stackone(int nb)3625 int process_stackone(int nb) {
3626 	struct stacking_configuration *arg;
3627 
3628 	arg = calloc(1, sizeof(struct stacking_configuration));
3629 	arg->f_fwhm = -1.f; arg->f_fwhm_p = -1.f; arg->f_round = -1.f;
3630 	arg->f_round_p = -1.f; arg->f_quality = -1.f; arg->f_quality_p = -1.f;
3631 	arg->filter_included = FALSE; arg->norm = NO_NORM; arg->force_no_norm = FALSE;
3632 	arg->apply_weight = FALSE;
3633 
3634 	sequence *seq = load_sequence(word[1], &arg->seqfile);
3635 	if (!seq)
3636 		goto failure;
3637 
3638 	// stack seqfilename { sum | min | max } [-filter-fwhm=value[%]] [-filter-wfwhm=value[%]] [-filter-round=value[%]] [-filter-quality=value[%]] [-filter-incl[uded]] -out=result_filename
3639 	// stack seqfilename { med | median } [-nonorm, norm=] [-filter-incl[uded]] -out=result_filename
3640 	// stack seqfilename { rej | mean } sigma_low sigma_high [-nonorm, norm=] [-filter-fwhm=value[%]] [-filter-round=value[%]] [-filter-quality=value[%]] [-filter-incl[uded]] [-weighted] -out=result_filename
3641 	if (!word[2]) {
3642 		arg->method = stack_summing_generic;
3643 	} else {
3644 		int start_arg_opt = 3;
3645 		gboolean allow_norm = FALSE;
3646 		if (!strcmp(word[2], "sum")) {
3647 			arg->method = stack_summing_generic;
3648 		} else if (!strcmp(word[2], "max")) {
3649 			arg->method = stack_addmax;
3650 		} else if (!strcmp(word[2], "min")) {
3651 			arg->method = stack_addmin;
3652 		} else if (!strcmp(word[2], "med") || !strcmp(word[2], "median")) {
3653 			arg->method = stack_median;
3654 			allow_norm = TRUE;
3655 		} else if (!strcmp(word[2], "rej") || !strcmp(word[2], "mean")) {
3656 			int shift = 1;
3657 			if (!strcmp(word[3], "p") || !strcmp(word[3], "percentile")) {
3658 				arg->type_of_rejection = PERCENTILE;
3659 			} else if (!strcmp(word[3], "s") || !strcmp(word[3], "sigma")) {
3660 				arg->type_of_rejection = SIGMA;
3661 			} else if (!strcmp(word[3], "a") || !strcmp(word[3], "mad")) {
3662 				arg->type_of_rejection = MAD;
3663 			} else if (!strcmp(word[3], "m") || !strcmp(word[3], "median")) {
3664 				arg->type_of_rejection = SIGMEDIAN;
3665 			} else if (!strcmp(word[3], "l") || !strcmp(word[3], "linear")) {
3666 				arg->type_of_rejection = LINEARFIT;
3667 			} else if (!strcmp(word[3], "w") || !strcmp(word[3], "winsorized")) {
3668 				arg->type_of_rejection = WINSORIZED;
3669 			} else if (!strcmp(word[3], "g") || !strcmp(word[3], "generalized")) {
3670 				arg->type_of_rejection = GESDT;
3671 			} else {
3672 				arg->type_of_rejection = WINSORIZED;
3673 				shift = 0;
3674 			}
3675 			if (!word[3 + shift] || !word[4 + shift] || (arg->sig[0] = g_ascii_strtod(word[3 + shift], NULL)) < 0.0
3676 					|| (arg->sig[1] = g_ascii_strtod(word[4 + shift], NULL)) < 0.0) {
3677 				siril_log_color_message(_("The average stacking with rejection requires two extra arguments: sigma low and high.\n"), "red");
3678 				goto failure;
3679 			}
3680 			arg->method = stack_mean_with_rejection;
3681 			start_arg_opt = 5 + shift;
3682 			allow_norm = TRUE;
3683 		}
3684 		else {
3685 			siril_log_color_message(_("Stacking method type '%s' is invalid\n"), "red", word[2]);
3686 			goto failure;
3687 		}
3688 		if (parse_stack_command_line(arg, start_arg_opt, allow_norm, TRUE))
3689 			goto failure;
3690 	}
3691 	set_cursor_waiting(TRUE);
3692 	gettimeofday(&arg->t_start, NULL);
3693 	if (!com.headless)
3694 		control_window_switch_to_tab(OUTPUT_LOGS);
3695 
3696 	start_in_new_thread(stackone_worker, arg);
3697 	return 0;
3698 
3699 failure:
3700 	g_free(arg->result_file);
3701 	g_free(arg->seqfile);
3702 	free(arg);
3703 	return 1;
3704 }
3705 
process_preprocess(int nb)3706 int process_preprocess(int nb) {
3707 	struct preprocessing_data *args;
3708 	int i, retvalue = 0;
3709 
3710 	if (word[1][0] == '\0') {
3711 		return -1;
3712 	}
3713 
3714 	sequence *seq = load_sequence(word[1], NULL);
3715 	if (!seq)
3716 		return 1;
3717 
3718 	args = calloc(1, sizeof(struct preprocessing_data));
3719 	args->ppprefix = "pp_";
3720 	args->bias_level = FLT_MAX;
3721 	if (seq->type == SEQ_SER)  args->output_seqtype = SEQ_SER; // to be able to check allow_32bit_output. Overiden by -fitseq if required
3722 
3723 	/* checking for options */
3724 	for (i = 2; i < nb; i++) {
3725 		if (word[i]) {
3726 			if (g_str_has_prefix(word[i], "-bias=")) {
3727 				gchar *expression = g_shell_unquote(word[i] + 6, NULL);
3728 				if (expression[0] == '=') {
3729 					int offsetlevel = evaluateoffsetlevel(expression+1);
3730 					if (!offsetlevel) {
3731 						siril_log_message(_("The offset value could not be parsed from expression: %s, aborting.\n"), expression +1);
3732 						g_free(expression);
3733 						retvalue = 1;
3734 						break;
3735 					} else {
3736 						g_free(expression);
3737 						siril_log_message(_("Synthetic offset: Level = %d\n"), offsetlevel);
3738 						int maxlevel = (gfit.orig_bitpix == BYTE_IMG) ? UCHAR_MAX : USHRT_MAX;
3739 						if ((offsetlevel > maxlevel) || (offsetlevel < -maxlevel) ) {   // not excluding all neg values here to allow defining a pedestal
3740 							siril_log_message(_("The offset value is out of allowable bounds [-%d,%d], aborting.\n"), maxlevel, maxlevel);
3741 							retvalue = 1;
3742 							break;
3743 						} else {
3744 							args->bias_level = (float)offsetlevel * INV_USHRT_MAX_SINGLE; //converting to [0 1] to use with soper
3745 							args->use_bias = TRUE;
3746 						}
3747 					}
3748 				} else {
3749 					g_free(expression);
3750 					args->bias = calloc(1, sizeof(fits));
3751 					if (!readfits(word[i] + 6, args->bias, NULL, !com.pref.force_to_16bit)) {
3752 						args->use_bias = TRUE;
3753 					} else {
3754 						retvalue = 1;
3755 						free(args->bias);
3756 						break;
3757 					}
3758 				}
3759 			} else if (g_str_has_prefix(word[i], "-dark=")) {
3760 				args->dark = calloc(1, sizeof(fits));
3761 				if (!readfits(word[i] + 6, args->dark, NULL, !com.pref.force_to_16bit)) {
3762 					args->use_dark = TRUE;
3763 					args->use_cosmetic_correction = TRUE;
3764 				} else {
3765 					retvalue = 1;
3766 					free(args->dark);
3767 					break;
3768 				}
3769 			} else if (g_str_has_prefix(word[i], "-flat=")) {
3770 				args->flat = calloc(1, sizeof(fits));
3771 				if (!readfits(word[i] + 6, args->flat, NULL, !com.pref.force_to_16bit)) {
3772 					args->use_flat = TRUE;
3773 				} else {
3774 					retvalue = 1;
3775 					free(args->flat);
3776 					break;
3777 				}
3778 			} else if (g_str_has_prefix(word[i], "-prefix=")) {
3779 				char *current = word[i], *value;
3780 				value = current + 8;
3781 				if (value[0] == '\0') {
3782 					siril_log_message(_("Missing argument to %s, aborting.\n"), current);
3783 					retvalue = 1;
3784 					break;
3785 				}
3786 				args->ppprefix = strdup(value);
3787 			} else if (!strcmp(word[i], "-opt")) {
3788 				args->use_dark_optim = TRUE;
3789 			} else if (!strcmp(word[i], "-fix_xtrans")) {
3790 				args->fix_xtrans = TRUE;
3791 			} else if (!strcmp(word[i], "-cfa")) {
3792 				args->is_cfa = TRUE;
3793 			} else if (!strcmp(word[i], "-debayer")) {
3794 				args->debayer = TRUE;
3795 			} else if (!strcmp(word[i], "-stretch")) {
3796 				siril_log_message(_("-stretch option is now deprecated.\n")); // TODO. Should we keep it only for compatibility?
3797 			} else if (!strcmp(word[i], "-flip")) {
3798 				siril_log_message(_("-flip option is now deprecated.\n")); // TODO. Should we keep it only for compatibility?
3799 			} else if (!strcmp(word[i], "-equalize_cfa")) {
3800 				args->equalize_cfa = TRUE;
3801 			} else if (!strcmp(word[i], "-fitseq")) {
3802 				args->output_seqtype = SEQ_FITSEQ;
3803 			}
3804 		}
3805 	}
3806 
3807 	if (retvalue) {
3808 		free(args);
3809 		return -1;
3810 	}
3811 
3812 	siril_log_color_message(_("Preprocessing...\n"), "green");
3813 	gettimeofday(&args->t_start, NULL);
3814 	args->seq = seq;
3815 	args->is_sequence = TRUE;
3816 	args->autolevel = TRUE;
3817 	args->normalisation = 1.0f;	// will be updated anyway
3818 	args->sigma[0] = -1.00; /* cold pixels: it is better to deactivate it */
3819 	args->sigma[1] =  3.00; /* hot pixels */
3820 	args->allow_32bit_output = (args->output_seqtype == SEQ_REGULAR
3821 			|| args->output_seqtype == SEQ_FITSEQ) && !com.pref.force_to_16bit;
3822 
3823 	// start preprocessing
3824 	set_cursor_waiting(TRUE);
3825 
3826 	start_sequence_preprocessing(args);
3827 	return 0;
3828 }
3829 
process_set_32bits(int nb)3830 int process_set_32bits(int nb) {
3831 	com.pref.force_to_16bit = word[0][3] == '1';
3832 	if (com.pref.force_to_16bit)
3833 		siril_log_message(_("16-bit per channel in processed images mode is active\n"));
3834 	else siril_log_message(_("32-bit per channel in processed images mode is active\n"));
3835 	writeinitfile();
3836 	if (!com.headless)
3837 		set_GUI_misc();
3838 	return 0;
3839 }
3840 
process_set_compress(int nb)3841 int process_set_compress(int nb) {
3842 	gboolean compress = g_ascii_strtoull(word[1], NULL, 10) == 1;
3843 	int method = 0;
3844 	double q = 16.0, hscale= 4.0;
3845 
3846 	if (compress) {
3847 		if (!word[2] || !word[3] || (!g_str_has_prefix(word[2], "-type="))) {
3848 			siril_log_message(_("Please specify the type of compression and quantization value.\n"));
3849 			return 1;
3850 		}
3851 		gchar *comp = NULL;
3852 		if (!g_ascii_strncasecmp(word[2] + 6, "rice", 4)) {
3853 			method = RICE_COMP;
3854 			comp = g_strdup("rice");
3855 		} else if (!g_ascii_strncasecmp(word[2] + 6, "gzip1", 5)) {
3856 			method = GZIP1_COMP;
3857 			comp = g_strdup("GZIP1");
3858 		} else if (!g_ascii_strncasecmp(word[2] + 6, "gzip2", 5))  {
3859 			method = GZIP2_COMP;
3860 			comp = g_strdup("GZIP2");
3861 		} /*else if (!g_ascii_strncasecmp(word[2] + 6, "hcompress", 9)) {
3862 			method = HCOMPRESS_COMP;
3863 			if (!word[4]) {
3864 				siril_log_message(_("Please specify the value of hcompress scale factor.\n"));
3865 				g_free(comp);
3866 				return 1;
3867 			}
3868 			hscale = g_ascii_strtod(word[4], NULL);
3869 			comp = g_strdup_printf("hcompress (scale factor = %.2lf) ", hscale);
3870 		}*/ else {
3871 //			siril_log_message(_("Wrong type of compression. Choices are rice, gzip1, gzip2 or hcompress\n"));
3872 			siril_log_message(_("Wrong type of compression. Choices are rice, gzip1, gzip2\n"));
3873 			return 1;
3874 		}
3875 		if (!word[3]) {
3876 			siril_log_message(_("Please specify the value of quantization.\n"));
3877 			g_free(comp);
3878 			return 1;
3879 		}
3880 		q = g_ascii_strtod(word[3], NULL);
3881 		if (q == 0.0 && (method == RICE_COMP || (method == HCOMPRESS_COMP))) {
3882 			siril_log_message(_("Quantization can only be equal to 0 for GZIP1 and GZIP2 algorithms.\n"));
3883 			return 1;
3884 		}
3885 		siril_log_message(_("Compression enabled with the %s algorithm and a quantization value of %.2lf\n"), comp, q);
3886 		g_free(comp);
3887 	} else {
3888 		siril_log_message(_("No compression enabled.\n"));
3889 	}
3890 	com.pref.comp.fits_enabled = compress;
3891 	com.pref.comp.fits_method = method;
3892 	com.pref.comp.fits_quantization = q;
3893 	com.pref.comp.fits_hcompress_scale = hscale;
3894 	if (!com.headless)
3895 		set_GUI_compression();
3896 	writeinitfile();
3897 	return 0;
3898 }
3899 
3900 #ifdef _OPENMP
process_set_cpu(int nb)3901 int process_set_cpu(int nb){
3902 	int proc_in, proc_out, proc_max;
3903 
3904 	proc_in = g_ascii_strtoull(word[1], NULL, 10);
3905 	proc_max = omp_get_num_procs();
3906 	if (proc_in > proc_max || proc_in < 1) {
3907 		siril_log_message(_("Number of logical processors MUST be greater "
3908 				"than 0 and lower or equal to %d.\n"), proc_max);
3909 		return 1;
3910 	}
3911 	omp_set_num_threads(proc_in);
3912 
3913 #pragma omp parallel
3914 	{
3915 		proc_out = omp_get_num_threads();
3916 	}
3917 
3918 	gchar *str = ngettext("Using now %d logical processor\n", "Using now %d logical processors\n", proc_out);
3919 	str = g_strdup_printf(str, proc_out);
3920 	siril_log_message(str);
3921 	g_free(str);
3922 
3923 	com.max_thread = proc_out;
3924 	if (!com.headless)
3925 		update_spinCPU(0);
3926 
3927 	return 0;
3928 }
3929 #endif
3930 
process_set_mem(int nb)3931 int process_set_mem(int nb){
3932 	double ratio = g_ascii_strtod(word[1], NULL);
3933 	if (ratio < 0.05 || ratio > 4.0) {
3934 		siril_log_message(_("The accepted range for the ratio of memory used for stacking is [0.05, 4], with values below the available memory recommended\n"));
3935 		return 1;
3936 	}
3937 	if (ratio > 1.0) {
3938 		siril_log_message(_("Setting the ratio of memory used for stacking above 1 will require the use of on-disk memory, which can be very slow and is unrecommended (%g requested)\n"), ratio);
3939 	}
3940 	com.pref.stack.memory_ratio = ratio;
3941 	writeinitfile();
3942 	siril_log_message(_("Usable memory for stacking changed to %g\n"), ratio);
3943 	if (!com.headless)
3944 		set_GUI_misc();
3945 	return 0;
3946 }
3947 
process_help(int nb)3948 int process_help(int nb){
3949 	control_window_switch_to_tab(OUTPUT_LOGS);
3950 
3951 	command *current = commands;
3952 	siril_log_message(_("********* LIST OF AVAILABLE COMMANDS *********\n"));
3953 	while (current->process) {
3954 		siril_log_message("%s\n", current->usage);
3955 		current++;
3956 	}
3957 	siril_log_message(_("********* END OF THE LIST *********\n"));
3958 	return 0;
3959 }
3960 
process_exit(int nb)3961 int process_exit(int nb){
3962 	gtk_main_quit();
3963 	return 0;
3964 }
3965 
process_extract(int nb)3966 int process_extract(int nb) {
3967 	int Nbr_Plan, maxplan, mins;
3968 
3969 	if (!single_image_is_loaded()) return 1;
3970 
3971 	Nbr_Plan = g_ascii_strtoull(word[1], NULL, 10);
3972 
3973 	mins = min (gfit.rx, gfit.ry);
3974 	maxplan = log(mins) / log(2) - 2;
3975 
3976 	if ( Nbr_Plan > maxplan ){
3977 		siril_log_message(_("Wavelet: maximum number of plans for this image size is %d\n"),
3978 				maxplan);
3979 		return 1;
3980 	}
3981 
3982 	struct wavelets_filter_data *args = malloc(sizeof(struct wavelets_filter_data));
3983 
3984 	args->Type = TO_PAVE_BSPLINE;
3985 	args->Nbr_Plan = Nbr_Plan;
3986 	args->fit = &gfit;
3987 	start_in_new_thread(extract_plans, args);
3988 
3989 	return 0;
3990 }
3991 
process_reloadscripts(int nb)3992 int process_reloadscripts(int nb){
3993 	return refresh_scripts(FALSE, NULL);
3994 }
3995 
3996 
process_requires(int nb)3997 int process_requires(int nb) {
3998 	gchar **version, **required;
3999 	gint major, minor, micro;
4000 	gint req_major, req_minor, req_micro;
4001 
4002 	version = g_strsplit(PACKAGE_VERSION, ".", 3);
4003 	required = g_strsplit(word[1], ".", 3);
4004 
4005 	if (g_strv_length(required) != 3) {
4006 		siril_log_color_message(_("Required version is not correct.\n"), "red");
4007 
4008 		g_strfreev(version);
4009 		g_strfreev(required);
4010 		return 1;
4011 	}
4012 
4013 	major = g_ascii_strtoull(version[0], NULL, 10);
4014 	minor = g_ascii_strtoull(version[1], NULL, 10);
4015 	micro = g_ascii_strtoull(version[2], NULL, 10);
4016 
4017 	req_major = g_ascii_strtoull(required[0], NULL, 10);
4018 	req_minor = g_ascii_strtoull(required[1], NULL, 10);
4019 	req_micro = g_ascii_strtoull(required[2], NULL, 10);
4020 
4021 	g_strfreev(version);
4022 	g_strfreev(required);
4023 
4024 	if ((major > req_major || (major == req_major && minor > req_minor)
4025 			|| (major == req_major && minor == req_minor && micro >= req_micro))) {
4026 		// no need to output something in script conditions
4027 		if (!com.script) {
4028 			siril_log_message(_("The required version of Siril is ok.\n"));
4029 		}
4030 		return 0;
4031 	} else {
4032 		if (!com.script) {
4033 			siril_log_color_message(_("A newer version of Siril is required, please update your version.\n"), "red");
4034 		} else {
4035 			siril_log_color_message(_("The script you are executing requires a newer version of Siril to run (%s), aborting.\n"), "red", word[1]);
4036 		}
4037 		return 1;
4038 	}
4039 }
4040