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 <string.h>
22 #include <math.h>
23 #include <gsl/gsl_statistics_ushort.h>
24 #include <gsl/gsl_cdf.h>
25 
26 #include "core/siril.h"
27 #include "core/proto.h"
28 #include "core/OS_utils.h"
29 #include "core/siril_date.h"
30 #include "io/sequence.h"
31 #include "io/ser.h"
32 #include "io/image_format_fits.h"
33 #include "gui/progress_and_log.h"
34 #include "algos/sorting.h"
35 #include "algos/statistics.h"
36 #include "stacking/stacking.h"
37 #include "stacking/siril_fit_linear.h"
38 
39 typedef struct {
40 	GDateTime *date_obs;
41 	gdouble exposure;
42 } DateEvent;
43 
free_list_date(gpointer data)44 static void free_list_date(gpointer data) {
45 	DateEvent *item = data;
46 
47 	g_date_time_unref(item->date_obs);
48 	g_slice_free(DateEvent, item);
49 }
50 
new_item(GDateTime * dt,gdouble exposure)51 static DateEvent* new_item(GDateTime *dt, gdouble exposure) {
52 	DateEvent *item;
53 
54 	item = g_slice_new(DateEvent);
55 	item->exposure = exposure;
56 	item->date_obs = dt;
57 
58 	return item;
59 }
60 
list_date_compare(gconstpointer * a,gconstpointer * b)61 static gint list_date_compare(gconstpointer *a, gconstpointer *b) {
62 	const DateEvent *dt1 = (const DateEvent *) a;
63 	const DateEvent *dt2 = (const DateEvent *) b;
64 
65 	return g_date_time_compare(dt1->date_obs, dt2->date_obs);
66 }
67 
68 static int stack_mean_or_median(struct stacking_args *args, gboolean is_mean);
69 
70 /*************************** MEDIAN AND MEAN STACKING **************************
71  * Median and mean stacking requires all images to be in memory, so we don't
72  * use the generic readfits() but directly the cfitsio routines to open them
73  * and seq_opened_read_region() to read data randomly from them.
74  * Since all data of all images cannot fit in memory, a divide and conqueer
75  * strategy is used, where each thread reads and processes only a part of the
76  * image, which size is computed depending on the available memory, image size
77  * and thread number.
78  *
79  * Median stacking does not use registration data, as it's generally used for
80  * preprocessing master file creation. Mean stacking however does.
81  *
82  * The difference between median and mean stacking is that once we have pixel
83  * data for all images, in the first case the result is the median of all
84  * values, in the other some values can be rejected and the average of the
85  * remaining ones is used.
86  * ****************************************************************************/
87 
stack_open_all_files(struct stacking_args * args,int * bitpix,int * naxis,long * naxes,GList ** list_date,fits * fit)88 int stack_open_all_files(struct stacking_args *args, int *bitpix, int *naxis, long *naxes, GList **list_date, fits *fit) {
89 	char msg[256], filename[256];
90 	int status, oldbitpix = 0, oldnaxis = -1, nb_frames = args->nb_images_to_stack;
91 	long oldnaxes[3] = { 0 };
92 
93 	if (args->seq->type == SEQ_REGULAR) {
94 		for (int i = 0; i < nb_frames; ++i) {
95 			int image_index = args->image_indices[i];	// image index in sequence
96 			if (!get_thread_run()) {
97 				return ST_GENERIC_ERROR;
98 			}
99 			if (!fit_sequence_get_image_filename(args->seq, image_index, filename, TRUE))
100 				continue;
101 
102 			snprintf(msg, 255, _("Opening image %s for stacking"), filename);
103 			msg[255] = '\0';
104 			set_progress_bar_data(msg, PROGRESS_NONE);
105 
106 			/* open input images */
107 			if (seq_open_image(args->seq, image_index)) {
108 				return ST_SEQUENCE_ERROR;
109 			}
110 
111 			/* here we use the internal data of sequences, it's quite ugly, we should
112 			 * consider moving these tests in seq_open_image() or wrapping them in a
113 			 * sequence function */
114 			status = 0;
115 			fits_get_img_param(args->seq->fptr[image_index], 3, bitpix, naxis, naxes, &status);
116 			if (status) {
117 				fits_report_error(stderr, status); /* print error message */
118 				return ST_SEQUENCE_ERROR;
119 			}
120 			if (*naxis > 3) {
121 				siril_log_message(_("Stacking error: images with > 3 dimensions "
122 						"are not supported\n"));
123 				return ST_SEQUENCE_ERROR;
124 			}
125 
126 			if (oldnaxis > 0) {
127 				if (*naxis != oldnaxis ||
128 						oldnaxes[0] != naxes[0] ||
129 						oldnaxes[1] != naxes[1] ||
130 						oldnaxes[2] != naxes[2]) {
131 					siril_log_message(_("Stacking error: input images have "
132 							"different sizes\n"));
133 					return ST_SEQUENCE_ERROR;
134 				}
135 			} else {
136 				oldnaxis = *naxis;
137 				oldnaxes[0] = naxes[0];
138 				oldnaxes[1] = naxes[1];
139 				oldnaxes[2] = naxes[2];
140 			}
141 
142 			if (oldbitpix > 0) {
143 				if (*bitpix != oldbitpix) {
144 					siril_log_message(_("Stacking error: input images have "
145 							"different precision\n"));
146 					return ST_SEQUENCE_ERROR;
147 				}
148 			} else {
149 				oldbitpix = *bitpix;
150 			}
151 
152 			gdouble current_exp;
153 			GDateTime *dt = NULL;
154 
155 			get_date_data_from_fitsfile(args->seq->fptr[image_index], &dt, &current_exp);
156 			if (dt)
157 				*list_date = g_list_prepend(*list_date, new_item(dt, current_exp));
158 
159 			/* We copy metadata from reference to the final fit */
160 			if (image_index == args->ref_image)
161 				import_metadata_from_fitsfile(args->seq->fptr[image_index], fit);
162 		}
163 
164 		if (naxes[2] == 0)
165 			naxes[2] = 1;
166 		g_assert(naxes[2] <= 3);
167 	}
168 	else if (args->seq->type == SEQ_SER) {
169 		g_assert(args->seq->ser_file);
170 		naxes[0] = args->seq->ser_file->image_width;
171 		naxes[1] = args->seq->ser_file->image_height;
172 		ser_color type_ser = args->seq->ser_file->color_id;
173 		*bitpix = (args->seq->ser_file->byte_pixel_depth == SER_PIXEL_DEPTH_8) ? BYTE_IMG : USHORT_IMG;
174 		if (!com.pref.debayer.open_debayer && type_ser != SER_RGB && type_ser != SER_BGR)
175 			type_ser = SER_MONO;
176 		naxes[2] = type_ser == SER_MONO ? 1 : 3;
177 		*naxis = type_ser == SER_MONO ? 2 : 3;
178 		/* case of Super Pixel not handled yet */
179 		if (com.pref.debayer.open_debayer && com.pref.debayer.bayer_inter == BAYER_SUPER_PIXEL) {
180 			siril_log_message(_("Super-pixel is not handled yet for on the fly SER stacking\n"));
181 			return ST_GENERIC_ERROR;
182 		}
183 
184 		import_metadata_from_serfile(args->seq->ser_file, fit);
185 		for (int frame = 0; frame < args->seq->number; frame++) {
186 			GDateTime *dt = ser_read_frame_date(args->seq->ser_file, frame);
187 			if (dt)
188 				*list_date = g_list_prepend(*list_date,	new_item(dt, 0.0));
189 		}
190 	}
191 	else if (args->seq->type == SEQ_FITSEQ) {
192 		g_assert(args->seq->fitseq_file);
193 		memcpy(naxes, args->seq->fitseq_file->naxes, sizeof args->seq->fitseq_file->naxes);
194 		*naxis = naxes[2] == 3 ? 3 : 2;
195 		*bitpix = args->seq->fitseq_file->bitpix;
196 
197 		for (int frame = 0; frame < args->seq->number; frame++) {
198 			if (fitseq_set_current_frame(args->seq->fitseq_file, frame)) {
199 				siril_log_color_message(_("There was an error opening frame %d for stacking\n"), "red", frame);
200 				return ST_SEQUENCE_ERROR;
201 			}
202 			gdouble current_exp;
203 			GDateTime *dt = NULL;
204 
205 			get_date_data_from_fitsfile(args->seq->fitseq_file->fptr, &dt, &current_exp);
206 			if (dt)
207 				*list_date = g_list_prepend(*list_date, new_item(dt, current_exp));
208 
209 			/* We copy metadata from reference to the final fit */
210 			if (frame == args->ref_image)
211 				import_metadata_from_fitsfile(args->seq->fitseq_file->fptr, fit);
212 		}
213 	} else {
214 		siril_log_message(_("Rejection stacking is only supported for FITS images/sequences and SER sequences.\nUse \"Sum Stacking\" instead.\n"));
215 		return ST_SEQUENCE_ERROR;
216 	}
217 
218 	return ST_OK;
219 }
220 
221 /* The number of blocks must be divisible by the number of channels or they won't be
222  * nearly the same size. It must also be divisible by the number of threads, or possibly
223  * be close to being when there are many. This favors memory over threads, but since they
224  * all start reading at the same time their execution will likely shift, so it may be
225  * better to use all available memory.
226  */
refine_blocks_candidate(int nb_threads,int nb_channels,int minimum_blocks)227 static int refine_blocks_candidate(int nb_threads, int nb_channels, int minimum_blocks) {
228 	// we assume that minimum_blocks, the candidate, is at least equal to the number
229 	// of threads
230 	int factor_of = nb_channels;
231 	if (nb_threads < 4) {
232 		// only allow factors of nb_threads
233 		if (factor_of != 1 && nb_threads % factor_of == 0)
234 			factor_of = nb_threads;
235 		else factor_of *= nb_threads;
236 		return round_to_ceiling_multiple(minimum_blocks, factor_of);
237 	}
238 	// allow 1 minus the factor for 4 - 7 threads
239 	// allow 3 minus the factor for 8 and more threads
240 	int minus_factor_allowed = nb_threads < 8 ? 1 : 3;
241 	int candidate = round_to_ceiling_multiple(minimum_blocks, factor_of);
242 	do {
243 		int rem = candidate % nb_threads;
244 		if (rem == 0 || rem >= (nb_threads - minus_factor_allowed))
245 			return candidate;
246 		candidate += factor_of;
247 	} while (1);
248 	return candidate;
249 }
250 
251 /* median or mean stacking require that the value of each pixel from all images
252  * is available. We cannot load all images in memory and it's too slow to read
253  * one pixel at a time in all images, so we prepare blocks.
254  * Blocks are a part of a channel that will be read from all images, and the
255  * stacking will be done on each pixel of the block before going to the next.
256  * Parallelization of work is done by assigning some blocks to a thread, which
257  * it will process sequentially.
258  * To improve load distribution, blocks should be small enough to allow all
259  * threads to work but as big as possible for the available memory.
260  */
stack_compute_parallel_blocks(struct _image_block ** blocksptr,long max_number_of_rows,long naxes[3],int nb_threads,long * largest_block_height,int * nb_blocks)261 int stack_compute_parallel_blocks(struct _image_block **blocksptr, long max_number_of_rows,
262 		long naxes[3], int nb_threads, long *largest_block_height, int *nb_blocks) {
263 	int candidate = nb_threads;	// candidate number of blocks
264 	while ((max_number_of_rows * candidate) / nb_threads < naxes[1] * naxes[2])
265 		candidate++;
266 	candidate = refine_blocks_candidate(nb_threads, (naxes[2] == 3L) ? 3 : 1, candidate);
267 
268 	*nb_blocks = candidate;
269 	long height_of_blocks = naxes[1] * naxes[2] / candidate;
270 	int remainder = naxes[1] % (candidate / naxes[2]);
271 	siril_log_message(_("We have %d parallel blocks of size %d (+%d) for stacking.\n"),
272 			*nb_blocks, height_of_blocks, remainder);
273 
274 	*largest_block_height = 0;
275 	long channel = 0, row = 0, end, j = 0;
276 	*blocksptr = malloc(*nb_blocks * sizeof(struct _image_block));
277 	if (!*blocksptr) {
278 		PRINT_ALLOC_ERR;
279 		return ST_ALLOC_ERROR;
280 	}
281 	struct _image_block *blocks = *blocksptr;
282 	do {
283 		if (j >= *nb_blocks) {
284 			siril_log_message(_("A bug has been found. Unable to split the image "
285 						"area into the correct processing blocks.\n"));
286 			return ST_GENERIC_ERROR;
287 		}
288 
289 		blocks[j].channel = channel;
290 		blocks[j].start_row = row;
291 		end = row + height_of_blocks - 1;
292 		if (remainder > 0) {
293 			// just add one pixel from the remainder to the first blocks to
294 			// avoid having all of them in the last block
295 			end++;
296 			remainder--;
297 		}
298 		if (end >= naxes[1] - 1 ||	// end of the line
299 				(naxes[1] - end < height_of_blocks / 10)) { // not far from it
300 			end = naxes[1] - 1;
301 			row = 0;
302 			channel++;
303 			remainder = naxes[1] - (*nb_blocks / naxes[2] * height_of_blocks);
304 		} else {
305 			row = end + 1;
306 		}
307 		blocks[j].end_row = end;
308 		blocks[j].height = blocks[j].end_row - blocks[j].start_row + 1;
309 		if (*largest_block_height < blocks[j].height) {
310 			*largest_block_height = blocks[j].height;
311 		}
312 		fprintf(stdout, "Block %ld: channel %lu, from %lu to %lu (h = %lu)\n",
313 				j, blocks[j].channel, blocks[j].start_row,
314 				blocks[j].end_row, blocks[j].height);
315 		j++;
316 
317 	} while (channel < naxes[2]) ;
318 
319 	return ST_OK;
320 }
321 
stack_read_block_data(struct stacking_args * args,int use_regdata,struct _image_block * my_block,struct _data_block * data,long * naxes,data_type itype,int thread_id)322 static void stack_read_block_data(struct stacking_args *args, int use_regdata,
323 		struct _image_block *my_block, struct _data_block *data,
324 		long *naxes, data_type itype, int thread_id) {
325 
326 	int ielem_size = itype == DATA_FLOAT ? sizeof(float) : sizeof(WORD);
327 	/* store the layer info to retrieve normalization coeffs*/
328 	data->layer = (int)my_block->channel;
329 	/* Read the block from all images, store them in pix[image] */
330 	for (int frame = 0; frame < args->nb_images_to_stack; ++frame) {
331 		gboolean clear = FALSE, readdata = TRUE;
332 		long offset = 0;
333 		/* area in C coordinates, starting with 0, not cfitsio coordinates. */
334 		rectangle area = {0, my_block->start_row, naxes[0], my_block->height};
335 
336 		if (!get_thread_run()) {
337 			return;
338 		}
339 		if (use_regdata && args->reglayer >= 0) {
340 			/* Load registration data for current image and modify area.
341 			 * Here, only the y shift is managed. If possible, the remaining part
342 			 * of the original area is read, the rest is filled with zeros. The x
343 			 * shift is managed in the main loop after the read. */
344 			regdata *layerparam = args->seq->regparam[args->reglayer];
345 			if (layerparam) {
346 				int shifty = round_to_int(
347 						layerparam[args->image_indices[frame]].shifty *
348 						args->seq->upscale_at_stacking);
349 #ifdef STACK_DEBUG
350 				fprintf(stdout, "shifty for image %d: %d\n", args->image_indices[frame], shifty);
351 #endif
352 				if (area.y + area.h - 1 + shifty < 0 || area.y + shifty >= naxes[1]) {
353 					// entirely outside image below or above: all black pixels
354 					clear = TRUE; readdata = FALSE;
355 				} else if (area.y + shifty < 0) {
356 					/* we read only the bottom part of the area here, which
357 					 * requires an offset in pix */
358 					clear = TRUE;
359 					area.h += area.y + shifty;	// cropping the height
360 					offset = naxes[0] * (area.y - shifty);	// positive
361 					area.y = 0;
362 				} else if (area.y + area.h - 1 + shifty >= naxes[1]) {
363 					/* we read only the upper part of the area here */
364 					clear = TRUE;
365 					area.y += shifty;
366 					area.h += naxes[1] - (area.y + area.h);
367 				} else {
368 					area.y += shifty;
369 				}
370 			}
371 #ifdef STACK_DEBUG
372 			else fprintf(stderr, "NO REGPARAM\n");
373 #endif
374 
375 			if (clear) {
376 				/* we are reading outside an image, fill with
377 				 * zeros and attempt to read lines that fit */
378 				memset(data->pix[frame], 0, my_block->height * naxes[0] * ielem_size);
379 			}
380 		}
381 
382 		if (!use_regdata || readdata) {
383 			// reading pixels from current frame
384 			void *buffer;
385 			if (itype == DATA_FLOAT)
386 				buffer = ((float*)data->pix[frame])+offset;
387 			else 	buffer = ((WORD *)data->pix[frame])+offset;
388 			int retval = seq_opened_read_region(args->seq, my_block->channel,
389 					args->image_indices[frame], buffer, &area, thread_id);
390 			if (retval) {
391 #ifdef _OPENMP
392 				int tid = omp_get_thread_num();
393 				if (tid == 0)
394 #endif
395 					siril_log_color_message(_("Error reading one of the image areas\n"), "red");
396 				break;
397 			}
398 		}
399 	}
400 }
401 
normalize_to16bit(int bitpix,double * mean)402 static void normalize_to16bit(int bitpix, double *mean) {
403 	switch(bitpix) {
404 		case BYTE_IMG:
405 			*mean *= (USHRT_MAX_DOUBLE / UCHAR_MAX_DOUBLE);
406 			break;
407 		default:
408 			; // do nothing
409 	}
410 }
411 
norm_to_0_1_range(fits * fit)412 static void norm_to_0_1_range(fits *fit) {
413 	float mini = fit->fdata[0];
414 	float maxi = fit->fdata[0];
415 	long n = fit->naxes[0] * fit->naxes[1] * fit->naxes[2];
416 
417 	/* search for min / max */
418 #ifdef _OPENMP
419 #pragma omp parallel for num_threads(com.max_thread) schedule(static) reduction(max:maxi) reduction(min:mini)
420 #endif
421 	for (int i = 1; i < n; i++) {
422 		float tmp = fit->fdata[i];
423 		if (tmp < mini)
424 			mini = tmp;
425 		if (tmp > maxi)
426 			maxi = tmp;
427 	}
428 	/* normalize to [0, 1] range */
429 #ifdef _OPENMP
430 #pragma omp parallel for num_threads(com.max_thread) schedule(static)
431 #endif
432 	for (int i = 0; i < n; i++) {
433 		fit->fdata[i] = (fit->fdata[i] - mini) / (maxi - mini);
434 	}
435 }
436 
437 /******************************* REJECTION STACKING ******************************
438  * The functions below are those managing the rejection, the stacking code is
439  * after and similar to median but takes into account the registration data and
440  * does a different operation to keep the final pixel values.
441  *********************************************************************************/
442 
percentile_clipping(WORD pixel,float sig[],float median,guint64 rej[])443 static int percentile_clipping(WORD pixel, float sig[], float median, guint64 rej[]) {
444 	float plow = sig[0];
445 	float phigh = sig[1];
446 
447 	if ((median - (float) pixel) / median > plow) {
448 		rej[0]++;
449 		return -1;
450 	}
451 	else if (((float)pixel - median) / median > phigh) {
452 		rej[1]++;
453 		return 1;
454 	}
455 	return 0;
456 }
457 
458 /* Rejection of pixels, following sigma_(high/low) * sigma.
459  * The function returns 0 if no rejections are required, 1 if it's a high
460  * rejection and -1 for a low-rejection */
sigma_clipping(WORD pixel,float sig[],float sigma,float median,guint64 rej[])461 static int sigma_clipping(WORD pixel, float sig[], float sigma, float median, guint64 rej[]) {
462 	float sigmalow = sig[0];
463 	float sigmahigh = sig[1];
464 
465 	if (median - pixel > sigmalow * sigma) {
466 		rej[0]++;
467 		return -1;
468 	}
469 	else if (pixel - median > sigmahigh * sigma) {
470 		rej[1]++;
471 		return 1;
472 	}
473 	return 0;
474 }
475 
Winsorize(WORD * pixel,WORD m0,WORD m1,int N)476 static void Winsorize(WORD *pixel, WORD m0, WORD m1, int N) {
477 	for (int j = 0; j < N; ++j) {
478 		pixel[j] = pixel[j] < m0 ? m0 : pixel[j];
479 		pixel[j] = pixel[j] > m1 ? m1 : pixel[j];
480 	}
481 }
482 
line_clipping(WORD pixel,float sig[],float sigma,int i,float a,float b,guint64 rej[])483 static int line_clipping(WORD pixel, float sig[], float sigma, int i, float a, float b, guint64 rej[]) {
484 	float sigmalow = sig[0];
485 	float sigmahigh = sig[1];
486 
487 	if (a * i + b - pixel > sigma * sigmalow) {
488 		rej[0]++;
489 		return -1;
490 	} else if (pixel - a * i - b > sigma * sigmahigh) {
491 		rej[1]++;
492 		return 1;
493 	}
494 	return 0;
495 }
496 
remove_element(WORD * array,int index,int array_length)497 static void remove_element(WORD *array, int index, int array_length) {
498 	for (int i = index; i < array_length - 1; i++)
499 		array[i] = array[i + 1];
500 }
501 
siril_stats_ushort_sd(const WORD data[],const int N,float * m)502 static float siril_stats_ushort_sd(const WORD data[], const int N, float *m) {
503 	double accumulator = 0.0; // accumulating in double precision is important for accuracy
504 	for (int i = 0; i < N; ++i) {
505 		accumulator += data[i];
506 	}
507 	float mean = (float)(accumulator / N);
508 	accumulator = 0.0;
509 	for (int i = 0; i < N; ++i)
510 		accumulator += (data[i] - mean) * (data[i] - mean);
511 
512 	if (m) *m = mean;
513 
514 	return sqrtf((float)(accumulator / (N - 1)));
515 }
516 
grubbs_stat(WORD * stack,int N,float * GCal,int * max_ind)517 static void grubbs_stat(WORD *stack, int N, float *GCal, int *max_ind) {
518 	float avg_y;
519 	float sd = siril_stats_ushort_sd(stack, N, &avg_y);
520 
521 	/* data are sorted */
522 	float max_of_deviations = avg_y - stack[0];
523 	float md2 = stack[N - 1] - avg_y;
524 
525 	if (md2 > max_of_deviations) {
526 		max_of_deviations = md2;
527 		*max_ind = N - 1;
528 	} else {
529 		*max_ind = 0;
530 	}
531 	*GCal = max_of_deviations / sd;
532 }
533 
check_G_values(float Gs,float Gc)534 int check_G_values(float Gs, float Gc) {
535 	return (Gs > Gc);
536 }
537 
confirm_outliers(struct outliers * out,int N,double median,int * rejected,guint64 rej[2])538 void confirm_outliers(struct outliers *out, int N, double median, int *rejected,
539 		guint64 rej[2]) {
540 	int i = N - 1;
541 
542 	while (i > 1 && !out[i].out) {
543 		i--;
544 	}
545 	for (int j = i; j >= 0; j--) {
546 		out[j].out = 1;
547 		if (out[j].x >= median) {
548 			rejected[out[j].i] = 1;
549 			rej[1]++;
550 		} else {
551 			rejected[out[j].i] = -1;
552 			rej[0]++;
553 		}
554 	}
555 }
556 
apply_rejection_ushort(struct _data_block * data,int nb_frames,struct stacking_args * args,guint64 crej[2])557 static int apply_rejection_ushort(struct _data_block *data, int nb_frames, struct stacking_args *args, guint64 crej[2]) {
558 	int N = nb_frames;	// N is the number of pixels kept from the current stack
559 	float median = 0.f;
560 	int pixel, output, changed, n, r = 0;
561 	int firstloop = 1;
562 	int kept = 0, removed = 0;
563 	WORD *stack = (WORD *)data->stack;
564 	WORD *w_stack = (WORD *)data->w_stack;
565 	int *rejected = (int *)data->rejected;
566 	WORD *o_stack = (WORD *)data->o_stack;
567 
568 	memcpy(o_stack, stack, N * sizeof(WORD)); /* making a copy of unsorted stack to apply weights (before the median sorts in place)*/
569 
570 	/* remove null pixels */
571 	for (int frame = 0; frame < N; frame++) {
572 		if (stack[frame] > 0) {
573 			if (frame != kept) {
574 				stack[kept] = stack[frame];
575 			}
576 			kept++;
577 		}
578 	}
579 	/* Preventing problems
580 	   0: should not happen but just in case.
581 	   1 or 2: no need to reject */
582 	if (kept <= 2) {
583 		return kept;
584 	}
585 	removed = N - kept;
586 	N = kept;
587 
588 	/* prepare median and check that the stack is not mostly zero */
589 	switch (args->type_of_rejection) {
590 		case PERCENTILE:
591 		case SIGMA:
592 		case MAD:
593 		case SIGMEDIAN:
594 		case WINSORIZED:
595 			median = quickmedian(stack, N);
596 			if (median == 0.f)
597 				return 0;
598 			break;
599 		default:
600 			break;
601 	}
602 
603 	switch (args->type_of_rejection) {
604 		case PERCENTILE:
605 			for (int frame = 0; frame < N; frame++) {
606 				rejected[frame] = percentile_clipping(stack[frame], args->sig, median, crej);
607 			}
608 
609 			for (pixel = 0, output = 0; pixel < N; pixel++) {
610 				if (!rejected[pixel]) {
611 					// copy only if there was a rejection
612 					if (pixel != output)
613 						stack[output] = stack[pixel];
614 					output++;
615 				}
616 			}
617 			N = output;
618 			break;
619 		case SIGMA:
620 		case MAD:
621 			do {
622 				float var;
623 				if (args->type_of_rejection == SIGMA)
624 					var = args->sd_calculator(stack, N);
625 				else
626 					var = args->mad_calculator(stack, N, median, FALSE);
627 
628 				if (!firstloop) {
629 					median = quickmedian(stack, N);
630 				} else {
631 					firstloop = 0;
632 				}
633 				for (int frame = 0; frame < N; frame++) {
634 					if (N - r <= 4) {
635 					// no more rejections
636 						rejected[frame] = 0;
637 					} else {
638 						rejected[frame] = sigma_clipping(stack[frame], args->sig, var, median, crej);
639 						if (rejected[frame]) {
640 							r++;
641 						}
642 					}
643 				}
644 				for (pixel = 0, output = 0; pixel < N; pixel++) {
645 					if (!rejected[pixel]) {
646 						// copy only if there was a rejection
647 						if (pixel != output)
648 							stack[output] = stack[pixel];
649 						output++;
650 					}
651 				}
652 				changed = N != output;
653 				N = output;
654 			} while (changed && N > 3);
655 			break;
656 		case SIGMEDIAN:
657 			do {
658 				const float sigma = args->sd_calculator(stack, N);
659 				if (!firstloop) {
660 					median = quickmedian (stack, N);
661 				} else {
662 					firstloop = 0;
663 				}
664 				n = 0;
665 				for (int frame = 0; frame < N; frame++) {
666 					if (sigma_clipping(stack[frame], args->sig, sigma, median, crej)) {
667 						stack[frame] = median;
668 						n++;
669 					}
670 				}
671 			} while (n > 0);
672 			break;
673 		case WINSORIZED:
674 			do {
675 				float sigma0;
676 				float sigma = args->sd_calculator(stack, N);
677 				if (!firstloop)
678 					median = quickmedian (stack, N);
679 				else firstloop = 0;
680 				memcpy(w_stack, stack, N * sizeof(WORD));
681 				do {
682 					Winsorize(w_stack, roundf_to_WORD(median - 1.5f * sigma), roundf_to_WORD(median + 1.5f * sigma), N);
683 					sigma0 = sigma;
684 					sigma = 1.134f * args->sd_calculator(w_stack, N);
685 				} while (fabs(sigma - sigma0) > sigma0 * 0.0005f);
686 				for (int frame = 0; frame < N; frame++) {
687 					if (N - r <= 4) {
688 						// no more rejections
689 						rejected[frame] = 0;
690 					} else {
691 						rejected[frame] = sigma_clipping(stack[frame], args->sig, sigma, median, crej);
692 						if (rejected[frame] != 0)
693 							r++;
694 					}
695 
696 				}
697 				for (pixel = 0, output = 0; pixel < N; pixel++) {
698 					if (!rejected[pixel]) {
699 						// copy only if there was a rejection
700 						stack[output] = stack[pixel];
701 						output++;
702 					}
703 				}
704 				changed = N != output;
705 				N = output;
706 			} while (changed && N > 3);
707 			break;
708 		case LINEARFIT:
709 			do {
710 				quicksort_s(stack, N);
711 				for (int frame = 0; frame < N; frame++) {
712 					data->yf[frame] = (float)stack[frame];
713 				}
714 				float a, b;
715 				siril_fit_linear(data->xf, data->yf, data->m_x, data->m_dx2, N, &b, &a);
716 				float sigma = 0.f;
717 				for (int frame = 0; frame < N; frame++)
718 					sigma += fabsf(stack[frame] - (a * frame + b));
719 				sigma /= (float)N;
720 				for (int frame = 0; frame < N; frame++) {
721 					if (N - r <= 4) {
722 						// no more rejections
723 						rejected[frame] = 0;
724 					} else {
725 						rejected[frame] =
726 							line_clipping(stack[frame], args->sig, sigma, frame, a, b, crej);
727 						if (rejected[frame] != 0)
728 							r++;
729 					}
730 				}
731 				for (pixel = 0, output = 0; pixel < N; pixel++) {
732 					if (!rejected[pixel]) {
733 						// copy only if there was a rejection
734 						if (pixel != output)
735 							stack[output] = stack[pixel];
736 						output++;
737 					}
738 				}
739 				changed = N != output;
740 				N = output;
741 			} while (changed && N > 3);
742 			break;
743 		case GESDT:
744 			/* Normaly The algorithm does not need to play with sorted data.
745 			 * But our implementation (after the rejection) needs to be sorted.
746 			 * So we do it, and by the way we get the median value. Indeed, by design
747 			 * this algorithm does not have low and high representation of rejection.
748 			 * We define:
749 			 * - cold pixel: rejected < median
750 			 * - hot pixel: rejected > median
751 			 */
752 
753 			quicksort_s(stack, N);
754 			median = gsl_stats_ushort_median_from_sorted_data(stack, 1, N);
755 
756 			int max_outliers = (int) nb_frames * args->sig[0];
757 			if (removed >= max_outliers) { /* more than max allowable have been already been removed, should not reject anymore*/
758 				return kept;
759 			}
760 			max_outliers -= removed;
761 			struct outliers *out = malloc(max_outliers * sizeof(struct outliers));
762 
763 			memcpy(w_stack, stack, N * sizeof(WORD));
764 			memset(rejected, 0, N * sizeof(int));
765 
766 			for (int iter = 0, size = N; iter < max_outliers; iter++, size--) {
767 				float Gstat;
768 				int max_index = 0;
769 
770 				grubbs_stat(w_stack, size, &Gstat, &max_index);
771 				out[iter].out = check_G_values(Gstat, args->critical_value[iter + removed]);
772 				out[iter].x = w_stack[max_index];
773 				out[iter].i = max_index;
774 				remove_element(w_stack, max_index, size);
775 			}
776 			confirm_outliers(out, max_outliers, median, rejected, crej);
777 			free(out);
778 
779 			for (pixel = 0, output = 0; pixel < N; pixel++) {
780 				if (!rejected[pixel]) {
781 					// copy only if there was a rejection
782 					if (pixel != output)
783 						stack[output] = stack[pixel];
784 					output++;
785 				}
786 			}
787 			N = output;
788 		break;
789 		default:
790 		case NO_REJEC:
791 			;		// Nothing to do, no rejection
792 	}
793 	return N;
794 }
795 
mean_and_reject(struct stacking_args * args,struct _data_block * data,int stack_size,data_type itype,guint64 crej[2])796 static double mean_and_reject(struct stacking_args *args, struct _data_block *data,
797 		int stack_size, data_type itype, guint64 crej[2]) {
798 	double mean;
799 
800 	int layer = data->layer;
801 	if (itype == DATA_USHORT) {
802 		int kept_pixels = apply_rejection_ushort(data, stack_size, args, crej);
803 		if (kept_pixels == 0)
804 			mean = quickmedian(data->stack, stack_size);
805 		else {
806 			if (args->apply_weight) {
807 				double *pweights = args->weights + layer * stack_size;
808 				WORD pmin = 65535, pmax = 0; /* min and max computed here instead of rejection step to avoid dealing with too many particular cases */
809 				for (int frame = 0; frame < kept_pixels; ++frame) {
810 					if (pmin > ((WORD*)data->stack)[frame]) pmin = ((WORD*)data->stack)[frame];
811 					if (pmax < ((WORD*)data->stack)[frame]) pmax = ((WORD*)data->stack)[frame];
812 				}
813 				double sum = 0.0;
814 				double norm = 0.0;
815 				WORD val;
816 
817 				for (int frame = 0; frame < stack_size; ++frame) {
818 					val = ((WORD*)data->o_stack)[frame];
819 					if (val >= pmin && val <= pmax && val > 0) {
820 						sum += (float)val * pweights[frame];
821 						norm += pweights[frame];
822 					}
823 				}
824 				mean = sum / norm;
825 			} else {
826 				gint64 sum = 0L;
827 				for (int frame = 0; frame < kept_pixels; ++frame) {
828 					sum += ((WORD *)data->stack)[frame];
829 				}
830 				mean = sum / (double)kept_pixels;
831 			}
832 		}
833 	} else {
834 		int kept_pixels = apply_rejection_float(data, stack_size, args, crej);
835 		if (kept_pixels == 0)
836 			mean = quickmedian_float(data->stack, stack_size);
837 		else {
838 			if (args->apply_weight) {
839 				double *pweights = args->weights + layer * stack_size;
840 				float pmin = 10000.0, pmax = -10000.0; /* min and max computed here instead of rejection step to avoid dealing with too many particular cases */
841 				for (int frame = 0; frame < kept_pixels; ++frame) {
842 					if (pmin > ((float*)data->stack)[frame]) pmin = ((float*)data->stack)[frame];
843 					if (pmax < ((float*)data->stack)[frame]) pmax = ((float*)data->stack)[frame];
844 				}
845 				double sum = 0.0;
846 				double norm = 0.0;
847 				float val;
848 				for (int frame = 0; frame < stack_size; ++frame) {
849 					val = ((float*)data->o_stack)[frame];
850 					if (val >= pmin && val <= pmax && val != 0.f) {
851 						sum += val * pweights[frame];
852 						norm += pweights[frame];
853 					}
854 				}
855 			mean = sum / norm;
856 			} else {
857 				double sum = 0.0;
858 				for (int frame = 0; frame < kept_pixels; ++frame) {
859 					sum += ((float*)data->stack)[frame];
860 				}
861 				mean = sum / (double)kept_pixels;
862 			}
863 		}
864 	}
865 	return mean;
866 }
867 
stack_mean_with_rejection(struct stacking_args * args)868 int stack_mean_with_rejection(struct stacking_args *args) {
869 	return stack_mean_or_median(args, TRUE);
870 }
871 
stack_median(struct stacking_args * args)872 int stack_median(struct stacking_args *args) {
873 	return stack_mean_or_median(args, FALSE);
874 }
875 
compute_weights(struct stacking_args * args)876 static int compute_weights(struct stacking_args *args) {
877 	int nb_frames = args->nb_images_to_stack;
878 	int nb_layers = args->seq->nb_layers;
879 	int idx = 0;
880 
881 	args->weights = malloc(nb_layers * nb_frames * sizeof(double));
882 	double *pweights[3];
883 
884 	for (int layer = 0; layer < nb_layers; ++layer) {
885 		double norm = 0.0;
886 		pweights[layer] = args->weights + layer * nb_frames;
887 		for (int i = 0; i < args->nb_images_to_stack; ++i) {
888 			idx = args->image_indices[i];
889 			pweights[layer][i] = 1.f /
890 				(args->coeff.pscale[layer][i] * args->coeff.pscale[layer][i] *
891 				 args->seq->stats[layer][idx]->bgnoise * args->seq->stats[layer][idx]->bgnoise);
892 			norm += pweights[layer][i];
893 		}
894 		norm /= (double) nb_frames;
895 
896 		for (int i = 0; i < args->nb_images_to_stack; i++) {
897 			pweights[layer][i] /= norm;
898 		}
899 	}
900 	return ST_OK;
901 }
902 
compute_date_time_keywords(GList * list_date,fits * fit)903 static void compute_date_time_keywords(GList *list_date, fits *fit) {
904 	if (list_date) {
905 		gdouble exposure = 0.0;
906 		GDateTime *date_obs;
907 		gdouble start, end;
908 		GList *list;
909 		/* First we want to sort the list */
910 		list_date = g_list_sort(list_date, (GCompareFunc) list_date_compare);
911 		/* Then we compute the sum of exposure */
912 		for (list = list_date; list; list = list->next) {
913 			exposure += ((DateEvent *)list->data)->exposure;
914 		}
915 
916 		/* go to the first stacked image and get needed values */
917 		list_date = g_list_first(list_date);
918 		date_obs = g_date_time_ref(((DateEvent *)list_date->data)->date_obs);
919 		start = date_time_to_Julian(((DateEvent *)list_date->data)->date_obs);
920 
921 		/* go to the last stacked image and get needed values
922 		 * This time we need to add the exposure to the date_obs
923 		 * to exactly retrieve the end of the exposure
924 		 */
925 		list_date = g_list_last(list_date);
926 		gdouble last_exp = ((DateEvent *)list_date->data)->exposure;
927 		GDateTime *last_date = ((DateEvent *)list_date->data)->date_obs;
928 		GDateTime *corrected_last_date = g_date_time_add_seconds(last_date, (gdouble) last_exp);
929 
930 		end = date_time_to_Julian(corrected_last_date);
931 
932 		g_date_time_unref(corrected_last_date);
933 
934 		/* we address the computed values to the keywords */
935 		fit->exposure = exposure;
936 		fit->date_obs = date_obs;
937 		fit->expstart = start;
938 		fit->expend = end;
939 	}
940 }
941 
942 /* How many rows fit in memory, based on image size, number and available memory.
943  * It returns at most the total number of rows of the image (naxes[1] * naxes[2]) */
stack_get_max_number_of_rows(long naxes[3],data_type type,int nb_images_to_stack)944 static long stack_get_max_number_of_rows(long naxes[3], data_type type, int nb_images_to_stack) {
945 	int max_memory = get_max_memory_in_MB();
946 	long total_nb_rows = naxes[1] * naxes[2];
947 
948 	siril_log_message(_("Using %d MB memory maximum for stacking\n"), max_memory);
949 	int elem_size = type == DATA_FLOAT ? sizeof(float) : sizeof(WORD);
950 	guint64 number_of_rows = (guint64)max_memory * BYTES_IN_A_MB /
951 		((guint64)naxes[0] * nb_images_to_stack * elem_size);
952 	// this is how many rows we can load in parallel from all images of the
953 	// sequence and be under the limit defined in config in megabytes.
954 	if (total_nb_rows < number_of_rows)
955 		return total_nb_rows;
956 	return (long)number_of_rows;
957 }
958 
stack_mean_or_median(struct stacking_args * args,gboolean is_mean)959 static int stack_mean_or_median(struct stacking_args *args, gboolean is_mean) {
960 	int bitpix, i, naxis, cur_nb = 0, retval = ST_OK, pool_size = 1;
961 	long naxes[3];
962 	struct _data_block *data_pool = NULL;
963 	struct _image_block *blocks = NULL;
964 	fits fit = { 0 }; // output result
965 	fits ref = { 0 }; // reference image, used to get metadata back
966 	// data for mean/rej only
967 	guint64 irej[3][2] = {{0,0}, {0,0}, {0,0}};
968 	regdata *layerparam = NULL;
969 	gboolean use_regdata = is_mean;
970 
971 
972 	int nb_frames = args->nb_images_to_stack; // number of frames actually used
973 	naxes[0] = naxes[1] = 0; naxes[2] = 1;
974 
975 	if (nb_frames < 2) {
976 		siril_log_message(_("Select at least two frames for stacking. Aborting.\n"));
977 		return ST_GENERIC_ERROR;
978 	} else if (nb_frames < 3 && is_mean && args->type_of_rejection == GESDT) {
979 		siril_log_message(_("The Generalized Extreme Studentized Deviate Test needs at least three frames for stacking. Aborting.\n"));
980 		return ST_GENERIC_ERROR;
981 	}
982 	g_assert(nb_frames <= args->seq->number);
983 
984 	if (use_regdata) {
985 		if (args->reglayer < 0) {
986 			siril_log_message(_("No registration layer passed, ignoring registration data!\n"));
987 			use_regdata = FALSE;
988 		}
989 		else layerparam = args->seq->regparam[args->reglayer];
990 	}
991 
992 	set_progress_bar_data(NULL, PROGRESS_RESET);
993 
994 	/* first loop: open all fits files and check they are of same size */
995 	GList *list_date = NULL;
996 	if ((retval = stack_open_all_files(args, &bitpix, &naxis, naxes, &list_date, &ref))) {
997 		goto free_and_close;
998 	}
999 
1000 	if (naxes[0] == 0) {
1001 		// no image has been loaded
1002 		siril_log_color_message(_("Rejection stack error: uninitialized sequence\n"), "red");
1003 		retval = ST_SEQUENCE_ERROR;
1004 		goto free_and_close;
1005 	}
1006 	if (naxes[0] != args->seq->rx || naxes[1] != args->seq->ry) {
1007 		siril_log_color_message(_("Rejection stack error: sequence has wrong image size (%dx%d for sequence, %ldx%ld for images)\n"), "red", args->seq->rx, args->seq->ry, naxes[0], naxes[1]);
1008 		retval = ST_SEQUENCE_ERROR;
1009 		goto free_and_close;
1010 	}
1011 	if (sequence_is_rgb(args->seq) && naxes[2] != 3) {
1012 		siril_log_message(_("Processing the sequence as RGB\n"));
1013 		naxes[2] = 3;
1014 	}
1015 	fprintf(stdout, "image size: %ldx%ld, %ld layers\n", naxes[0], naxes[1], naxes[2]);
1016 
1017 	/* initialize result image */
1018 	fits *fptr = &fit;
1019 	if ((retval = new_fit_image(&fptr, naxes[0], naxes[1], naxes[2],
1020 					args->use_32bit_output ? DATA_FLOAT : DATA_USHORT))) {
1021 		goto free_and_close;
1022 	}
1023 	copy_fits_metadata(&ref, fptr);
1024 	clearfits(&ref);
1025 	if (!args->use_32bit_output && (args->output_norm || fit.orig_bitpix != BYTE_IMG)) {
1026 		fit.bitpix = USHORT_IMG;
1027 		if (args->output_norm)
1028 			fit.orig_bitpix = USHORT_IMG;
1029 	}
1030 
1031 	/* manage threads */
1032 	int nb_threads;
1033 #ifdef _OPENMP
1034 	nb_threads = com.max_thread;
1035 	if (nb_threads > 1 && (args->seq->type == SEQ_REGULAR || args->seq->type == SEQ_FITSEQ)) {
1036 		if (fits_is_reentrant()) {
1037 			fprintf(stdout, "cfitsio was compiled with multi-thread support,"
1038 					" stacking will be executed by several cores\n");
1039 		} else {
1040 			nb_threads = 1;
1041 			fprintf(stdout, "cfitsio was compiled without multi-thread support,"
1042 					" stacking will be executed on only one core\n");
1043 			siril_log_message(_("Your version of cfitsio does not support multi-threading\n"));
1044 		}
1045 	}
1046 #ifdef HAVE_FFMS2
1047 	if (args->seq->type == SEQ_AVI) {
1048 		siril_log_color_message(_("Stacking a film will work only on one core and will be slower than if you convert it to SER\n"), "salmon");
1049 		nb_threads = 1;
1050 	}
1051 #endif // HAVE_FFMS2
1052 #else
1053 	nb_threads = 1;
1054 #endif
1055 
1056 	/* manage memory */
1057 	long largest_block_height;
1058 	int nb_blocks;
1059 	data_type itype = get_data_type(bitpix);
1060 	long max_number_of_rows = stack_get_max_number_of_rows(naxes, itype, args->nb_images_to_stack);
1061 	/* Compute parallel processing data: the data blocks, later distributed to threads */
1062 	if ((retval = stack_compute_parallel_blocks(&blocks, max_number_of_rows, naxes, nb_threads,
1063 					&largest_block_height, &nb_blocks))) {
1064 		goto free_and_close;
1065 	}
1066 
1067 	/* Allocate the buffers.
1068 	 * We allocate as many as the number of threads, each thread will pick one of the buffers.
1069 	 * Buffers are allocated to the largest block size calculated above.
1070 	 */
1071 #ifdef _OPENMP
1072 	pool_size = nb_threads;
1073 	g_assert(pool_size > 0);
1074 #endif
1075 	size_t npixels_in_block = largest_block_height * naxes[0];
1076 	g_assert(npixels_in_block > 0);
1077 	int ielem_size = itype == DATA_FLOAT ? sizeof(float) : sizeof(WORD);
1078 
1079 	fprintf(stdout, "allocating data for %d threads (each %'lu MB)\n", pool_size,
1080 			(unsigned long)(nb_frames * npixels_in_block * ielem_size) / BYTES_IN_A_MB);
1081 	data_pool = calloc(pool_size, sizeof(struct _data_block));
1082 	size_t bufferSize = ielem_size * nb_frames * (npixels_in_block + 1ul) + 4ul; // buffer for tmp and stack, added 4 byte for alignment
1083 	if (is_mean) {
1084 		bufferSize += nb_frames * sizeof(int); // for rejected
1085 		bufferSize += ielem_size * nb_frames; // for o_stack
1086 		if (args->type_of_rejection == WINSORIZED) {
1087 			bufferSize += ielem_size * nb_frames; // for w_frame
1088 		} else if (args->type_of_rejection == GESDT) {
1089 			bufferSize += ielem_size * nb_frames; // for w_frame
1090 			bufferSize += sizeof(float) * (int) floor(nb_frames * args->sig[0]); //and GCritical
1091 		} else if (args->type_of_rejection == LINEARFIT) {
1092 			bufferSize += 2 * sizeof(float) * nb_frames; // for xc and yc
1093 		}
1094 	}
1095 	for (i = 0; i < pool_size; i++) {
1096 		data_pool[i].pix = malloc(nb_frames * sizeof(void *));
1097 		data_pool[i].tmp = malloc(bufferSize);
1098 		if (!data_pool[i].pix || !data_pool[i].tmp) {
1099 			PRINT_ALLOC_ERR;
1100 			gchar *available = g_format_size_full(get_available_memory(), G_FORMAT_SIZE_IEC_UNITS);
1101 			fprintf(stderr, "Cannot allocate %zu (free memory: %s)\n", bufferSize / BYTES_IN_A_MB, available);
1102 			fprintf(stderr, "CHANGE MEMORY SETTINGS if stacking takes too much.\n");
1103 
1104 			g_free(available);
1105 			retval = ST_ALLOC_ERROR;
1106 			goto free_and_close;
1107 		}
1108 		data_pool[i].stack = (void*) ((char*) data_pool[i].tmp
1109 				+ nb_frames * npixels_in_block * ielem_size);
1110 		if (is_mean) {
1111 			size_t offset = (size_t)ielem_size * nb_frames * (npixels_in_block + 1);
1112 			int temp = offset % sizeof(int);
1113 			if (temp > 0) { // align buffer
1114 				offset += sizeof(int) - temp;
1115 			}
1116 			data_pool[i].rejected = (int*)((char*)data_pool[i].tmp + offset);
1117 			data_pool[i].o_stack = (void*)((char*)data_pool[i].rejected + sizeof(int) * nb_frames);
1118 
1119 			if (args->type_of_rejection == WINSORIZED) {
1120 				data_pool[i].w_stack = (void*)((char*)data_pool[i].o_stack + ielem_size * nb_frames);
1121 			} else if (args->type_of_rejection == GESDT) {
1122 				data_pool[i].w_stack = (void*)((char*)data_pool[i].o_stack + ielem_size * nb_frames);
1123 				int max_outliers = (int) floor(nb_frames * args->sig[0]);
1124 				args->critical_value = malloc(max_outliers * sizeof(float));
1125 				for (int j = 0, size = nb_frames; j < max_outliers; j++, size--) {
1126 					float t_dist = gsl_cdf_tdist_Pinv(1 - args->sig[1] / (2 * size), size - 2);
1127 					float numerator = (size - 1) * t_dist;
1128 					float denominator = sqrtf(size) * sqrtf(size - 2 + (t_dist * t_dist));
1129 					args->critical_value[j] = numerator / denominator;
1130 				}
1131 			} else if (args->type_of_rejection == LINEARFIT) {
1132 				data_pool[i].xf = (float (*)) ((char*)data_pool[i].o_stack + ielem_size * nb_frames);
1133 				data_pool[i].yf = data_pool[i].xf + nb_frames;
1134 				// precalculate some stuff
1135 				data_pool[i].m_x = (nb_frames - 1) * 0.5f;
1136 				data_pool[i].m_dx2 = 0.f;
1137 				for (int j = 0; j < nb_frames; ++j) {
1138 					const float dx = j - data_pool[i].m_x;
1139 					data_pool[i].xf[j] = 1.f / (j + 1);
1140 					data_pool[i].m_dx2 += (dx * dx - data_pool[i].m_dx2)
1141 							* data_pool[i].xf[j];
1142 				}
1143 				data_pool[i].m_dx2 = 1.f / data_pool[i].m_dx2;
1144 			}
1145 		}
1146 
1147 		for (int j = 0; j < nb_frames; ++j) {
1148 			if (itype == DATA_FLOAT)
1149 				data_pool[i].pix[j] = ((float*)data_pool[i].tmp) + j * npixels_in_block;
1150 			else 	data_pool[i].pix[j] = ((WORD *)data_pool[i].tmp) + j * npixels_in_block;
1151 		}
1152 	}
1153 
1154 	if (itype == DATA_USHORT) {
1155 		args->sd_calculator = nb_frames < 65536 ? siril_stats_ushort_sd_32 : siril_stats_ushort_sd_64;
1156 		args->mad_calculator = siril_stats_ushort_mad;
1157 	}
1158 
1159 	if (args->apply_weight) {
1160 		siril_log_message(_("Computing weights...\n"));
1161 		retval = compute_weights(args);
1162 		if(retval) {
1163 			retval = ST_GENERIC_ERROR;
1164 			goto free_and_close;
1165 		}
1166 	}
1167 
1168 	siril_log_message(_("Starting stacking...\n"));
1169 	if (is_mean)
1170 		set_progress_bar_data(_("Rejection stacking in progress..."), PROGRESS_RESET);
1171 	else	set_progress_bar_data(_("Median stacking in progress..."), PROGRESS_RESET);
1172 	double total = (double)(naxes[2] * naxes[1] + 2); // for progress bar
1173 
1174 #ifdef _OPENMP
1175 #pragma omp parallel for num_threads(nb_threads) private(i) schedule(dynamic) if (nb_threads > 1 && (args->seq->type == SEQ_SER || fits_is_reentrant()))
1176 #endif
1177 	for (i = 0; i < nb_blocks; i++)
1178 	{
1179 		/**** Step 1: get allocated memory for the current thread ****/
1180 		struct _image_block *my_block = blocks+i;
1181 		struct _data_block *data;
1182 		int data_idx = 0;
1183 		long x, y;
1184 
1185 		if (!get_thread_run()) retval = ST_GENERIC_ERROR;
1186 		if (retval) continue;
1187 #ifdef _OPENMP
1188 		data_idx = omp_get_thread_num();
1189 #ifdef STACK_DEBUG
1190 		struct timeval thread_start;
1191 		gettimeofday(&thread_start, NULL);
1192 		fprintf(stdout, "Thread %d takes block %d.\n", data_idx, i);
1193 #endif
1194 #endif
1195 		data = &data_pool[data_idx];
1196 
1197 		/**** Step 2: load image data for the corresponding image block ****/
1198 		stack_read_block_data(args, use_regdata, my_block, data, naxes, itype, data_idx);
1199 
1200 #if defined _OPENMP && defined STACK_DEBUG
1201 		{
1202 			struct timeval thread_mid;
1203 			int min, sec;
1204 			gettimeofday(&thread_mid, NULL);
1205 			get_min_sec_from_timevals(thread_start, thread_mid, &min, &sec);
1206 			fprintf(stdout, "Thread %d loaded block %d after %d min %02d s.\n\n",
1207 					data_idx, i, min, sec);
1208 		}
1209 #endif
1210 
1211 		/**** Step 3: iterate over the y and x of the image block and stack ****/
1212 		int layer = my_block->channel;
1213 		for (y = 0; y < my_block->height; y++)
1214 		{
1215 			/* index of the pixel in the result image
1216 			 * we read line y, but we need to store it at
1217 			 * ry - y - 1 to not have the image mirrored. */
1218 			size_t pdata_idx = (naxes[1] - (my_block->start_row + y) - 1) * naxes[0];
1219 			/* index of the line in the read data, data->pix[frame] */
1220 			size_t line_idx = y * naxes[0];
1221 			guint64 crej[2] = {0, 0};
1222 			if (retval) break;
1223 
1224 			// update progress bar
1225 #ifdef _OPENMP
1226 #pragma omp atomic
1227 #endif
1228 			cur_nb++;
1229 
1230 			if (!get_thread_run()) {
1231 				retval = ST_GENERIC_ERROR;
1232 				break;
1233 			}
1234 			if (!(cur_nb % 16))	// every 16 iterations
1235 				set_progress_bar_data(NULL, (double)cur_nb/total);
1236 
1237 			for (x = 0; x < naxes[0]; ++x) {
1238 				/* copy all images pixel values in the same row array `stack'
1239 				 * to optimize caching and improve readability */
1240 				for (int frame = 0; frame < nb_frames; ++frame) {
1241 					int pix_idx = line_idx + x;
1242 					if (use_regdata) {
1243 						int shiftx = 0;
1244 						if (layerparam) {
1245 							shiftx = round_to_int(
1246 									layerparam[args->image_indices[frame]].shiftx *
1247 									args->seq->upscale_at_stacking);
1248 						}
1249 
1250 						if (shiftx && (x - shiftx >= naxes[0] || x - shiftx < 0)) {
1251 							/* outside bounds, images are black. We could
1252 							 * also set the background value instead, if available */
1253 							if (itype == DATA_FLOAT)
1254 								((float*)data->stack)[frame] = 0.0f;
1255 							else ((WORD *)data->stack)[frame] = 0;
1256 							continue;
1257 						}
1258 
1259 						pix_idx -= shiftx;
1260 					}
1261 
1262 					WORD pixel = 0; float fpixel = 0.f;
1263 					if (itype == DATA_FLOAT)
1264 						fpixel = ((float*) data->pix[frame])[pix_idx];
1265 					else
1266 						pixel = ((WORD*) data->pix[frame])[pix_idx];
1267 					double tmp;
1268 					switch (args->normalize) {
1269 						default:
1270 						case NO_NORM:
1271 							// no normalization (scale[frame] = 1, offset[frame] = 0, mul[frame] = 1)
1272 							if (itype == DATA_FLOAT)
1273 								((float*)data->stack)[frame] = fpixel;
1274 							else	((WORD *)data->stack)[frame] = pixel;
1275 							/* it's faster if we don't convert it to double
1276 							 * to make identity operations */
1277 							break;
1278 						case ADDITIVE:
1279 							// additive (scale[frame] = 1, mul[frame] = 1)
1280 						case ADDITIVE_SCALING:
1281 							// additive + scale (mul[frame] = 1)
1282 							if (itype == DATA_FLOAT) {
1283 								if (fpixel != 0.f) { // do not normalize null pixels to detect them later
1284 									tmp = fpixel * args->coeff.pscale[layer][frame];
1285 									((float*)data->stack)[frame] = (float)(tmp - args->coeff.poffset[layer][frame]);
1286 								} else {
1287 									((float*)data->stack)[frame] = 0.f;
1288 								}
1289 							} else {
1290 								if (pixel > 0) { // do not normalize null pixels to detect them later
1291 									tmp = (double)pixel * args->coeff.pscale[layer][frame];
1292 									((WORD *)data->stack)[frame] = round_to_WORD(tmp - args->coeff.poffset[layer][frame]);
1293 								} else {
1294 									((WORD *)data->stack)[frame] = 0;
1295 								}
1296 							}
1297 							break;
1298 						case MULTIPLICATIVE:
1299 							// multiplicative  (scale[frame] = 1, offset[frame] = 0)
1300 						case MULTIPLICATIVE_SCALING:
1301 							// multiplicative + scale (offset[frame] = 0)
1302 							if (itype == DATA_FLOAT) {
1303 								tmp = fpixel * args->coeff.pscale[layer][frame];
1304 								((float*)data->stack)[frame] = (float)(tmp * args->coeff.pmul[layer][frame]);
1305 							} else {
1306 								tmp = (double)pixel * args->coeff.pscale[layer][frame];
1307 								((WORD *)data->stack)[frame] = round_to_WORD(tmp * args->coeff.pmul[layer][frame]);
1308 							}
1309 							break;
1310 					}
1311 				}
1312 
1313 				double result; // resulting pixel value, either mean or median
1314 				if (is_mean) {
1315 					result = mean_and_reject(args, data, nb_frames, itype, crej);
1316 				} else {
1317 					if (itype == DATA_USHORT)
1318 						result = quickmedian(data->stack, nb_frames);
1319 					else 	result = quickmedian_float(data->stack, nb_frames);
1320 				}
1321 
1322 				if (args->use_32bit_output) {
1323 					if (itype == DATA_USHORT)
1324 						fit.fpdata[my_block->channel][pdata_idx] = min(double_ushort_to_float_range(result), 1.f);
1325 					else	fit.fpdata[my_block->channel][pdata_idx] = min((float)result, 1.f);
1326 				} else {
1327 					/* in case of 8bit data we may want to normalize to 16bits */
1328 					if (args->output_norm) {
1329 						normalize_to16bit(bitpix, &result);
1330 					}
1331 					fit.pdata[my_block->channel][pdata_idx] = round_to_WORD(result);
1332 				}
1333 				pdata_idx++;
1334 			} // end of for x
1335 
1336 			if (is_mean && args->type_of_rejection != NO_REJEC) {
1337 #ifdef _OPENMP
1338 #pragma omp atomic
1339 #endif
1340 				irej[my_block->channel][0] += crej[0];
1341 #ifdef _OPENMP
1342 #pragma omp atomic
1343 #endif
1344 				irej[my_block->channel][1] += crej[1];
1345 			}
1346 
1347 		} // end of for y
1348 #if defined _OPENMP && defined STACK_DEBUG
1349 		{
1350 			struct timeval thread_end;
1351 			int min, sec;
1352 			gettimeofday(&thread_end, NULL);
1353 			get_min_sec_from_timevals(thread_start, thread_end, &min, &sec);
1354 			fprintf(stdout, "Thread %d finishes block %d after %d min %02d s.\n",
1355 					data_idx, i, min, sec);
1356 		}
1357 #endif
1358 	} /* end of loop over parallel stacks */
1359 
1360 	if (retval)
1361 		goto free_and_close;
1362 
1363 	set_progress_bar_data(_("Finalizing stacking..."), (double)cur_nb/total);
1364 	if (is_mean) {
1365 		double nb_tot = (double) naxes[0] * (double) naxes[1] * (double) nb_frames;
1366 		for (long channel = 0; channel < naxes[2]; channel++) {
1367 			siril_log_message(_("Pixel rejection in channel #%d: %.3lf%% - %.3lf%%\n"),
1368 					channel, (double) irej[channel][0] / nb_tot * 100.0,
1369 					(double) irej[channel][1] / nb_tot * 100.0);
1370 		}
1371 	}
1372 
1373 	/* copy result to gfit if success */
1374 	clearfits(&gfit);
1375 	copyfits(&fit, &gfit, CP_FORMAT, -1);
1376 
1377 	if (args->use_32bit_output) {
1378 		gfit.fdata = fit.fdata;
1379 		for (i = 0; i < fit.naxes[2]; i++)
1380 			gfit.fpdata[i] = fit.fpdata[i];
1381 
1382 		if (args->output_norm) {
1383 			norm_to_0_1_range(&gfit);
1384 		}
1385 	} else {
1386 		gfit.data = fit.data;
1387 		for (i = 0; i < fit.naxes[2]; i++)
1388 			gfit.pdata[i] = fit.pdata[i];
1389 	}
1390 
1391 	compute_date_time_keywords(list_date, &gfit);
1392 
1393 free_and_close:
1394 	fprintf(stdout, "free and close (%d)\n", retval);
1395 	for (i = 0; i < nb_frames; ++i) {
1396 		seq_close_image(args->seq, args->image_indices[i]);
1397 	}
1398 
1399 	if (data_pool) {
1400 		for (i=0; i<pool_size; i++) {
1401 			if (data_pool[i].pix) free(data_pool[i].pix);
1402 			if (data_pool[i].tmp) free(data_pool[i].tmp);
1403 		}
1404 		free(data_pool);
1405 	}
1406 	g_list_free_full(list_date, (GDestroyNotify) free_list_date);
1407 	if (blocks) free(blocks);
1408 	if (args->normalize) {
1409 		free(args->coeff.offset);
1410 		free(args->coeff.scale);
1411 		free(args->coeff.mul);
1412 	}
1413 
1414 	if (args->weights) free(args->weights);
1415 	if (retval) {
1416 		/* if retval is set, gfit has not been modified */
1417 		if (fit.data) free(fit.data);
1418 		if (fit.fdata) free(fit.fdata);
1419 		if (is_mean)
1420 			set_progress_bar_data(_("Rejection stacking failed. Check the log."), PROGRESS_RESET);
1421 		else	set_progress_bar_data(_("Median stacking failed. Check the log."), PROGRESS_RESET);
1422 		siril_log_message(_("Stacking failed.\n"));
1423 	} else {
1424 		if (is_mean) {
1425 			set_progress_bar_data(_("Rejection stacking complete."), PROGRESS_DONE);
1426 			siril_log_message(_("Rejection stacking complete. %d images have been stacked.\n"), nb_frames);
1427 		} else {
1428 			set_progress_bar_data(_("Median stacking complete."), PROGRESS_DONE);
1429 			siril_log_message(_("Median stacking complete. %d images have been stacked.\n"), nb_frames);
1430 		}
1431 	}
1432 	return retval;
1433 }
1434 
1435