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