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 "plot.h"
22 
23 #include <cairo.h>
24 #include <math.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #ifndef _WIN32
29 #include <sys/wait.h>
30 #endif
31 
32 #include "core/siril.h"
33 #include "core/proto.h"
34 #include "core/siril_date.h"
35 #include "core/OS_utils.h"
36 #include "core/processing.h"
37 #include "gui/utils.h"
38 #include "gui/image_display.h"
39 #include "gui/message_dialog.h"
40 #include "gui/progress_and_log.h"
41 #include "gui/sequence_list.h"
42 #include "kplot.h"
43 #include "algos/PSF.h"
44 #include "io/ser.h"
45 #include "gui/gnuplot_i/gnuplot_i.h"
46 #include "gui/PSF_list.h"
47 
48 #define XLABELSIZE 15
49 
50 static GtkWidget *drawingPlot = NULL, *sourceCombo = NULL, *combo = NULL,
51 		*varCurve = NULL, *buttonClearAll = NULL,
52 		*buttonClearLatest = NULL, *arcsec = NULL, *julianw = NULL;
53 static pldata *plot_data;
54 static struct kpair ref;
55 static gboolean is_fwhm = FALSE, use_photometry = FALSE, requires_color_update =
56 		FALSE;
57 static char *ylabel = NULL;
58 static gchar *xlabel = NULL;
59 static enum photmetry_source selected_source = ROUNDNESS;
60 static int julian0 = 0;
61 static gnuplot_ctrl *gplot = NULL;
62 static gboolean is_arcsec = FALSE;
63 static gboolean force_Julian = FALSE;
64 
65 static void update_ylabel();
66 static void set_colors(struct kplotcfg *cfg);
67 static void free_colors(struct kplotcfg *cfg);
68 
alloc_plot_data(int size)69 static pldata *alloc_plot_data(int size) {
70 	pldata *plot = calloc(1, sizeof(pldata));
71 	if (!plot) {
72 		PRINT_ALLOC_ERR;
73 		return NULL;
74 	}
75 	plot->frame = calloc(size, sizeof(double));
76 	if (!plot->frame) {
77 		PRINT_ALLOC_ERR;
78 		free(plot);
79 		return NULL;
80 	}
81 	plot->julian = calloc(size, sizeof(double));
82 	if (!plot->julian) {
83 		PRINT_ALLOC_ERR;
84 		free(plot->frame);
85 		free(plot);
86 		return NULL;
87 	}
88 	plot->data = calloc(size, sizeof(struct kpair));
89 	if (!plot->data) {
90 		PRINT_ALLOC_ERR;
91 		free(plot->frame);
92 		free(plot->julian);
93 		free(plot);
94 		return NULL;
95 	}
96 	plot->err = calloc(size, sizeof(struct kpair));
97 	if (!plot->err) {
98 		PRINT_ALLOC_ERR;
99 		free(plot->frame);
100 		free(plot->julian);
101 		free(plot->data);
102 		free(plot);
103 		return NULL;
104 	}
105 	plot->nb = size;
106 	plot->next = NULL;
107 	return plot;
108 }
109 
build_registration_dataset(sequence * seq,int layer,int ref_image,pldata * plot)110 static void build_registration_dataset(sequence *seq, int layer, int ref_image,
111 		pldata *plot) {
112 	int i, j;
113 
114 	for (i = 0, j = 0; i < plot->nb; i++) {
115 		if (!seq->imgparam[i].incl)
116 			continue;
117 		plot->data[j].x = (double) i + 1;
118 		plot->data[j].y = is_fwhm ?	seq->regparam[layer][i].fwhm :
119 						seq->regparam[layer][i].quality;
120 		plot->frame[j] =  plot->data[j].x;
121 		j++;
122 	}
123 	plot->nb = j;
124 
125 	ref.x = (double) ref_image + 1;
126 	ref.y = is_fwhm ?
127 			seq->regparam[layer][ref_image].fwhm :
128 			seq->regparam[layer][ref_image].quality;
129 
130 }
131 
set_x_values(sequence * seq,pldata * plot,int i,int j)132 static void set_x_values(sequence *seq, pldata *plot, int i, int j) {
133 	if (seq->imgparam[i].date_obs) {
134 		double julian;
135 		GDateTime *tsi = g_date_time_ref(seq->imgparam[i].date_obs);
136 		if (seq->exposure) {
137 			GDateTime *new_dt = g_date_time_add_seconds(tsi, seq->exposure / 2.0);
138 			julian = date_time_to_Julian(new_dt);
139 			g_date_time_unref(new_dt);
140 		} else {
141 			julian = date_time_to_Julian(tsi);
142 		}
143 
144 		plot->julian[j] = julian - (double) julian0;
145 
146 		g_date_time_unref(tsi);
147 	} else {
148 		plot->julian[j] = (double) i + 1; // should not happen.
149 	}
150 	plot->frame[j] = (double) i + 1;
151 
152 	if (julian0 && force_Julian) {
153 		plot->data[j].x = plot->julian[j];
154 	} else {
155 		plot->data[j].x = plot->frame[j];
156 	}
157 
158 	plot->err[j].x = plot->data[j].x;
159 }
160 
build_photometry_dataset(sequence * seq,int dataset,int size,int ref_image,pldata * plot)161 static void build_photometry_dataset(sequence *seq, int dataset, int size,
162 		int ref_image, pldata *plot) {
163 	int i, j;
164 	double offset = -1001.0;
165 	fitted_PSF **psfs = seq->photometry[dataset], *ref_psf;
166 	if (seq->reference_star >= 0 && !seq->photometry[seq->reference_star])
167 		seq->reference_star = -1;
168 
169 	for (i = 0, j = 0; i < size; i++) {
170 		if (!seq->imgparam[i].incl || !psfs[i])
171 			continue;
172 		if (!julian0 && !xlabel) {
173 			if (seq->imgparam[i].date_obs) {
174 				GDateTime *ts0 = g_date_time_ref(seq->imgparam[i].date_obs);
175 				if (seq->exposure) {
176 					GDateTime *new_dt = g_date_time_add_seconds(ts0, seq->exposure / 2.0);
177 					julian0 = (int) date_time_to_Julian(new_dt);
178 					g_date_time_unref(new_dt);
179 				} else {
180 					julian0 = (int) date_time_to_Julian(ts0);
181 				}
182 				g_date_time_unref(ts0);
183 			}
184 			if (julian0 && force_Julian) {
185 				xlabel = calloc(XLABELSIZE, sizeof(char));
186 				g_snprintf(xlabel, XLABELSIZE, "(JD) %d +", julian0);
187 			} else {
188 				xlabel = g_strdup(_("Frames"));
189 			}
190 		}
191 		set_x_values(seq, plot, i, j);
192 
193 		switch (selected_source) {
194 			case ROUNDNESS:
195 				plot->data[j].y = psfs[i]->fwhmy / psfs[i]->fwhmx;
196 				break;
197 			case FWHM:
198 				if (is_arcsec)
199 					fwhm_to_arcsec_if_needed(&gfit, psfs[i]);
200 				else
201 					fwhm_to_pixels(psfs[i]);
202 				plot->data[j].y = psfs[i]->fwhmx;
203 				break;
204 			case AMPLITUDE:
205 				plot->data[j].y = psfs[i]->A;
206 				break;
207 			case MAGNITUDE:
208 				plot->data[j].y = psfs[i]->mag;
209 				plot->err[j].y = psfs[i]->s_mag;
210 
211 				if (seq->reference_star >= 0) {
212 					/* we have a reference star for the sequence,
213 					 * with photometry data */
214 					ref_psf = seq->photometry[seq->reference_star][i];
215 					if (ref_psf)
216 						offset = seq->reference_mag - ref_psf->mag;
217 				} else if (com.magOffset > 0.0)
218 					offset = com.magOffset;
219 
220 				/* apply the absolute apparent magnitude offset */
221 				if (offset > -1000.0)
222 					plot->data[j].y += offset;
223 				break;
224 			case BACKGROUND:
225 				plot->data[j].y = psfs[i]->B;
226 				break;
227 			case X_POSITION:
228 				plot->data[j].y = psfs[i]->xpos;
229 				break;
230 			case Y_POSITION:
231 				plot->data[j].y = psfs[i]->ypos;
232 				break;
233 		}
234 
235 		/* we'll just take the reference image point from the last data set rendered */
236 		if (i == ref_image) {
237 			ref.x = plot->data[j].x;
238 			ref.y = plot->data[j].y;
239 		}
240 		j++;
241 	}
242 	plot->nb = j;
243 }
244 
245 #ifdef _WIN32
246 static const gchar *possible_path[] = { "C:\\PROGRA~1\\gnuplot\\bin\\gnuplot.exe", "C:\\msys64\\mingw64\\bin\\gnuplot.exe" };
247 static const gchar *gnuplot_path = NULL;
248 
249 /* returns true if the gnuplot.exe exists in the wanted folder */
gnuplot_is_available()250 static gboolean gnuplot_is_available() {
251 	size_t size, i = 0;
252 	gboolean found = FALSE;
253 
254 	size = sizeof(possible_path) / sizeof(gchar*);
255 	do {
256 		found = g_file_test(possible_path[i], G_FILE_TEST_EXISTS);
257 		i++;
258 	} while (i < size && !found);
259 
260 	if (found)
261 		gnuplot_path = possible_path[i - 1];
262 
263 	return found;
264 }
265 #else
266 /* returns true if the command gnuplot is available */
gnuplot_is_available()267 static gboolean gnuplot_is_available() {
268 	gchar *str = g_strdup_printf("%s -e > /dev/null 2>&1", siril_win_get_gnuplot_path());
269 
270 	int retval = system(str);
271 	g_free(str);
272 	if (WIFEXITED(retval))
273 		return 0 == WEXITSTATUS(retval);
274 	return FALSE;
275 }
276 #endif
277 
siril_win_get_gnuplot_path()278 gchar *siril_win_get_gnuplot_path() {
279 #ifdef _WIN32
280 	gchar *str = g_strdup_printf("\"%s -persist\"", gnuplot_path);
281 	return str;
282 #else
283 	return "gnuplot";
284 #endif
285 }
286 
lightCurve(pldata * plot,sequence * seq,gchar * filename)287 static int lightCurve(pldata *plot, sequence *seq, gchar *filename) {
288 	int i, j, k, nbImages = 0, ret = 0;
289 	pldata *tmp_plot = plot;
290 	double *vmag, *err, *x, *real_x;
291 
292 	if (!gnuplot_is_available()) {
293 		char *msg = siril_log_message(_("Please consider to install it before "
294 				"trying to plot a graph of a variable star.\n"));
295 
296 		siril_message_dialog( GTK_MESSAGE_WARNING, _("Gnuplot is unavailable"), msg);
297 
298 		return -1;
299 	}
300 
301 	/* get number of data */
302 	for (i = 0, j = 0; i < plot->nb; i++) {
303 		if (!seq->imgparam[i].incl)
304 			continue;
305 		++nbImages;
306 	}
307 	if (nbImages < 1)
308 		return -1;
309 	vmag = calloc(nbImages, sizeof(double));
310 	err = calloc(nbImages, sizeof(double));
311 	x = calloc(nbImages, sizeof(double));
312 	real_x = calloc(nbImages, sizeof(double));
313 	if (!vmag || !err || !x || !real_x) {
314 		PRINT_ALLOC_ERR;
315 		return -1;
316 	}
317 	for (i = 0, j = 0; i < plot->nb; i++) {
318 		if (!seq->imgparam[i].incl)
319 			continue;
320 		double cmag = 0.0, cerr = 0.0;
321 
322 		vmag[j] = tmp_plot->data[j].y;
323 		/* when data are sorted we need to check order
324 		 * by matching timestamps in order
325 		 * to sort uncertainties as well
326 		 */
327 		for (k = 0; k < plot->nb; k++) {
328 			if (tmp_plot->err[k].x == tmp_plot->data[j].x)
329 				err[j] = tmp_plot->err[k].y;
330 		}
331 
332 		x[j] = tmp_plot->data[j].x;
333 		real_x[j] = x[j] + (double) julian0;
334 		tmp_plot = tmp_plot->next;
335 		int n = 0;
336 		/* Warning: first data plotted are variable data, others are references
337 		 * Variable is done above, now we compute references */
338 		while ((n + 1) < MAX_SEQPSF && seq->photometry[n + 1]) {
339 			/* variable data, inversion of Pogson's law
340 			 * Flux = 10^(-0.4 * mag)
341 			 */
342 			cmag += pow(10, -0.4 * tmp_plot->data[j].y);
343 			/* when data are sorted we need to check order
344 			 * by matching timestamps in order
345 			 * to sort uncertainties as well
346 			 */
347 			for (k = 0; k < plot->nb; k++) {
348 				if (tmp_plot->err[k].x == tmp_plot->data[j].x)
349 					cerr += tmp_plot->err[k].y;
350 			}
351 			tmp_plot = tmp_plot->next;
352 			++n;
353 		}
354 		/* Converting back to magnitude */
355 		if (n > 0) {
356 			cmag = -2.5 * log10(cmag / n);
357 			cerr = (cerr / n) / sqrt((double) n);
358 
359 			vmag[j] = vmag[j] - cmag;
360 			err[j] = fmin(9.999, sqrt(err[j] * err[j] + cerr * cerr));
361 		}
362 		tmp_plot = plot;
363 		j++;
364 	}
365 
366 	/*  data are computed, now plot the graph. */
367 
368 	/* First, close the graph if already exists */
369 	if (gplot != NULL) {
370 		gnuplot_close(gplot);
371 	}
372 
373 	if ((gplot = gnuplot_init()) == NULL) {
374 		free(vmag);
375 		free(err);
376 		free(x);
377 		free(real_x);
378 		return -1;
379 	}
380 
381 	/* Plotting light curve */
382 	gnuplot_set_title(gplot, _("Light Curve"));
383 	gnuplot_set_xlabel(gplot, xlabel);
384 	gnuplot_reverse_yaxis(gplot);
385 	gnuplot_setstyle(gplot, "errorbars");
386 	gnuplot_plot_xyyerr(gplot, x, vmag, err, nbImages, "");
387 
388 	/* Exporting data in a dat file */
389 	ret = gnuplot_write_xyyerr_dat(filename, real_x, vmag, err, nbImages, "JD_UT V-C err");
390 	if (!ret) {
391 		siril_log_message(_("%s has been saved.\n"), filename);
392 	} else {
393 		siril_message_dialog( GTK_MESSAGE_ERROR, _("Error"), _("Something went wrong while saving plot"));
394 	}
395 
396 	free(vmag);
397 	free(x);
398 	free(real_x);
399 	return 0;
400 }
401 
exportCSV(pldata * plot,sequence * seq,gchar * filename)402 static int exportCSV(pldata *plot, sequence *seq, gchar *filename) {
403 	GError *error = NULL;
404 
405 	GFile *file = g_file_new_for_path(filename);
406 	GOutputStream *output_stream = (GOutputStream*) g_file_replace(file, NULL, FALSE,
407 			G_FILE_CREATE_NONE, NULL, &error);
408 
409 	if (output_stream == NULL) {
410 		if (error != NULL) {
411 			g_warning("%s\n", error->message);
412 			g_clear_error(&error);
413 			fprintf(stderr, "exportCSV: Cannot export\n");
414 		}
415 		g_object_unref(file);
416 		return 1;
417 	}
418 
419 	if (use_photometry) {
420 		pldata *tmp_plot = plot;
421 		for (int i = 0, j = 0; i < plot->nb; i++) {
422 			if (!seq->imgparam[i].incl)
423 				continue;
424 			int x = 0;
425 			double date = tmp_plot->data[j].x;
426 			if (julian0 && force_Julian) {
427 				date += julian0;
428 			}
429 			gchar *buffer = g_strdup_printf("%.10lf", date);
430 			if (!g_output_stream_write_all(output_stream, buffer, strlen(buffer), NULL, NULL, &error)) {
431 				g_warning("%s\n", error->message);
432 				g_free(buffer);
433 				g_clear_error(&error);
434 				g_object_unref(output_stream);
435 				g_object_unref(file);
436 				return 1;
437 			}
438 			g_free(buffer);
439 			while (x < MAX_SEQPSF && seq->photometry[x]) {
440 				buffer = g_strdup_printf(", %g", tmp_plot->data[j].y);
441 				if (!g_output_stream_write_all(output_stream, buffer, strlen(buffer), NULL, NULL, &error)) {
442 					g_warning("%s\n", error->message);
443 					g_free(buffer);
444 					g_clear_error(&error);
445 					g_object_unref(output_stream);
446 					g_object_unref(file);
447 					return 1;
448 				}
449 				tmp_plot = tmp_plot->next;
450 				++x;
451 				g_free(buffer);
452 			}
453 			if (!g_output_stream_write_all(output_stream, "\n", 1, NULL, NULL, &error)) {
454 				g_warning("%s\n", error->message);
455 				g_free(buffer);
456 				g_clear_error(&error);
457 				g_object_unref(output_stream);
458 				g_object_unref(file);
459 				return 1;
460 			}
461 			tmp_plot = plot;
462 			j++;
463 		}
464 	} else {
465 		for (int i = 0, j = 0; i < plot->nb; i++) {
466 			if (!seq->imgparam[i].incl)
467 				continue;
468 			double date = plot->data[j].x;
469 			if (julian0) {
470 				date += julian0;
471 			}
472 			gchar *buffer = g_strdup_printf("%.10lf, %g\n", date, plot->data[j].y);
473 			if (!g_output_stream_write_all(output_stream, buffer, strlen(buffer), NULL, NULL, &error)) {
474 				g_warning("%s\n", error->message);
475 				g_free(buffer);
476 				g_clear_error(&error);
477 				g_object_unref(output_stream);
478 				g_object_unref(file);
479 				return 1;
480 			}
481 			j++;
482 			g_free(buffer);
483 		}
484 	}
485 	siril_log_message(_("%s has been saved.\n"), filename);
486 	g_object_unref(output_stream);
487 	g_object_unref(file);
488 
489 	return 0;
490 }
491 
free_plot_data()492 static void free_plot_data() {
493 	pldata *plot = plot_data;
494 	while (plot) {
495 		pldata *next = plot->next;
496 		if (plot->julian)
497 			free(plot->julian);
498 		if (plot->frame)
499 			free(plot->frame);
500 		if (plot->data)
501 			free(plot->data);
502 		if (plot->err)
503 			free(plot->err);
504 		free(plot);
505 		plot = next;
506 	}
507 	plot_data = NULL;
508 	julian0 = 0;
509 	if (xlabel) {
510 		g_free(xlabel);
511 		xlabel = NULL;
512 	}
513 }
514 
on_plotSourceCombo_changed(GtkComboBox * box,gpointer user_data)515 void on_plotSourceCombo_changed(GtkComboBox *box, gpointer user_data) {
516 	use_photometry = gtk_combo_box_get_active(GTK_COMBO_BOX(box));
517 	gtk_widget_set_visible(combo, use_photometry);
518 	gtk_widget_set_visible(varCurve, use_photometry);
519 	gtk_widget_set_visible(arcsec, use_photometry);
520 	gtk_widget_set_visible(julianw, use_photometry);
521 	drawPlot();
522 }
523 
reset_plot()524 void reset_plot() {
525 	free_plot_data();
526 	if (sourceCombo) {
527 		gtk_combo_box_set_active(GTK_COMBO_BOX(sourceCombo), 0);
528 		gtk_widget_set_visible(sourceCombo, FALSE);
529 		gtk_widget_set_visible(combo, FALSE);
530 		gtk_widget_set_visible(varCurve, FALSE);
531 		gtk_widget_set_visible(arcsec, FALSE);
532 		gtk_widget_set_visible(julianw, FALSE);
533 		gtk_widget_set_sensitive(buttonClearLatest, FALSE);
534 		gtk_widget_set_sensitive(buttonClearAll, FALSE);
535 	}
536 }
537 
compare(const void * a,const void * b)538 static int compare(const void *a, const void *b) {
539     struct kpair datax_a = * ((struct kpair *) a);
540     struct kpair datax_b = * ((struct kpair *) b);
541 
542     if (datax_a.x > datax_b.x) {
543         return 1;
544     } else if (datax_a.x < datax_b.x) {
545         return -1;
546     } else
547         return 0;
548 }
549 
drawPlot()550 void drawPlot() {
551 	int i, ref_image, layer = 0;
552 	sequence *seq;
553 
554 	if (drawingPlot == NULL) {
555 		drawingPlot = lookup_widget("DrawingPlot");
556 		combo = lookup_widget("plotCombo");
557 		varCurve = lookup_widget("varCurvePhotometry");
558 		arcsec = lookup_widget("arcsecPhotometry");
559 		julianw = lookup_widget("JulianPhotometry");
560 		sourceCombo = lookup_widget("plotSourceCombo");
561 		buttonClearAll = lookup_widget("clearAllPhotometry");
562 		buttonClearLatest = lookup_widget("clearLastPhotometry");
563 	}
564 
565 	seq = &com.seq;
566 	if (plot_data)
567 		free_plot_data();
568 
569 	if (seq->reference_image == -1)
570 		ref_image = 0;
571 	else ref_image = seq->reference_image;
572 
573 	if (use_photometry) {
574 		// photometry data display
575 		pldata *plot;
576 		update_ylabel();
577 		ref.x = -1.0;
578 		ref.y = -1.0;
579 
580 		plot = alloc_plot_data(seq->number);
581 		plot_data = plot;
582 		for (i = 0; i < MAX_SEQPSF && seq->photometry[i]; i++) {
583 			if (i > 0) {
584 				plot->next = alloc_plot_data(seq->number);
585 				plot = plot->next;
586 			}
587 
588 			build_photometry_dataset(seq, i, seq->number, ref_image, plot);
589 			qsort(plot->data, plot->nb, sizeof(struct kpair), compare);
590 		}
591 	} else {
592 		// registration data display
593 		if (!(seq->regparam))
594 			return;
595 
596 		for (i = 0; i < seq->nb_layers; i++) {
597 			if (com.seq.regparam[i]) {
598 				layer = i;
599 				break;
600 			}
601 		}
602 		if ((!seq->regparam[layer]))
603 			return;
604 
605 		if (seq->regparam[layer][ref_image].fwhm > 0.0f) {
606 			is_fwhm = TRUE;
607 			ylabel = _("FWHM");
608 		} else if (seq->regparam[layer][ref_image].quality > 0.0) {
609 			is_fwhm = FALSE;
610 			ylabel = _("Quality");
611 		} else
612 			return;
613 
614 		/* building data array */
615 		plot_data = alloc_plot_data(seq->number);
616 
617 		build_registration_dataset(seq, layer, ref_image, plot_data);
618 	}
619 	gtk_widget_set_sensitive(julianw, julian0);
620 	gtk_widget_queue_draw(drawingPlot);
621 }
622 
set_filter(GtkFileChooser * dialog,const gchar * format)623 static void set_filter(GtkFileChooser *dialog, const gchar *format) {
624 	GtkFileFilter *f = gtk_file_filter_new();
625 	gchar *name = g_strdup_printf(_("Output files (*%s)"), format);
626 	gchar *pattern = g_strdup_printf("*%s", format);
627 	gtk_file_filter_set_name(f, name);
628 	gtk_file_filter_add_pattern(f, pattern);
629 	gtk_file_chooser_add_filter(dialog, f);
630 	gtk_file_chooser_set_filter(dialog, f);
631 
632 	g_free(name);
633 	g_free(pattern);
634 }
635 
save_dialog(const gchar * format,int (export_function)(pldata *,sequence *,gchar *))636 static void save_dialog(const gchar *format, int (export_function)(pldata *, sequence *, gchar *)) {
637 	GtkWindow *control_window = GTK_WINDOW(GTK_APPLICATION_WINDOW(lookup_widget("control_window")));
638 	SirilWidget *widgetdialog = siril_file_chooser_save(control_window, GTK_FILE_CHOOSER_ACTION_SAVE);
639 	GtkFileChooser *dialog = GTK_FILE_CHOOSER(widgetdialog);
640 
641 	gtk_file_chooser_set_current_folder(dialog, com.wd);
642 	gtk_file_chooser_set_select_multiple(dialog, FALSE);
643 	gtk_file_chooser_set_do_overwrite_confirmation(dialog, TRUE);
644 	gtk_file_chooser_set_current_name(dialog, format);
645 	set_filter(dialog, format);
646 
647 	gint res = siril_dialog_run(widgetdialog);
648 	if (res == GTK_RESPONSE_ACCEPT) {
649 		gchar *file = gtk_file_chooser_get_filename(dialog);
650 		export_function(plot_data, &com.seq, file);
651 
652 		g_free(file);
653 	}
654 	siril_widget_destroy(widgetdialog);
655 }
656 
on_ButtonSaveCSV_clicked(GtkButton * button,gpointer user_data)657 void on_ButtonSaveCSV_clicked(GtkButton *button, gpointer user_data) {
658 	set_cursor_waiting(TRUE);
659 	save_dialog(".csv", exportCSV);
660 	set_cursor_waiting(FALSE);
661 }
662 
on_varCurvePhotometry_clicked(GtkButton * button,gpointer user_data)663 void on_varCurvePhotometry_clicked(GtkButton *button, gpointer user_data) {
664 	set_cursor_waiting(TRUE);
665 	save_dialog(".dat", lightCurve);
666 	set_cursor_waiting(FALSE);
667 }
668 
free_photometry_set(sequence * seq,int set)669 void free_photometry_set(sequence *seq, int set) {
670 	for (int j = 0; j < seq->number; j++) {
671 		if (seq->photometry[set][j])
672 			free(seq->photometry[set][j]);
673 	}
674 	free(seq->photometry[set]);
675 	seq->photometry[set] = NULL;
676 }
677 
on_clearLatestPhotometry_clicked(GtkButton * button,gpointer user_data)678 void on_clearLatestPhotometry_clicked(GtkButton *button, gpointer user_data) {
679 	int i;
680 	for (i = 0; i < MAX_SEQPSF && com.seq.photometry[i]; i++)
681 		;
682 	if (i != 0) {
683 		i--;
684 		free_photometry_set(&com.seq, i);
685 	}
686 	if (i == 0) {
687 		reset_plot();
688 		clear_stars_list();
689 	}
690 	drawPlot();
691 }
692 
on_clearAllPhotometry_clicked(GtkButton * button,gpointer user_data)693 void on_clearAllPhotometry_clicked(GtkButton *button, gpointer user_data) {
694 	for (int i = 0; i < MAX_SEQPSF && com.seq.photometry[i]; i++) {
695 		free_photometry_set(&com.seq, i);
696 	}
697 	reset_plot();
698 	clear_stars_list();
699 	drawPlot();
700 }
701 
on_DrawingPlot_draw(GtkWidget * widget,cairo_t * cr,gpointer data)702 gboolean on_DrawingPlot_draw(GtkWidget *widget, cairo_t *cr, gpointer data) {
703 	guint width, height, i, j;
704 	double mean, color;
705 	int nb_graphs = 0;
706 	struct kpair *avg;
707 	struct kplotcfg cfgplot;
708 	struct kdatacfg cfgdata;
709 	struct kdata *d1, *ref_d, *mean_d;
710 	struct kplot *p;
711 
712 	if (plot_data) {
713 		pldata *plot = plot_data;
714 		d1 = ref_d = mean_d = NULL;
715 
716 		color = (com.pref.combo_theme == 0) ? 0.0 : 1.0;
717 
718 		kplotcfg_defaults(&cfgplot);
719 		kdatacfg_defaults(&cfgdata);
720 		set_colors(&cfgplot);
721 		cfgplot.ticlabel = TICLABEL_LEFT | TICLABEL_BOTTOM;
722 		cfgplot.border = BORDER_ALL;
723 		cfgplot.borderline.clr.type = KPLOTCTYPE_RGBA;
724 		cfgplot.borderline.clr.rgba[0] = 0.5;
725 		cfgplot.borderline.clr.rgba[1] = 0.5;
726 		cfgplot.borderline.clr.rgba[2] = 0.5;
727 		cfgplot.borderline.clr.rgba[3] = 1.0;
728 		cfgplot.xaxislabel = xlabel == NULL ? _("Frames") : xlabel;
729 		cfgplot.xtics = 3;
730 		cfgplot.yaxislabel = ylabel;
731 		cfgplot.yaxislabelrot = M_PI_2 * 3.0;
732 		cfgplot.xticlabelpad = cfgplot.yticlabelpad = 10.0;
733 		cfgdata.point.radius = 10;
734 
735 		p = kplot_alloc(&cfgplot);
736 
737 		// data plots
738 		while (plot) {
739 			d1 = kdata_array_alloc(plot->data, plot->nb);
740 			kplot_attach_data(p, d1,
741 					((plot_data->nb <= 100) ? KPLOT_LINESPOINTS : KPLOT_LINES),
742 					NULL);
743 			plot = plot->next;
744 			nb_graphs++;
745 		}
746 
747 		/* mean and min/max */
748 		mean = kdata_ymean(d1);
749 		//sigma = kdata_ystddev(d1);
750 		int min_data = kdata_xmin(d1, NULL);
751 		int max_data = kdata_xmax(d1, NULL);
752 
753 		if (nb_graphs == 1) {
754 			avg = calloc((max_data - min_data) + 1, sizeof(struct kpair));
755 			j = min_data;
756 			for (i = 0; i < (max_data - min_data) + 1; i++) {
757 				avg[i].x = plot_data->data[j].x;
758 				avg[i].y = mean;
759 				++j;
760 			}
761 
762 			mean_d = kdata_array_alloc(avg, (max_data - min_data) + 1);
763 			kplot_attach_data(p, mean_d, KPLOT_LINES, NULL);	// mean plot
764 			free(avg);
765 
766 			if (ref.x >= 0.0 && ref.y >= 0.0) {
767 				ref_d = kdata_array_alloc(&ref, 1);
768 				kplot_attach_data(p, ref_d, KPLOT_POINTS, &cfgdata);	// ref image dot
769 			}
770 		}
771 
772 		width = gtk_widget_get_allocated_width(widget);
773 		height = gtk_widget_get_allocated_height(widget);
774 
775 		cairo_set_source_rgb(cr, color, color, color);
776 		cairo_rectangle(cr, 0.0, 0.0, width, height);
777 		cairo_fill(cr);
778 		kplot_draw(p, width, height, cr);
779 
780 		/* copy graph colours for star highlight */
781 		if (requires_color_update) {
782 			for (i = 0; i < cfgplot.clrsz; i++) {
783 				com.seq.photometry_colors[i][0] = cfgplot.clrs[i].rgba[0];
784 				com.seq.photometry_colors[i][1] = cfgplot.clrs[i].rgba[1];
785 				com.seq.photometry_colors[i][2] = cfgplot.clrs[i].rgba[2];
786 			}
787 			redraw(com.cvport, REMAP_ONLY);
788 			requires_color_update = FALSE;
789 		}
790 
791 		free_colors(&cfgplot);
792 		kplot_free(p);
793 		kdata_destroy(d1);
794 		kdata_destroy(ref_d);
795 		if (mean_d)
796 			kdata_destroy(mean_d);
797 	}
798 	return FALSE;
799 }
800 
on_plotCombo_changed(GtkComboBox * box,gpointer user_data)801 void on_plotCombo_changed(GtkComboBox *box, gpointer user_data) {
802 	drawPlot();
803 }
804 
on_arcsecPhotometry_toggled(GtkToggleButton * button,gpointer user_data)805 void on_arcsecPhotometry_toggled(GtkToggleButton *button, gpointer user_data) {
806 	is_arcsec = gtk_toggle_button_get_active(button);
807 	drawPlot();
808 }
809 
on_JulianPhotometry_toggled(GtkToggleButton * button,gpointer user_data)810 void on_JulianPhotometry_toggled(GtkToggleButton *button, gpointer user_data) {
811 	force_Julian = gtk_toggle_button_get_active(button);
812 	drawPlot();
813 }
814 
update_ylabel()815 static void update_ylabel() {
816 	selected_source = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
817 	gtk_widget_set_sensitive(varCurve, selected_source == MAGNITUDE);
818 	gboolean arcsec_is_ok = (gfit.focal_length > 0.0 && gfit.pixel_size_x > 0.f
819 			&& gfit.pixel_size_y > 0.f && gfit.binning_x > 0
820 			&& gfit.binning_y > 0);
821 	gtk_widget_set_visible(arcsec, selected_source == FWHM && arcsec_is_ok);
822 	switch (selected_source) {
823 	case ROUNDNESS:
824 		ylabel = _("Star roundness (1 is round)");
825 		break;
826 	case FWHM:
827 		if (is_arcsec)
828 			ylabel = _("FWHM ('')");
829 		else
830 			ylabel = _("FWHM (px)");
831 		break;
832 	case AMPLITUDE:
833 		ylabel = _("Amplitude");
834 		break;
835 	case MAGNITUDE:
836 		if (com.magOffset > 0.0 || com.seq.reference_star >= 0)
837 			ylabel = _("Star magnitude (absolute)");
838 		else
839 			ylabel = _("Star magnitude (relative, use setmag)");
840 		break;
841 	case BACKGROUND:
842 		ylabel = _("Background value");
843 		break;
844 	case X_POSITION:
845 		ylabel = _("Star position on X axis");
846 		break;
847 	case Y_POSITION:
848 		ylabel = _("Star position on Y axis");
849 		break;
850 	}
851 }
852 
notify_new_photometry()853 void notify_new_photometry() {
854 	control_window_switch_to_tab(PLOT);
855 	requires_color_update = TRUE;
856 	gtk_widget_set_visible(sourceCombo, TRUE);
857 	gtk_combo_box_set_active(GTK_COMBO_BOX(sourceCombo), 1);
858 	gtk_widget_set_sensitive(buttonClearLatest, TRUE);
859 	gtk_widget_set_sensitive(buttonClearAll, TRUE);
860 }
861 
set_colors(struct kplotcfg * cfg)862 static void set_colors(struct kplotcfg *cfg) {
863 	int i;
864 	cfg->clrsz = MAX_SEQPSF;
865 	cfg->clrs = calloc(cfg->clrsz, sizeof(struct kplotccfg));
866 	for (i = 0; i < cfg->clrsz; i++) {
867 		cfg->clrs[i].type = KPLOTCTYPE_RGBA;
868 		cfg->clrs[i].rgba[3] = 1.0;
869 	}
870 	cfg->clrs[0].rgba[0] = 0x94 / 255.0;
871 	cfg->clrs[0].rgba[1] = 0x04 / 255.0;
872 	cfg->clrs[0].rgba[2] = 0xd3 / 255.0;
873 	cfg->clrs[1].rgba[0] = 0x00 / 255.0;
874 	cfg->clrs[1].rgba[1] = 0x9e / 255.0;
875 	cfg->clrs[1].rgba[2] = 0x73 / 255.0;
876 	cfg->clrs[2].rgba[0] = 0x56 / 255.0;
877 	cfg->clrs[2].rgba[1] = 0xb4 / 255.0;
878 	cfg->clrs[2].rgba[2] = 0xe9 / 255.0;
879 	cfg->clrs[3].rgba[0] = 0xe6 / 255.0;
880 	cfg->clrs[3].rgba[1] = 0x9f / 255.0;
881 	cfg->clrs[3].rgba[2] = 0x00 / 255.0;
882 	cfg->clrs[4].rgba[0] = 0xf0 / 255.0;
883 	cfg->clrs[4].rgba[1] = 0xe4 / 255.0;
884 	cfg->clrs[4].rgba[2] = 0x42 / 255.0;
885 	cfg->clrs[5].rgba[0] = 0x00 / 255.0;
886 	cfg->clrs[5].rgba[1] = 0x72 / 255.0;
887 	cfg->clrs[5].rgba[2] = 0xb2 / 255.0;
888 	cfg->clrs[6].rgba[0] = 0xe5 / 255.0;
889 	cfg->clrs[6].rgba[1] = 0x1e / 255.0;
890 	cfg->clrs[6].rgba[2] = 0x10 / 255.0;
891 }
892 
free_colors(struct kplotcfg * cfg)893 static void free_colors(struct kplotcfg *cfg) {
894 	free(cfg->clrs);
895 }
896 
get_index_of_frame(gdouble x,gdouble y)897 static int get_index_of_frame(gdouble x, gdouble y) {
898 	if (!plot_data) return -1;
899 	if (!com.seq.imgparam) return -1;
900 
901 	pldata *plot = plot_data;
902 
903 	point min = { plot->frame[0], plot->data[0].y };
904 	/* ensure to correctly find max frame index */
905 	int i = 0;
906 	while (plot->frame[com.seq.selnum - 1 - i] == 0.0) {
907 		i++;
908 	}
909 	point max = { plot->frame[com.seq.selnum - 1 - i], plot->data[com.seq.selnum - 1 - i].y };
910 	point intervale = { get_dimx() / (max.x - min.x), get_dimy() / (max.y - min.y)};
911 	point pos = { x - get_offsx(), get_dimy() - y + get_offsy() };
912 
913 	int index = (int) round(pos.x / intervale.x) + (int) min.x - 1;
914 	if (index >= 0 && index <= max.x && com.seq.imgparam[index].incl) {
915 		return index;
916 	}
917 	return -1;
918 }
919 
on_DrawingPlot_motion_notify_event(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)920 gboolean on_DrawingPlot_motion_notify_event(GtkWidget *widget,
921 		GdkEventMotion *event, gpointer user_data) {
922 
923 	gtk_widget_set_has_tooltip(widget, FALSE);
924 
925 	int index = get_index_of_frame(event->x, event->y);
926 	if (index >= 0) {
927 		gchar *tooltip_text = g_strdup_printf("Frame: %d", (index + 1));
928 		gtk_widget_set_tooltip_text(widget, tooltip_text);
929 		return TRUE;
930 	}
931 	return FALSE;
932 }
933 
on_DrawingPlot_enter_notify_event(GtkWidget * widget,GdkEvent * event,gpointer user_data)934 gboolean on_DrawingPlot_enter_notify_event(GtkWidget *widget, GdkEvent *event,
935 		gpointer user_data) {
936 	if (plot_data) {
937 		set_cursor("tcross");
938 	}
939 	return TRUE;
940 }
941 
on_DrawingPlot_leave_notify_event(GtkWidget * widget,GdkEvent * event,gpointer user_data)942 gboolean on_DrawingPlot_leave_notify_event(GtkWidget *widget, GdkEvent *event,
943 		gpointer user_data) {
944 	if (get_thread_run()) {
945 		set_cursor_waiting(TRUE);
946 	} else {
947 		/* trick to get default cursor */
948 		set_cursor_waiting(FALSE);
949 	}
950 	return TRUE;
951 }
952 
do_popup_plotmenu(GtkWidget * my_widget,GdkEventButton * event)953 static void do_popup_plotmenu(GtkWidget *my_widget, GdkEventButton *event) {
954 	static GtkMenu *menu = NULL;
955 	static GtkMenuItem *menu_item = NULL;
956 
957 	int index = get_index_of_frame(event->x, event->y);
958 	if (index < 0) return;
959 
960 	if (!menu) {
961 		menu = GTK_MENU(lookup_widget("menu_plot"));
962 		gtk_menu_attach_to_widget(GTK_MENU(menu), my_widget, NULL);
963 		menu_item = GTK_MENU_ITEM(lookup_widget("menu_plot_exclude"));
964 	}
965 
966 #if GTK_CHECK_VERSION(3, 22, 0)
967 	gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
968 #else
969 	int button, event_time;
970 
971 	if (event) {
972 		button = event->button;
973 		event_time = event->time;
974 	} else {
975 		button = 0;
976 		event_time = gtk_get_current_event_time();
977 	}
978 
979 	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, button,
980 			event_time);
981 #endif
982 	gchar *str = g_strdup_printf(_("Exclude Frame %d"), index + 1);
983 	gtk_menu_item_set_label(menu_item, str);
984 
985 	g_free(str);
986 }
987 
on_DrawingPlot_button_press_event(GtkWidget * widget,GdkEventButton * event,gpointer user_data)988 gboolean on_DrawingPlot_button_press_event(GtkWidget *widget,
989 	GdkEventButton *event, gpointer user_data) {
990 	do_popup_plotmenu(widget, event);
991 	return TRUE;
992 }
993 
extract_int_from_label(const gchar * str)994 static signed long extract_int_from_label(const gchar *str) {
995 	gchar *p = (gchar *)str;
996 	while (*p) {
997 		if (g_ascii_isdigit(*p) || ((*p == '-' || *p == '+') && g_ascii_isdigit(*(p + 1)))) {
998 	        // Found a number
999 	        return g_ascii_strtoll(p, &p, 10); // return number
1000 	    } else {
1001 	        // Otherwise, move on to the next character.
1002 	        p++;
1003 	    }
1004 	}
1005 	return -1;
1006 }
1007 
on_menu_plot_exclude_activate(GtkMenuItem * menuitem,gpointer user_data)1008 void on_menu_plot_exclude_activate(GtkMenuItem *menuitem, gpointer user_data) {
1009 	const gchar *label = gtk_menu_item_get_label(menuitem);
1010 	gint index;
1011 
1012 	index = extract_int_from_label(label);
1013 	if (index > 0) {
1014 		index--;
1015 
1016 		exclude_single_frame(index);
1017 		update_seqlist();
1018 	}
1019 }
1020