diff options
author | Thomas White <taw@physics.org> | 2022-11-22 17:23:50 +0100 |
---|---|---|
committer | Thomas White <taw@physics.org> | 2022-11-24 15:29:05 +0100 |
commit | fb3b7046644010bb3511e69f622081bbbc5b76db (patch) | |
tree | be05dfc5fdad3334d55816e0c77a99eadb824f8a /src | |
parent | 4ee6229c44a12deba76242546d424067483e89c6 (diff) |
GUI: Colour scale, part 1: Basic image histogram
Diffstat (limited to 'src')
-rw-r--r-- | src/crystfel_gui.c | 14 | ||||
-rw-r--r-- | src/crystfelcolourscale.c | 309 | ||||
-rw-r--r-- | src/crystfelcolourscale.h | 87 | ||||
-rw-r--r-- | src/gui_project.h | 1 |
4 files changed, 409 insertions, 2 deletions
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; |