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, ¤t_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, ¤t_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