diff options
Diffstat (limited to 'src/crystfelimageview.c')
-rw-r--r-- | src/crystfelimageview.c | 1061 |
1 files changed, 1061 insertions, 0 deletions
diff --git a/src/crystfelimageview.c b/src/crystfelimageview.c new file mode 100644 index 00000000..d5c0b51f --- /dev/null +++ b/src/crystfelimageview.c @@ -0,0 +1,1061 @@ +/* + * crystfelimageview.c + * + * CrystFEL's image viewer widget + * + * Copyright © 2020-2021 Deutsches Elektronen-Synchrotron DESY, + * a research centre of the Helmholtz Association. + * + * Authors: + * 2020-2021 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 <utils.h> +#include <detgeom.h> +#include <colscale.h> + +#include "crystfelimageview.h" + + +static int rerender_image(CrystFELImageView *iv); + + +static void scroll_interface_init(GtkScrollable *iface) +{ +} + + +enum +{ + CRYSTFELIMAGEVIEW_0, + CRYSTFELIMAGEVIEW_VADJ, + CRYSTFELIMAGEVIEW_HADJ, + CRYSTFELIMAGEVIEW_VPOL, + CRYSTFELIMAGEVIEW_HPOL, +}; + + +G_DEFINE_TYPE_WITH_CODE(CrystFELImageView, crystfel_image_view, + GTK_TYPE_DRAWING_AREA, + G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, + scroll_interface_init)) + +static void redraw(CrystFELImageView *iv) +{ + gint w, h; + w = gtk_widget_get_allocated_width(GTK_WIDGET(iv)); + h = gtk_widget_get_allocated_height(GTK_WIDGET(iv)); + gtk_widget_queue_draw_area(GTK_WIDGET(iv), 0, 0, w, h); +} + + +static void cleanup_image(CrystFELImageView *iv) +{ + if ( iv->pixbufs != NULL ) { + int i; + for ( i=0; i<iv->image->detgeom->n_panels; i++ ) { + if ( iv->pixbufs[i] != NULL ) { + gdk_pixbuf_unref(iv->pixbufs[i]); + } + } + } + free(iv->pixbufs); + + iv->image = NULL; + iv->pixbufs = NULL; +} + + +static gint destroy_sig(GtkWidget *window, CrystFELImageView *iv) +{ + cleanup_image(iv); + return FALSE; +} + + +static gint realise_sig(GtkWidget *window, CrystFELImageView *iv) +{ + return FALSE; +} + + +static void configure_scroll_adjustments(CrystFELImageView *iv) +{ + if ( iv->hadj != NULL ) { + double pos = gtk_adjustment_get_value(iv->hadj); + double vis_size = iv->visible_width / iv->zoom; + gtk_adjustment_configure(iv->hadj, pos, + 0.0, iv->detector_w, + 0.0001, 0.1, vis_size); + } + if ( iv->vadj != NULL ) { + double pos = gtk_adjustment_get_value(iv->vadj); + double vis_size = iv->visible_height / iv->zoom; + gtk_adjustment_configure(iv->vadj, pos, + 0.0, iv->detector_h, + 0.0001, 0.1, vis_size); + } +} + + +static gint scroll_sig(GtkWidget *window, GdkEventScroll *event, + CrystFELImageView *iv) +{ + double zoom_scale, ratio; + int claim = FALSE; + int zoom_allowed = 1; + + if ( event->direction == GDK_SCROLL_UP ) { + zoom_scale = 1.1; + claim = TRUE; + } + if ( event->direction == GDK_SCROLL_DOWN ) { + zoom_scale = 0.9; + claim = TRUE; + } + if ( event->direction == GDK_SCROLL_LEFT ) return TRUE; + if ( event->direction == GDK_SCROLL_RIGHT ) return TRUE; + + ratio = iv->zoom / iv->image->detgeom->panels[0].pixel_pitch; + + if ( (ratio < 5e6) && (zoom_scale < 1.0) ) zoom_allowed = 0; + if ( (ratio > 1e10) && (zoom_scale > 1.0) ) zoom_allowed = 0; + + if ( claim && zoom_allowed ) { + + double shift_x, shift_y; + double scr_x, scr_y; + + scr_x = gtk_adjustment_get_value(iv->hadj); + scr_y = gtk_adjustment_get_value(iv->vadj); + + shift_x = event->x*((1.0/(zoom_scale*iv->zoom))-(1.0/iv->zoom)); + shift_y = event->y*((1.0/(zoom_scale*iv->zoom))-(1.0/iv->zoom)); + iv->zoom *= zoom_scale; + + configure_scroll_adjustments(iv); + + gtk_adjustment_set_value(iv->hadj, scr_x - shift_x); + gtk_adjustment_set_value(iv->vadj, scr_y - shift_y); + + redraw(iv); + + } + + return claim; +} + + +static gint button_press_sig(GtkWidget *window, GdkEventButton *event, + CrystFELImageView *iv) +{ + iv->drag_start_x = event->x; + iv->drag_start_y = event->y; + iv->drag_start_sp_x = gtk_adjustment_get_value(iv->hadj); + iv->drag_start_sp_y = gtk_adjustment_get_value(iv->vadj); + return FALSE; +} + + +static gint motion_sig(GtkWidget *window, GdkEventMotion *event, + CrystFELImageView *iv) +{ + double ddx, ddy; + ddx = event->x - iv->drag_start_x; + ddy = event->y - iv->drag_start_y; + gtk_adjustment_set_value(iv->hadj, iv->drag_start_sp_x - ddx/iv->zoom); + gtk_adjustment_set_value(iv->vadj, iv->drag_start_sp_y - ddy/iv->zoom); + redraw(iv); + return FALSE; +} + + +static gint configure_sig(GtkWidget *window, GdkEventConfigure *rec, + CrystFELImageView *iv) +{ + iv->visible_width = rec->width; + iv->visible_height = rec->height; + configure_scroll_adjustments(iv); + return FALSE; +} + + +static int clamp(double val, int min, int max) +{ + if ( val < min ) return min; + if ( val > max ) return max; + return val; +} + + +static void swap(int *a, int *b) +{ + int tmp = *a; + *a = *b; + *b = tmp; +} + + +static void draw_pixel_values(cairo_t *cr, + double imin_fs, double imin_ss, + double imax_fs, double imax_ss, + struct detgeom_panel p, float *dp, + int *bad) +{ + int min_fs, max_fs, min_ss, max_ss; + int fs, ss; + PangoLayout *layout; + PangoFontDescription *fontdesc; + double w, h; + + /* FIXME: This is wrong for slanty pixels */ + min_fs = clamp(imin_fs, 0, p.w-1); + min_ss = clamp(imin_ss, 0, p.h-1); + max_fs = clamp(imax_fs, 0, p.w-1); + max_ss = clamp(imax_ss, 0, p.h-1); + if ( min_ss > max_ss ) swap(&min_ss, &max_ss); + if ( min_fs > max_fs ) swap(&min_fs, &max_fs); + + layout = pango_cairo_create_layout(cr); + fontdesc = pango_font_description_from_string("Sans 10"); + pango_layout_set_font_description(layout, fontdesc); + + w = 1.0; + h = 1.0; + cairo_device_to_user_distance(cr, &w, &h); + + for ( fs=min_fs; fs<=max_fs; fs++ ) { + for ( ss=min_ss; ss<=max_ss; ss++ ) { + + double x, y; + double fsd, ssd; + char tmp[64]; + PangoRectangle rec; + double rw, rh; + const char *b1; + const char *b2; + + fsd = fs + 0.5; + ssd = ss + 0.5; + + x = (fsd*p.fsx + ssd*p.ssx + p.cnx)*p.pixel_pitch; + y = (fsd*p.fsy + ssd*p.ssy + p.cny)*p.pixel_pitch; + + if ( bad[fs+ss*p.w] ) { + b1 = "("; + b2 = ")"; + } else { + b1 = ""; + b2 = ""; + } + + snprintf(tmp, 63, "%s%.f%s", b1, dp[fs+ss*p.w], b2); + pango_layout_set_text(layout, tmp, -1); + + cairo_save(cr); + + cairo_translate(cr, x, y); + cairo_scale(cr, w, h); + pango_cairo_update_layout(cr, layout); + pango_layout_get_extents(layout, NULL, &rec); + rw = pango_units_to_double(rec.width); + rh = pango_units_to_double(rec.height); + + cairo_rectangle(cr, -rw/2.0, -rh/2.0, rw, rh); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_fill(cr); + + cairo_move_to(cr, -rw/2.0, -rh/2.0); + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + pango_cairo_show_layout(cr, layout); + + cairo_restore(cr); + + } + } + + pango_font_description_free(fontdesc); + g_object_unref(layout); + + cairo_arc(cr, p.cnx*p.pixel_pitch, p.cny*p.pixel_pitch, 0.00002, + 0, 2.0*M_PI); + cairo_set_source_rgba(cr, 0.0, 1.0, 1.0, 1.0); + cairo_fill(cr); +} + + +static void draw_panel_rectangle(cairo_t *cr, CrystFELImageView *iv, + int i, cairo_matrix_t *gtkmatrix) +{ + struct detgeom_panel p = iv->image->detgeom->panels[i]; + cairo_matrix_t m; + cairo_pattern_t *patt; + double min_x, min_y, max_x, max_y; + double xs, ys, pixel_size_on_screen; + int have_pixels = 1; + + cairo_save(cr); + + /* Move to the right location */ + cairo_translate(cr, p.cnx*p.pixel_pitch, p.cny*p.pixel_pitch); + + /* Twiddle directions according to matrix */ + cairo_matrix_init(&m, p.fsx, p.fsy, p.ssx, p.ssy, + 0.0, 0.0); + cairo_transform(cr, &m); + + gdk_cairo_set_source_pixbuf(cr, iv->pixbufs[i], 0.0, 0.0); + patt = cairo_get_source(cr); + + cairo_pattern_get_matrix(patt, &m); + cairo_matrix_scale(&m, 1.0/p.pixel_pitch, 1.0/p.pixel_pitch); + cairo_pattern_set_matrix(patt, &m); + + cairo_pattern_set_filter(patt, CAIRO_FILTER_NEAREST); + + cairo_rectangle(cr, 0.0, 0.0, p.w*p.pixel_pitch, p.h*p.pixel_pitch); + cairo_fill_preserve(cr); + cairo_set_line_width(cr, 0.00001); + cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); + cairo_stroke(cr); + + /* Are any pixels from this panel visible? */ + min_x = 0.0; + min_y = 0.0; + max_x = iv->visible_width; + max_y = iv->visible_height; + + /* Take into account the transformation which GTK already + * has in effect on the widget (translation from parent + * window's origin). */ + cairo_matrix_transform_point(gtkmatrix, &min_x, &min_y); + cairo_matrix_transform_point(gtkmatrix, &max_x, &max_y); + cairo_device_to_user(cr, &min_x, &min_y); + cairo_device_to_user(cr, &max_x, &max_y); + + min_x /= p.pixel_pitch; + min_y /= p.pixel_pitch; + max_x /= p.pixel_pitch; + max_y /= p.pixel_pitch; + if ( (min_x < 0.0) && (max_x < 0.0) ) have_pixels = 0; + if ( (min_y < 0.0) && (max_y < 0.0) ) have_pixels = 0; + if ( (min_x > p.w) && (max_x > p.w) ) have_pixels = 0; + if ( (min_y > p.h) && (max_y > p.h) ) have_pixels = 0; + + cairo_restore(cr); + + cairo_arc(cr, 0.0, 0.0, 0.006, 0, 2.0*M_PI); + cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); + cairo_set_line_width(cr, 0.00001); + cairo_stroke(cr); + + xs = p.pixel_pitch; + ys = p.pixel_pitch; + cairo_user_to_device_distance(cr, &xs, &ys); + pixel_size_on_screen = smallest(fabs(xs), fabs(ys)); + if ( (pixel_size_on_screen > 40.0) && have_pixels ) { + draw_pixel_values(cr, min_x, min_y, max_x, max_y, p, + iv->image->dp[i], iv->image->bad[i]); + } +} + + +static void draw_peaks(cairo_t *cr, CrystFELImageView *iv, + ImageFeatureList *pks) +{ + int i, n_pks; + double bs, lw; + + bs = 5.0; /* Box size in pixels on screen */ + lw = 1.0; /* Line width in pixels on screen */ + cairo_device_to_user_distance(cr, &bs, &lw); + bs = fabs(bs); + lw = fabs(lw); + + n_pks = image_feature_count(pks); + for ( i=0; i<n_pks; i++ ) { + + const struct imagefeature *f; + struct detgeom_panel *p; + double x, y; + double this_bs; + double this_lw; + int show_cen = 0; + + f = image_get_feature_const(pks, i); + if ( f == NULL ) continue; + p = &iv->image->detgeom->panels[f->pn]; + + this_lw = biggest(0.1*p->pixel_pitch, lw); + this_bs = biggest(iv->peak_box_size * p->pixel_pitch, + bs); + + if ( this_bs > bs ) { + show_cen = 1; + } + + x = p->pixel_pitch*(p->cnx + p->fsx*f->fs + p->ssx*f->ss); + y = p->pixel_pitch*(p->cny + p->fsy*f->fs + p->ssy*f->ss); + cairo_rectangle(cr, x-this_bs, y-this_bs, 2*this_bs, 2*this_bs); + cairo_set_line_width(cr, this_lw); + cairo_set_source_rgba(cr, 1.0, 1.0, 0.0, 0.9); + cairo_stroke(cr); + + if ( show_cen ) { + cairo_move_to(cr, x-0.2*p->pixel_pitch, y); + cairo_line_to(cr, x+0.2*p->pixel_pitch, y); + cairo_move_to(cr, x, y-0.2*p->pixel_pitch); + cairo_line_to(cr, x, y+0.2*p->pixel_pitch); + cairo_set_source_rgba(cr, 0.4, 0.4, 0.0, 0.9); + cairo_stroke(cr); + } + + } +} + + +static void draw_refls(cairo_t *cr, + CrystFELImageView *iv, + RefList *list, + int label_reflections, + double *colour) +{ + const Reflection *refl; + RefListIterator *iter; + double bs, lw; + + if ( list == NULL ) return; + + bs = 5.0; + lw = 1.0; + cairo_device_to_user_distance(cr, &bs, &lw); + bs = fabs(bs); + lw = fabs(lw); + + for ( refl = first_refl_const(list, &iter); + refl != NULL; + refl = next_refl_const(refl, iter) ) + { + struct detgeom_panel *p; + double fs, ss; + int pn; + double x, y; + float this_bs; + float this_lw; + int show_cen = 0; + + get_detector_pos(refl, &fs, &ss); + pn = get_panel_number(refl); + p = &iv->image->detgeom->panels[pn]; + + this_lw = biggest(0.1*p->pixel_pitch, lw); + this_bs = biggest(iv->refl_box_size * p->pixel_pitch, + bs); + + if ( this_bs > bs ) { + show_cen = 1; + } + + x = p->pixel_pitch*(p->cnx + p->fsx*fs + p->ssx*ss); + y = p->pixel_pitch*(p->cny + p->fsy*fs + p->ssy*ss); + + cairo_arc(cr, x, y, this_bs, 0, 2*M_PI); + cairo_set_line_width(cr, this_lw); + + if ( get_redundancy(refl) == 0 ) { + cairo_set_source_rgba(cr, 0.7, 0.0, 0.0, 0.9); + } else { + cairo_set_source_rgba(cr, + colour[0], + colour[1], + colour[2], + 0.9); + } + cairo_stroke(cr); + + if ( show_cen ) { + cairo_move_to(cr, x-0.2*p->pixel_pitch, y); + cairo_line_to(cr, x+0.2*p->pixel_pitch, y); + cairo_move_to(cr, x, y-0.2*p->pixel_pitch); + cairo_line_to(cr, x, y+0.2*p->pixel_pitch); + cairo_set_source_rgba(cr, + colour[0]/2.0, + colour[1]/2.0, + colour[2]/2.0, + 0.9); + cairo_stroke(cr); + } + + if ( label_reflections ) { + + signed int h, k, l; + char tmp[64]; + + get_indices(refl, &h, &k, &l); + snprintf(tmp, 64, "%i %i %i", h, k, l); + + cairo_save(cr); + cairo_new_path(cr); + cairo_move_to(cr, x, y); + cairo_set_source_rgba(cr, 0.0, 0.4, 0.0, 0.9); + cairo_set_font_size(cr, 11*p->pixel_pitch); + cairo_scale(cr, 1.0, -1.0); + cairo_show_text(cr, tmp); + cairo_fill(cr); + cairo_restore(cr); + + } + + + } +} + + +static double crystal_cols[][3] = +{ + {0.0, 1.0, 0.0}, /* bright green */ + {1.0, 1.0, 0.0}, /* bright yellow */ + {0.0, 0.8, 0.8}, /* cyan */ + {1.0, 1.0, 1.0}, /* white */ +}; + +static int n_crystal_cols = 4; + + +static gint draw_sig(GtkWidget *window, cairo_t *cr, CrystFELImageView *iv) +{ + cairo_matrix_t m; + + if ( iv->image == NULL ) return FALSE; + if ( iv->need_rerender ) rerender_image(iv); + + cairo_save(cr); + + /* Overall background (light grey) */ + cairo_set_source_rgb(cr, 0.7, 0.7, 0.7); + cairo_paint(cr); + + /* Get the transformation matrix before my transformations */ + cairo_get_matrix(cr, &m); + + cairo_scale(cr, iv->zoom, iv->zoom); + cairo_translate(cr, iv->offs_x, iv->offs_y); + cairo_scale(cr, 1.0, -1.0); + cairo_translate(cr, -gtk_adjustment_get_value(iv->hadj), + gtk_adjustment_get_value(iv->vadj)); + + if ( iv->pixbufs != NULL ) { + int i; + for ( i=0; i<iv->image->detgeom->n_panels; i++ ) { + cairo_save(cr); + draw_panel_rectangle(cr, iv, i, &m); + cairo_restore(cr); + } + } + + if ( iv->show_peaks ) { + draw_peaks(cr, iv, iv->image->features); + } + + if ( iv->show_refls ) { + int i; + for ( i=0; i<iv->image->n_crystals; i++ ) { + Crystal *cry = iv->image->crystals[i]; + draw_refls(cr, iv, + crystal_get_reflections(cry), + iv->label_refls, + crystal_cols[i % n_crystal_cols]); + } + } + + cairo_restore(cr); + + return FALSE; +} + + +static void scroll_adjust_sig(GtkAdjustment *adj, CrystFELImageView *iv) +{ + redraw(iv); +} + + +static void crystfel_image_view_set_property(GObject *obj, guint id, + const GValue *val, + GParamSpec *spec) +{ + CrystFELImageView *iv = CRYSTFEL_IMAGE_VIEW(obj); + + switch ( id ) { + + case CRYSTFELIMAGEVIEW_VPOL : + iv->vpol = g_value_get_enum(val); + break; + + case CRYSTFELIMAGEVIEW_HPOL : + iv->hpol = g_value_get_enum(val); + break; + + case CRYSTFELIMAGEVIEW_VADJ : + iv->vadj = g_value_get_object(val); + configure_scroll_adjustments(iv); + if ( iv->vadj != NULL ) { + g_signal_connect(G_OBJECT(iv->vadj), "value-changed", + G_CALLBACK(scroll_adjust_sig), iv); + } + break; + + case CRYSTFELIMAGEVIEW_HADJ : + iv->hadj = g_value_get_object(val); + configure_scroll_adjustments(iv); + if ( iv->hadj != NULL ) { + g_signal_connect(G_OBJECT(iv->hadj), "value-changed", + G_CALLBACK(scroll_adjust_sig), iv); + } + break; + + default : + printf("setting %i\n", id); + break; + + } +} + + +static void crystfel_image_view_get_property(GObject *obj, guint id, GValue *val, + GParamSpec *spec) +{ + CrystFELImageView *iv = CRYSTFEL_IMAGE_VIEW(obj); + + switch ( id ) { + + case CRYSTFELIMAGEVIEW_VADJ : + g_value_set_object(val, iv->vadj); + break; + + case CRYSTFELIMAGEVIEW_HADJ : + g_value_set_object(val, iv->hadj); + break; + + case CRYSTFELIMAGEVIEW_VPOL : + g_value_set_enum(val, iv->vpol); + break; + + case CRYSTFELIMAGEVIEW_HPOL : + g_value_set_enum(val, iv->hpol); + break; + + default : + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, spec); + break; + + } +} + + +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 = 640; +} + + +static void get_preferred_height(GtkWidget *widget, gint *min, gint *natural) +{ + *min = 0; + *natural = 640; +} + + +static void crystfel_image_view_class_init(CrystFELImageViewClass *klass) +{ + GObjectClass *goc = G_OBJECT_CLASS(klass); + goc->set_property = crystfel_image_view_set_property; + goc->get_property = crystfel_image_view_get_property; + g_object_class_override_property(goc, CRYSTFELIMAGEVIEW_VADJ, "vadjustment"); + g_object_class_override_property(goc, CRYSTFELIMAGEVIEW_HADJ, "hadjustment"); + g_object_class_override_property(goc, CRYSTFELIMAGEVIEW_VPOL, "vscroll-policy"); + g_object_class_override_property(goc, CRYSTFELIMAGEVIEW_HPOL, "hscroll-policy"); + + 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_image_view_init(CrystFELImageView *iv) +{ + iv->vpol = GTK_SCROLL_NATURAL; + iv->hpol = GTK_SCROLL_NATURAL; + iv->vadj = NULL; + iv->hadj = NULL; +} + + +GtkWidget *crystfel_image_view_new() +{ + CrystFELImageView *iv; + + iv = g_object_new(CRYSTFEL_TYPE_IMAGE_VIEW, NULL); + + /* All values initially meaningless */ + iv->detector_w = 1.0; + iv->detector_h = 1.0; + iv->zoom = -1.0; + iv->image = NULL; + iv->show_peaks = 0; + iv->brightness = 1.0; + iv->pixbufs = NULL; + iv->peak_box_size = 1.0; + iv->refl_box_size = 1.0; + iv->label_refls = 1; + iv->need_rerender = 0; + iv->need_recentre = 1; + + g_signal_connect(G_OBJECT(iv), "destroy", + G_CALLBACK(destroy_sig), iv); + g_signal_connect(G_OBJECT(iv), "realize", + G_CALLBACK(realise_sig), iv); + g_signal_connect(G_OBJECT(iv), "button-press-event", + G_CALLBACK(button_press_sig), iv); + g_signal_connect(G_OBJECT(iv), "scroll-event", + G_CALLBACK(scroll_sig), iv); + g_signal_connect(G_OBJECT(iv), "motion-notify-event", + G_CALLBACK(motion_sig), iv); + g_signal_connect(G_OBJECT(iv), "configure-event", + G_CALLBACK(configure_sig), iv); + g_signal_connect(G_OBJECT(iv), "draw", + G_CALLBACK(draw_sig), iv); + + gtk_widget_set_can_focus(GTK_WIDGET(iv), TRUE); + gtk_widget_add_events(GTK_WIDGET(iv), + 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 + | GDK_SCROLL_MASK); + + gtk_widget_grab_focus(GTK_WIDGET(iv)); + + gtk_widget_show(GTK_WIDGET(iv)); + + return GTK_WIDGET(iv); +} + + +static void check_extents(struct detgeom_panel p, double *min_x, double *min_y, + double *max_x, double *max_y, double fs, double ss) +{ + double xs, ys; + + xs = (fs*p.fsx + ss*p.ssx + p.cnx) * p.pixel_pitch; + ys = (fs*p.fsy + ss*p.ssy + p.cny) * p.pixel_pitch; + + if ( xs > *max_x ) *max_x = xs; + if ( ys > *max_y ) *max_y = ys; + if ( xs < *min_x ) *min_x = xs; + if ( ys < *min_y ) *min_y = ys; +} + + +static void detgeom_pixel_extents(struct detgeom *det, + double *min_x, double *min_y, + double *max_x, double *max_y) +{ + int i; + + *min_x = 0.0; + *max_x = 0.0; + *min_y = 0.0; + *max_y = 0.0; + + /* To determine the maximum extents of the detector, put all four + * corners of each panel through the transformations and watch for the + * biggest */ + + for ( i=0; i<det->n_panels; i++ ) { + + check_extents(det->panels[i], min_x, min_y, max_x, max_y, + 0.0, 0.0); + + check_extents(det->panels[i], min_x, min_y, max_x, max_y, + 0.0, det->panels[i].h+1); + + check_extents(det->panels[i], min_x, min_y, max_x, max_y, + det->panels[i].w+1, 0.0); + + check_extents(det->panels[i], min_x, min_y, max_x, max_y, + det->panels[i].w+1, det->panels[i].h+1); + + + } +} + + +static void free_pixbuf(guchar *data, gpointer p) +{ + free(data); +} + + +static GdkPixbuf *render_panel(float *data, int *badmap, int w, int h, + int scale_type, double scale_top) + + +{ + guchar *pixbuf_data; + long int i; + + /* Rendered (colourful) version */ + pixbuf_data = malloc(3*w*h); + if ( pixbuf_data == NULL ) return NULL; + + for ( i=0; i<w*h; i++ ) { + + double r, g, b; + + if ( !badmap[i] ) { + + colscale_lookup(data[i], scale_top, + scale_type, &r, &g, &b); + + pixbuf_data[3*i+0] = 255*r; + pixbuf_data[3*i+1] = 255*g; + pixbuf_data[3*i+2] = 255*b; + + } else { + + /* Bad pixel indicator colour */ + pixbuf_data[3*i+0] = 30; + pixbuf_data[3*i+1] = 20; + pixbuf_data[3*i+2] = 0; + + } + + } + + /* Create the pixbuf from the 8-bit display data */ + return gdk_pixbuf_new_from_data(pixbuf_data, + GDK_COLORSPACE_RGB, + FALSE, 8, w, h, w*3, + free_pixbuf, NULL); + +} + + +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); +} + + +static void center_adjustment(GtkAdjustment *adj) +{ + double min = gtk_adjustment_get_lower(adj); + double max = gtk_adjustment_get_upper(adj); + double page = gtk_adjustment_get_page_size(adj); + gtk_adjustment_set_value(adj, min+(max-min-page)/2.0); +} + + +static int rerender_image(CrystFELImageView *iv) +{ + int i; + double min_x, min_y, max_x, max_y; + double border; + double scale_top; + + if ( iv->image == NULL ) return 0; + if ( iv->image->detgeom == NULL ) return 0; + + if ( iv->pixbufs == NULL ) { + iv->pixbufs = calloc(iv->image->detgeom->n_panels, + sizeof(GdkPixbuf *)); + if ( iv->pixbufs == NULL ) return 1; + } else { + for ( i=0; i<iv->image->detgeom->n_panels; i++ ) { + gdk_pixbuf_unref(iv->pixbufs[i]); + } + } + + scale_top = auto_scale_top(iv->image); + + for ( i=0; i<iv->image->detgeom->n_panels; i++ ) { + iv->pixbufs[i] = render_panel(iv->image->dp[i], + iv->image->bad[i], + iv->image->detgeom->panels[i].w, + iv->image->detgeom->panels[i].h, + SCALE_COLOUR, scale_top); + if ( iv->pixbufs[i] == NULL ) return 1; + } + + detgeom_pixel_extents(iv->image->detgeom, &min_x, &min_y, + &max_x, &max_y); + iv->detector_w = max_x - min_x; + iv->detector_h = max_y - min_y; + border = iv->detector_w * 0.1; + iv->detector_w += border; + iv->detector_h += border; + if ( iv->zoom < 0.0 ) { + /* Set initial values */ + iv->offs_x = -min_x + border/2.0; + iv->offs_y = max_y + border/2.0; + iv->zoom = 1.0/iv->image->detgeom->panels[0].pixel_pitch; + } + configure_scroll_adjustments(iv); + if ( iv->need_recentre ) { + center_adjustment(iv->hadj); + center_adjustment(iv->vadj); + iv->need_recentre = 0; + } + + iv->need_rerender = 0; + redraw(iv); + + return 0; +} + + +int crystfel_image_view_set_image(CrystFELImageView *iv, + const struct image *image) +{ + cleanup_image(iv); + iv->image = image; + iv->need_rerender = 1; + redraw(iv); + return 0; +} + + +void crystfel_image_view_reset_zoom(CrystFELImageView *iv) +{ + iv->detector_w = 1.0; + iv->detector_h = 1.0; + iv->zoom = -1.0; + iv->need_recentre = 1; + iv->need_rerender = 1; + redraw(iv); +} + + +void crystfel_image_view_set_brightness(CrystFELImageView *iv, + double brightness) +{ + iv->brightness = brightness; + iv->need_rerender = 1; + redraw(iv); +} + + +void crystfel_image_view_set_show_peaks(CrystFELImageView *iv, + int show_peaks) +{ + iv->show_peaks = show_peaks; + iv->need_rerender = 1; + redraw(iv); +} + + +void crystfel_image_view_set_show_reflections(CrystFELImageView *iv, + int show_refls) +{ + iv->show_refls = show_refls; + iv->need_rerender = 1; + redraw(iv); +} + + +void crystfel_image_view_set_label_reflections(CrystFELImageView *iv, + int label_refls) +{ + iv->label_refls = label_refls; + iv->need_rerender = 1; + redraw(iv); +} + + +void crystfel_image_view_set_peak_box_size(CrystFELImageView *iv, + float box_size) +{ + iv->peak_box_size = box_size; + iv->need_rerender = 1; + redraw(iv); +} + + +void crystfel_image_view_set_refl_box_size(CrystFELImageView *iv, + float box_size) +{ + iv->refl_box_size = box_size; + iv->need_rerender = 1; + redraw(iv); +} |