aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas White <taw@physics.org>2022-11-22 17:23:50 +0100
committerThomas White <taw@physics.org>2022-11-24 15:29:05 +0100
commitfb3b7046644010bb3511e69f622081bbbc5b76db (patch)
treebe05dfc5fdad3334d55816e0c77a99eadb824f8a
parent4ee6229c44a12deba76242546d424067483e89c6 (diff)
GUI: Colour scale, part 1: Basic image histogram
-rw-r--r--CMakeLists.txt3
-rw-r--r--meson.build1
-rw-r--r--src/crystfel_gui.c14
-rw-r--r--src/crystfelcolourscale.c309
-rw-r--r--src/crystfelcolourscale.h87
-rw-r--r--src/gui_project.h1
6 files changed, 412 insertions, 3 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d7d09df4..88cc9884 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -420,7 +420,8 @@ if (GTK_FOUND)
src/gui_project.c src/crystfelindexingopts.c src/crystfelmergeopts.c
src/crystfelsymmetryselector.c src/gtk-util-routines.c src/gui_fom.c
src/gui_export.c src/gui_ambi.c src/gui_import.c
- src/gtkmultifilechooserbutton.c src/gui_backend_slurm.c)
+ src/gtkmultifilechooserbutton.c src/gui_backend_slurm.c
+ src/crystfelcolourscale.c)
add_executable(crystfel ${CRYSTFEL_GUI_SOURCES}
${CMAKE_CURRENT_BINARY_DIR}/version.c
diff --git a/meson.build b/meson.build
index bc4ccebb..2ba4e54d 100644
--- a/meson.build
+++ b/meson.build
@@ -209,6 +209,7 @@ if gtkdep.found()
'src/crystfelindexingopts.c',
'src/crystfelmergeopts.c',
'src/crystfelsymmetryselector.c',
+ 'src/crystfelcolourscale.c',
'src/gtk-util-routines.c',
'src/gui_import.c',
'src/gui_peaksearch.c',
diff --git a/src/crystfel_gui.c b/src/crystfel_gui.c
index b13b9b0a..4ac9fd86 100644
--- a/src/crystfel_gui.c
+++ b/src/crystfel_gui.c
@@ -48,7 +48,7 @@
#include <cell-utils.h>
#include "crystfelimageview.h"
-#include "crystfelimageview.h"
+#include "crystfelcolourscale.h"
#include "crystfel_gui.h"
#include "gui_import.h"
#include "gui_peaksearch.h"
@@ -280,6 +280,9 @@ void update_imageview(struct crystfelproject *proj)
crystfel_image_view_set_image(CRYSTFEL_IMAGE_VIEW(proj->imageview),
proj->cur_image);
+ crystfel_colour_scale_scan_image(CRYSTFEL_COLOUR_SCALE(proj->colscale),
+ proj->cur_image);
+
gtk_widget_set_sensitive(proj->next_button,
!(proj->cur_frame == proj->n_frames-1));
gtk_widget_set_sensitive(proj->last_button,
@@ -1034,6 +1037,7 @@ int main(int argc, char *argv[])
GtkWidget *scroll;
GtkWidget *frame;
GtkWidget *main_vbox;
+ GtkWidget *iv_hbox;
GtkWidget *toolbar;
GtkWidget *results_toolbar;
GtkWidget *button;
@@ -1187,15 +1191,21 @@ int main(int argc, char *argv[])
proj.cur_frame = 0;
frame = gtk_frame_new(NULL);
gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
+
+ iv_hbox = gtk_hbox_new(FALSE, 0.0);
scroll = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
GTK_POLICY_ALWAYS, GTK_POLICY_ALWAYS);
gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(proj.imageview));
- gtk_box_pack_start(GTK_BOX(main_vbox), scroll, TRUE, TRUE, 0.0);
+ gtk_box_pack_start(GTK_BOX(main_vbox), iv_hbox, TRUE, TRUE, 0.0);
+ gtk_box_pack_start(GTK_BOX(iv_hbox), scroll, TRUE, TRUE, 0.0);
gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(main_vbox));
gtk_paned_pack2(GTK_PANED(hpaned), GTK_WIDGET(frame), TRUE, TRUE);
proj.main_vbox = main_vbox;
+ proj.colscale = crystfel_colour_scale_new();
+ gtk_box_pack_start(GTK_BOX(iv_hbox), proj.colscale, FALSE, FALSE, 0.0);
+
/* Icon region at left */
proj.icons = gtk_vbox_new(FALSE, 0.0);
scroll = gtk_scrolled_window_new(NULL, NULL);
diff --git a/src/crystfelcolourscale.c b/src/crystfelcolourscale.c
new file mode 100644
index 00000000..b1d009a8
--- /dev/null
+++ b/src/crystfelcolourscale.c
@@ -0,0 +1,309 @@
+/*
+ * crystfelcolourscale.c
+ *
+ * CrystFEL's colour scale widget
+ *
+ * Copyright © 2020-2022 Deutsches Elektronen-Synchrotron DESY,
+ * a research centre of the Helmholtz Association.
+ *
+ * Authors:
+ * 2020-2022 Thomas White <taw@physics.org>
+ *
+ * This file is part of CrystFEL.
+ *
+ * CrystFEL is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * CrystFEL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CrystFEL. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <gtk/gtk.h>
+#include <glib-object.h>
+#include <gsl/gsl_statistics_float.h>
+
+#include "crystfelcolourscale.h"
+
+
+G_DEFINE_TYPE(CrystFELColourScale, crystfel_colour_scale,
+ GTK_TYPE_DRAWING_AREA)
+
+static void redraw(CrystFELColourScale *cs)
+{
+ gint w, h;
+ w = gtk_widget_get_allocated_width(GTK_WIDGET(cs));
+ h = gtk_widget_get_allocated_height(GTK_WIDGET(cs));
+ gtk_widget_queue_draw_area(GTK_WIDGET(cs), 0, 0, w, h);
+}
+
+
+static gint destroy_sig(GtkWidget *window, CrystFELColourScale *cs)
+{
+ return FALSE;
+}
+
+
+static gint realise_sig(GtkWidget *window, CrystFELColourScale *cs)
+{
+ return FALSE;
+}
+
+
+static gint button_press_sig(GtkWidget *window, GdkEventButton *event,
+ CrystFELColourScale *cs)
+{
+ cs->drag_start_x = event->x;
+ cs->drag_start_y = event->y;
+ return FALSE;
+}
+
+
+static gint motion_sig(GtkWidget *window, GdkEventMotion *event,
+ CrystFELColourScale *cs)
+{
+ double ddx, ddy;
+ ddx = event->x - cs->drag_start_x;
+ ddy = event->y - cs->drag_start_y;
+ /* FIXME: Do something */
+ redraw(cs);
+ return FALSE;
+}
+
+
+static gint configure_sig(GtkWidget *window, GdkEventConfigure *rec,
+ CrystFELColourScale *cs)
+{
+ cs->visible_width = rec->width;
+ cs->visible_height = rec->height;
+ return FALSE;
+}
+
+
+static gint draw_sig(GtkWidget *window, cairo_t *cr, CrystFELColourScale *cs)
+{
+ int i;
+ int mx = 0;
+ double max_w = cs->visible_width;
+ double bin_h = cs->visible_height/COLSCALE_N_BINS;
+
+ cairo_save(cr);
+
+ /* Overall background */
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ cairo_paint(cr);
+
+ for ( i=0; i<COLSCALE_N_BINS; i++ ) {
+ if ( cs->bins[i] > mx ) mx = cs->bins[i];
+ }
+
+ /* Origin at bottom right */
+ cairo_translate(cr, cs->visible_width, cs->visible_height);
+ cairo_scale(cr, -1.0, -1.0);
+
+ for ( i=0; i<COLSCALE_N_BINS; i++ ) {
+ cairo_rectangle(cr, 0.0, bin_h*i, max_w*cs->bins[i]/mx, bin_h);
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_fill(cr);
+ }
+
+ cairo_restore(cr);
+
+ return FALSE;
+}
+
+
+static GtkSizeRequestMode get_request_mode(GtkWidget *widget)
+{
+ return GTK_SIZE_REQUEST_CONSTANT_SIZE;
+}
+
+
+static void get_preferred_width(GtkWidget *widget, gint *min, gint *natural)
+{
+ *min = 0;
+ *natural = 40;
+}
+
+
+static void get_preferred_height(GtkWidget *widget, gint *min, gint *natural)
+{
+ *min = 0;
+ *natural = 640;
+}
+
+
+static void crystfel_colour_scale_class_init(CrystFELColourScaleClass *klass)
+{
+ GTK_WIDGET_CLASS(klass)->get_request_mode = get_request_mode;
+ GTK_WIDGET_CLASS(klass)->get_preferred_width = get_preferred_width;
+ GTK_WIDGET_CLASS(klass)->get_preferred_height = get_preferred_height;
+ GTK_WIDGET_CLASS(klass)->get_preferred_height_for_width = NULL;
+}
+
+
+static void crystfel_colour_scale_init(CrystFELColourScale *cs)
+{
+}
+
+
+GtkWidget *crystfel_colour_scale_new()
+{
+ CrystFELColourScale *cs;
+
+ cs = g_object_new(CRYSTFEL_TYPE_COLOUR_SCALE, NULL);
+
+ g_signal_connect(G_OBJECT(cs), "destroy",
+ G_CALLBACK(destroy_sig), cs);
+ g_signal_connect(G_OBJECT(cs), "realize",
+ G_CALLBACK(realise_sig), cs);
+ g_signal_connect(G_OBJECT(cs), "button-press-event",
+ G_CALLBACK(button_press_sig), cs);
+ g_signal_connect(G_OBJECT(cs), "motion-notify-event",
+ G_CALLBACK(motion_sig), cs);
+ g_signal_connect(G_OBJECT(cs), "configure-event",
+ G_CALLBACK(configure_sig), cs);
+ g_signal_connect(G_OBJECT(cs), "draw",
+ G_CALLBACK(draw_sig), cs);
+
+ gtk_widget_set_can_focus(GTK_WIDGET(cs), TRUE);
+ gtk_widget_add_events(GTK_WIDGET(cs),
+ GDK_POINTER_MOTION_HINT_MASK
+ | GDK_BUTTON1_MOTION_MASK
+ | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
+ | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
+
+ gtk_widget_grab_focus(GTK_WIDGET(cs));
+
+ gtk_widget_show(GTK_WIDGET(cs));
+
+ return GTK_WIDGET(cs);
+}
+
+
+static double auto_scale_top(const struct image *image)
+{
+ int pn;
+ double total_mean = 0.0;
+ double total_variance = 0.0;
+
+ for ( pn=0; pn<image->detgeom->n_panels; pn++ ) {
+
+ long int i, j;
+ int w, h;
+ float *data;
+ float this_mean;
+
+ w = image->detgeom->panels[pn].w;
+ h = image->detgeom->panels[pn].h;
+
+ data = malloc(w*h*sizeof(float));
+ if ( data == NULL ) return 100.0;
+
+ j = 0;
+ for ( i=0; i<w*h; i++ ) {
+ if ( !image->bad[pn][i] ) {
+ data[j++] = image->dp[pn][i];
+ }
+ }
+
+ this_mean = gsl_stats_float_mean(data, 1, j);
+
+ total_mean += this_mean;
+ total_variance += gsl_stats_float_variance_m(data, 1, j,
+ this_mean);
+
+ free(data);
+ }
+
+ return (total_mean/image->detgeom->n_panels)
+ + 10.0*sqrt(total_variance/image->detgeom->n_panels);
+}
+
+
+void image_min_max(struct image *image, double *pmin, double *pmax)
+{
+ int pn;
+ for ( pn=0; pn<image->detgeom->n_panels; pn++ ) {
+ int w, h;
+ long int i;
+ w = image->detgeom->panels[pn].w;
+ h = image->detgeom->panels[pn].h;
+ for ( i=0; i<w*h; i++ ) {
+ if ( !image->bad[pn][i] ) {
+ double v = image->dp[pn][i];
+ *pmin = fmin(v, *pmin);
+ *pmax = fmax(v, *pmax);
+ }
+ }
+ }
+}
+
+
+void histogram_image(struct image *image,
+ int *bins, int n_bins,
+ double min, double max)
+{
+ int pn;
+ for ( pn=0; pn<image->detgeom->n_panels; pn++ ) {
+ int w, h;
+ long int i;
+ w = image->detgeom->panels[pn].w;
+ h = image->detgeom->panels[pn].h;
+ for ( i=0; i<w*h; i++ ) {
+ if ( !image->bad[pn][i] ) {
+ int bin;
+ double v = image->dp[pn][i];
+ bin = n_bins*(v-min)/(max-min);
+ if ( bin < 0 ) bin = 0;
+ if ( bin >= n_bins ) bin = n_bins-1;
+ bins[bin]++;
+ }
+ }
+ }
+}
+
+
+void crystfel_colour_scale_scan_image(CrystFELColourScale *cs,
+ struct image *image)
+{
+ double range_min, range_max;
+ int i;
+ int n_filled = 0;
+
+ if ( image == NULL ) return;
+
+ image_min_max(image, &range_min, &range_max);
+
+ for ( i=0; i<COLSCALE_N_BINS; i++ ) {
+ cs->bins[i] = 0;
+ }
+
+ histogram_image(image, cs->bins, COLSCALE_N_BINS, range_min, range_max);
+
+ for ( i=0; i<COLSCALE_N_BINS; i++ ) {
+ if ( cs->bins[i] > 0 ) n_filled++;
+ }
+
+ if ( n_filled < 3 ) {
+ ERROR("WARNING: Suspicious pixel value distribution - "
+ "are there still some bad pixels to mask?\n");
+ }
+
+ redraw(cs);
+}
diff --git a/src/crystfelcolourscale.h b/src/crystfelcolourscale.h
new file mode 100644
index 00000000..128db6d8
--- /dev/null
+++ b/src/crystfelcolourscale.h
@@ -0,0 +1,87 @@
+/*
+ * crystfelcolourscale.h
+ *
+ * CrystFEL's colour scale widget
+ *
+ * Copyright © 2020-2022 Deutsches Elektronen-Synchrotron DESY,
+ * a research centre of the Helmholtz Association.
+ *
+ * Authors:
+ * 2020-2022 Thomas White <taw@physics.org>
+ *
+ * This file is part of CrystFEL.
+ *
+ * CrystFEL is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * CrystFEL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CrystFEL. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CRYSTFELCOLOURSCALE_H
+#define CRYSTFELCOLOURSCALE_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "image.h"
+
+#define CRYSTFEL_TYPE_COLOUR_SCALE (crystfel_colour_scale_get_type())
+
+#define CRYSTFEL_COLOUR_SCALE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ CRYSTFEL_TYPE_COLOUR_SCALE, CrystFELColourScale))
+
+#define CRYSTFEL_IS_COLOUR_SCALE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+ CRYSTFEL_TYPE_COLOUR_SCALE))
+
+#define CRYSTFEL_COLOUR_SCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((obj), \
+ CRYSTFEL_TYPE_COLOUR_SCALE, CrystFELColourScale))
+
+#define CRYSTFEL_IS_COLOUR_SCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((obj), \
+ CRYSTFEL_TYPE_COLOUR_SCALE))
+
+#define CRYSTFEL_COLOUR_SCALE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+ CRYSTFEL_TYPE_COLOUR_SCALE, CrystFELColourScale))
+
+
+#define COLSCALE_N_BINS (256)
+
+struct _crystfelcolourscale
+{
+ GtkDrawingArea parent_instance;
+ double visible_width;
+ double visible_height;
+ double drag_start_x;
+ double drag_start_y;
+
+ int bins[COLSCALE_N_BINS];
+};
+
+struct _crystfelcolourscaleclass
+{
+ GtkDrawingAreaClass parent_class;
+};
+
+typedef struct _crystfelcolourscale CrystFELColourScale;
+typedef struct _crystfelcolourscaleclass CrystFELColourScaleClass;
+
+extern GType crystfel_colour_scale_get_type(void);
+extern GtkWidget *crystfel_colour_scale_new(void);
+
+extern void crystfel_colour_scale_scan_image(CrystFELColourScale *cs,
+ struct image *image);
+
+extern void crystfel_colour_scale_get_range(CrystFELColourScale *cs,
+ double scale_min,
+ double scale_max);
+
+#endif /* CRYSTFELCOLOURSCALE_H */
diff --git a/src/gui_project.h b/src/gui_project.h
index 546bae9a..75524a81 100644
--- a/src/gui_project.h
+++ b/src/gui_project.h
@@ -274,6 +274,7 @@ struct crystfelproject {
GtkActionGroup *action_group;
GtkWidget *imageview;
+ GtkWidget *colscale;
GtkWidget *icons; /* Drawing area for task icons */
GtkWidget *report; /* Text view at the bottom for messages */
GtkWidget *main_vbox;