aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cell_explorer.c1161
-rw-r--r--src/multihistogram.c139
-rw-r--r--src/multihistogram.h50
3 files changed, 1350 insertions, 0 deletions
diff --git a/src/cell_explorer.c b/src/cell_explorer.c
new file mode 100644
index 00000000..70df7d02
--- /dev/null
+++ b/src/cell_explorer.c
@@ -0,0 +1,1161 @@
+/*
+ * cell_explorer.c
+ *
+ * Examine cell parameter histograms
+ *
+ * Copyright © 2014 Deutsches Elektronen-Synchrotron DESY,
+ * a research centre of the Helmholtz Association.
+ *
+ * Authors:
+ * 2014 Thomas White <taw@physics.org>
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <math.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <crystfel/stream.h>
+#include <crystfel/image.h>
+#include <crystfel/utils.h>
+#include <crystfel/index.h>
+
+#include "multihistogram.h"
+
+
+static void show_help(const char *s)
+{
+ printf("Syntax: %s <my.stream>\n\n", s);
+ printf(
+"Examine cell parameter histograms.\n"
+"\n"
+" -h, --help Display this help message.\n"
+);
+}
+
+
+#define CAT_P (0)
+#define CAT_A (1)
+#define CAT_B (2)
+#define CAT_C (3)
+#define CAT_I (4)
+#define CAT_F (5)
+#define CAT_H (6)
+#define CAT_R (7)
+#define CAT_EXCLUDE (8)
+
+
+typedef struct {
+
+ GtkWidget *da;
+ MultiHistogram *h;
+ double min;
+ double max;
+ int n;
+ const char *units;
+
+ int width;
+ double dmin; /* Display min/max */
+ double dmax;
+
+ double sel1;
+ double sel2;
+ int show_sel;
+ int erase_sel_on_release;
+
+ double press_x;
+ double press_y;
+ double press_min;
+ int sel;
+
+ struct _cellwindow *parent;
+
+} HistoBox;
+
+
+typedef struct _cellwindow {
+
+ GtkWidget *window;
+ GtkUIManager *ui;
+ GtkActionGroup *action_group;
+
+ GtkWidget *indmlist;
+
+ UnitCell **cells;
+ IndexingMethod *indms;
+ int n_cells;
+
+ IndexingMethod unique_indms[256];
+ int active_indms[256];
+ int n_unique_indms;
+
+ HistoBox *hist_a;
+ HistoBox *hist_b;
+ HistoBox *hist_c;
+ HistoBox *hist_al;
+ HistoBox *hist_be;
+ HistoBox *hist_ga;
+
+ int cols_on[8];
+
+} CellWindow;
+
+
+#define P_COL 0.0, 0.0, 0.0
+#define A_COL 0.0, 0.8, 0.8
+#define B_COL 0.0, 0.0, 0.8
+#define C_COL 0.4, 0.3, 1.0
+#define I_COL 0.0, 0.8, 0.0
+#define F_COL 1.0, 0.3, 1.0
+#define H_COL 0.8, 0.0, 0.0
+#define R_COL 0.6, 0.6, 0.0
+
+static void set_col(cairo_t *cr, CellWindow *w, int cat)
+{
+ if ( w->cols_on[cat] == 0 ) {
+ cairo_set_source_rgb(cr, 0.9, 0.9, 0.9);
+ return;
+ }
+
+ if ( w->cols_on[cat] == 1 ) {
+ cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
+ return;
+ }
+
+ switch ( cat ) {
+
+ case CAT_P : cairo_set_source_rgb(cr, P_COL); break;
+ case CAT_A : cairo_set_source_rgb(cr, A_COL); break;
+ case CAT_B : cairo_set_source_rgb(cr, B_COL); break;
+ case CAT_C : cairo_set_source_rgb(cr, C_COL); break;
+ case CAT_I : cairo_set_source_rgb(cr, I_COL); break;
+ case CAT_F : cairo_set_source_rgb(cr, F_COL); break;
+ case CAT_H : cairo_set_source_rgb(cr, H_COL); break;
+ case CAT_R : cairo_set_source_rgb(cr, R_COL); break;
+
+ }
+}
+
+
+static gboolean destroy_sig(GtkWidget *da, CellWindow *w)
+{
+ gtk_main_quit();
+ return FALSE;
+}
+
+
+static void redraw_all(CellWindow *cw)
+{
+ gtk_widget_queue_draw(cw->hist_a->da);
+ gtk_widget_queue_draw(cw->hist_b->da);
+ gtk_widget_queue_draw(cw->hist_c->da);
+ gtk_widget_queue_draw(cw->hist_al->da);
+ gtk_widget_queue_draw(cw->hist_be->da);
+ gtk_widget_queue_draw(cw->hist_ga->da);
+}
+
+
+/* Calculate a round interval which occurs about three times in span */
+static double calc_tic_interval(double span, int *nm)
+{
+ if ( span / 500.0 > 2 ) { *nm = 5; return 500.0; }
+ if ( span / 100.0 > 2 ) { *nm = 10; return 100.0; }
+ if ( span / 50.0 > 2 ) { *nm = 5; return 50.0; }
+ if ( span / 10.0 > 2 ) { *nm = 10; return 10.0; }
+ if ( span / 5.0 > 2 ) { *nm = 5; return 5.0; }
+ if ( span / 1.0 > 2 ) { *nm = 10; return 1.0; }
+ if ( span / 0.5 > 2 ) { *nm = 5; return 0.5; }
+ *nm = 10; return 0.1;
+}
+
+
+static void draw_axis(cairo_t *cr, HistoBox *b, int width, int height)
+{
+ char label[128];
+ double t, ti, mt;
+ int nm;
+ const double ws = width / (b->dmax-b->dmin);
+
+ /* Draw the abscissa */
+ cairo_move_to(cr, 0.0, height-20.5);
+ cairo_line_to(cr, width, height-20.5);
+ cairo_set_line_width(cr, 1.0);
+ cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
+ cairo_stroke(cr);
+
+ /* Draw major tics (with labels) */
+ t = calc_tic_interval(b->dmax-b->dmin, &nm);
+
+ cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
+ cairo_set_line_width(cr, 1.0);
+
+ ti = t*trunc(b->dmin/t);
+ for ( ; ti<=b->dmax; ti+=t ) {
+
+ cairo_text_extents_t ext;
+
+ cairo_move_to(cr, ws*(ti-b->dmin), height-20.0);
+ cairo_line_to(cr, ws*(ti-b->dmin), height-12.0);
+ cairo_stroke(cr);
+
+ snprintf(label, 127, "%.1f%s", ti, b->units);
+ cairo_text_extents(cr, label, &ext);
+ cairo_move_to(cr, ws*(ti-b->dmin)-ext.x_advance/2, height-3.0);
+ cairo_show_text(cr, label);
+
+ }
+
+ mt = t / nm;
+
+ cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
+ cairo_set_line_width(cr, 1.0);
+
+ ti = mt*trunc(b->dmin/mt);
+ for ( ; ti<=b->dmax; ti+=mt ) {
+ cairo_move_to(cr, ws*(ti-b->dmin), height-20.0);
+ cairo_line_to(cr, ws*(ti-b->dmin), height-18.0);
+ cairo_stroke(cr);
+ }
+}
+
+
+static gboolean draw_sig(GtkWidget *da, GdkEventExpose *event, HistoBox *b)
+{
+ int width, height;
+ int i, max;
+ double h_height;
+ cairo_t *cr;
+ double gstep;
+ int *data_p, *data_a, *data_b, *data_c, *data_i, *data_f;
+ int *data_r, *data_h, *data_excl;
+ int start, stop;
+
+ cr = gdk_cairo_create(da->window);
+
+ width = da->allocation.width;
+ height = da->allocation.height;
+ b->width = width; /* Store for later use when dragging */
+
+ /* Overall background */
+ cairo_rectangle(cr, 0.0, 0.0, width, height);
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ cairo_fill(cr);
+
+ cairo_save(cr);
+
+ cairo_translate(cr, 0.0, height);
+ cairo_scale(cr, 1.0, -1.0);
+
+ data_p = multihistogram_get_data(b->h, CAT_P);
+ data_a = multihistogram_get_data(b->h, CAT_A);
+ data_b = multihistogram_get_data(b->h, CAT_B);
+ data_c = multihistogram_get_data(b->h, CAT_C);
+ data_i = multihistogram_get_data(b->h, CAT_I);
+ data_f = multihistogram_get_data(b->h, CAT_F);
+ data_h = multihistogram_get_data(b->h, CAT_H);
+ data_r = multihistogram_get_data(b->h, CAT_R);
+ data_excl = multihistogram_get_data(b->h, CAT_EXCLUDE);
+
+ max = 0;
+ for ( i=0; i<b->n; i++ ) {
+ int sum;
+ sum = data_p[i] + data_a[i] + data_b[i] + data_c[i]
+ + data_i[i] + data_f[i] + data_h[i] + data_r[i]
+ + data_excl[i];
+ if ( sum > max ) max = sum;
+ }
+
+ h_height = height - 20.0;
+ cairo_translate(cr, 0.0, 20.0);
+
+ cairo_scale(cr, width, h_height);
+ gstep = (b->max-b->min)/b->n;
+
+ /* Start with first visible bin */
+ if ( b->dmin > b->min ) {
+ start = (b->dmin - b->min)/gstep;
+ start--;
+ } else {
+ start = 0;
+ }
+ if ( b->dmax < b->max ) {
+ stop = (b->dmax - b->min)/gstep;
+ stop++;
+ } else {
+ stop = b->n;
+ }
+ for ( i=start; i<stop; i++ ) {
+
+ double hp = (double)data_p[i] / max;
+ double ha = (double)data_a[i] / max;
+ double hb = (double)data_b[i] / max;
+ double hc = (double)data_c[i] / max;
+ double hi = (double)data_i[i] / max;
+ double hf = (double)data_f[i] / max;
+ double hh = (double)data_h[i] / max;
+ double hr = (double)data_r[i] / max;
+ double he = (double)data_excl[i] / max;
+ double x = b->min+i*gstep;
+ double x2, w2;
+ double s;
+
+ x2 = (x - b->dmin)/(b->dmax - b->dmin);
+ w2 = gstep/(b->dmax - b->dmin);
+
+ s = 0.0;
+
+ cairo_rectangle(cr, x2, s, w2, hp);
+ set_col(cr, b->parent, CAT_P);
+ cairo_fill(cr);
+ s += hp;
+
+ cairo_rectangle(cr, x2, s, w2, ha);
+ set_col(cr, b->parent, CAT_A);
+ cairo_fill(cr);
+ s += ha;
+
+ cairo_rectangle(cr, x2, s, w2, hb);
+ set_col(cr, b->parent, CAT_B);
+ cairo_fill(cr);
+ s += hb;
+
+ cairo_rectangle(cr, x2, s, w2, hc);
+ set_col(cr, b->parent, CAT_C);
+ cairo_fill(cr);
+ s += hc;
+
+ cairo_rectangle(cr, x2, s, w2, hi);
+ set_col(cr, b->parent, CAT_I);
+ cairo_fill(cr);
+ s += hi;
+
+ cairo_rectangle(cr, x2, s, w2, hf);
+ set_col(cr, b->parent, CAT_F);
+ cairo_fill(cr);
+ s += hf;
+
+ cairo_rectangle(cr, x2, s, w2, hh);
+ set_col(cr, b->parent, CAT_H);
+ cairo_fill(cr);
+ s += hh;
+
+ cairo_rectangle(cr, x2, s, w2, hr);
+ set_col(cr, b->parent, CAT_R);
+ cairo_fill(cr);
+ s += hr;
+
+ cairo_rectangle(cr, x2, s, w2, he);
+ cairo_set_source_rgb(cr, 0.9, 0.9, 0.9);
+ cairo_fill(cr);
+
+ }
+
+ if ( b->show_sel ) {
+ cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.2);
+ cairo_rectangle(cr, (b->sel1-b->dmin)/(b->dmax - b->dmin), 0.0,
+ (b->sel2-b->sel1)/(b->dmax - b->dmin), 1.0);
+ cairo_fill(cr);
+ }
+
+ cairo_restore(cr);
+
+ draw_axis(cr, b, width, height);
+
+ cairo_destroy(cr);
+
+ return FALSE;
+}
+
+
+static void centered_text(cairo_t *cr, double x, double sq, const char *t)
+{
+ cairo_text_extents_t ext;
+
+ cairo_set_font_size(cr, sq-5.0);
+ cairo_text_extents(cr, t, &ext);
+
+ cairo_move_to(cr, x+(sq-ext.x_advance)/2.0, sq-(sq-ext.height)/2.0);
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+
+ cairo_show_text(cr, t);
+}
+
+
+static gboolean keyconf_sig(GtkWidget *key, GdkEventConfigure *event,
+ CellWindow *w)
+{
+ gtk_widget_set_size_request(GTK_WIDGET(key), 8*event->height, -1);
+ return FALSE;
+}
+
+
+static gint keyclick_sig(GtkWidget *widget, GdkEventButton *event,
+ CellWindow *w)
+{
+ int width, cat;
+
+ /* Ignore extra events for double click */
+ if ( event->type != GDK_BUTTON_PRESS ) return FALSE;
+
+ width = widget->allocation.width;
+
+ cat = 8*event->x / width;
+
+ if ( cat == 0 ) {
+ /* Special handling for P so that it doesn't go
+ * black->black->grey */
+ w->cols_on[cat] = (w->cols_on[cat]+1) % 2;
+ } else {
+ w->cols_on[cat] = (w->cols_on[cat]+1) % 3;
+ }
+
+ gtk_widget_queue_draw(widget);
+ redraw_all(w);
+
+ return TRUE;
+}
+
+
+
+static gboolean keydraw_sig(GtkWidget *da, GdkEventExpose *event, CellWindow *w)
+{
+ int width, height;
+ cairo_t *cr;
+ double x;
+
+ cr = gdk_cairo_create(da->window);
+
+ width = da->allocation.width;
+ height = da->allocation.height;
+
+ /* Overall background */
+ cairo_rectangle(cr, 0.0, 0.0, width, height);
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ cairo_fill(cr);
+
+ x = 0.0;
+ cairo_rectangle(cr, x, 0.0, height, height);
+ set_col(cr, w, CAT_P);
+ cairo_fill(cr);
+ centered_text(cr, x, height, "P");
+
+ x += height;
+ cairo_rectangle(cr, x, 0.0, height, height);
+ set_col(cr, w, CAT_A);
+ cairo_fill(cr);
+ centered_text(cr, x, height, "A");
+
+ x += height;
+ cairo_rectangle(cr, x, 0.0, height, height);
+ set_col(cr, w, CAT_B);
+ cairo_fill(cr);
+ centered_text(cr, x, height, "B");
+
+ x += height;
+ cairo_rectangle(cr, x, 0.0, height, height);
+ set_col(cr, w, CAT_C);
+ cairo_fill(cr);
+ centered_text(cr, x, height, "C");
+
+ x += height;
+ cairo_rectangle(cr, x, 0.0, height, height);
+ set_col(cr, w, CAT_I);
+ cairo_fill(cr);
+ centered_text(cr, x, height, "I");
+
+ x += height;
+ cairo_rectangle(cr, x, 0.0, height, height);
+ set_col(cr, w, CAT_F);
+ cairo_fill(cr);
+ centered_text(cr, x, height, "F");
+
+ x += height;
+ cairo_rectangle(cr, x, 0.0, height, height);
+ set_col(cr, w, CAT_H);
+ cairo_fill(cr);
+ centered_text(cr, x, height, "H");
+
+ x += height;
+ cairo_rectangle(cr, x, 0.0, height, height);
+ set_col(cr, w, CAT_R);
+ cairo_fill(cr);
+ centered_text(cr, x, height, "R");
+
+ return FALSE;
+}
+
+
+static int check_exclude(HistoBox *h, double v)
+{
+ double min, max;
+
+ if ( !h->show_sel ) return 0;
+
+ if ( h->sel1 > h->sel2 ) {
+ min = h->sel2;
+ max = h->sel1;
+ } else {
+ min = h->sel1;
+ max = h->sel2;
+ }
+
+ if ( v < min ) return 1;
+ if ( v > max ) return 1;
+
+ return 0;
+}
+
+
+static void scan_cells(CellWindow *w)
+{
+ int i;
+ UnitCell **cells = w->cells;
+ int n_cells = w->n_cells;
+
+ multihistogram_delete_all_values(w->hist_a->h);
+ multihistogram_delete_all_values(w->hist_b->h);
+ multihistogram_delete_all_values(w->hist_c->h);
+ multihistogram_delete_all_values(w->hist_al->h);
+ multihistogram_delete_all_values(w->hist_be->h);
+ multihistogram_delete_all_values(w->hist_ga->h);
+
+ multihistogram_set_num_bins(w->hist_a->h, w->hist_a->n);
+ multihistogram_set_num_bins(w->hist_b->h, w->hist_b->n);
+ multihistogram_set_num_bins(w->hist_c->h, w->hist_c->n);
+ multihistogram_set_num_bins(w->hist_al->h, w->hist_al->n);
+ multihistogram_set_num_bins(w->hist_be->h, w->hist_be->n);
+ multihistogram_set_num_bins(w->hist_ga->h, w->hist_ga->n);
+
+ for ( i=0; i<n_cells; i++ ) {
+
+ double a, b, c, al, be, ga;
+ int cat, j;
+ int ignore = 0;
+
+ for ( j=0; j<w->n_unique_indms; j++ ) {
+ if ( w->unique_indms[j] == w->indms[i] ) {
+ if ( !w->active_indms[j] ) ignore = 1;
+ }
+ }
+
+ if ( ignore ) continue;
+
+ cell_get_parameters(cells[i], &a, &b, &c, &al, &be, &ga);
+ a *= 1e10; b *= 1e10; c *= 1e10;
+ al = rad2deg(al); be = rad2deg(be); ga = rad2deg(ga);
+
+ switch ( cell_get_centering(cells[i]) ) {
+ case 'P' : cat = 1<<CAT_P; break;
+ case 'A' : cat = 1<<CAT_A; break;
+ case 'B' : cat = 1<<CAT_B; break;
+ case 'C' : cat = 1<<CAT_C; break;
+ case 'I' : cat = 1<<CAT_I; break;
+ case 'F' : cat = 1<<CAT_F; break;
+ case 'H' : cat = 1<<CAT_H; break;
+ case 'R' : cat = 1<<CAT_R; break;
+ default : abort();
+ }
+
+ if ( check_exclude(w->hist_a, a) ||
+ check_exclude(w->hist_b, b) ||
+ check_exclude(w->hist_c, c) ||
+ check_exclude(w->hist_al, al) ||
+ check_exclude(w->hist_be, be) ||
+ check_exclude(w->hist_ga, ga) )
+ {
+ cat = 1<<CAT_EXCLUDE;
+ }
+
+ multihistogram_add_value(w->hist_a->h, a, cat);
+ multihistogram_add_value(w->hist_b->h, b, cat);
+ multihistogram_add_value(w->hist_c->h, c, cat);
+ multihistogram_add_value(w->hist_al->h, al, cat);
+ multihistogram_add_value(w->hist_be->h, be, cat);
+ multihistogram_add_value(w->hist_ga->h, ga, cat);
+
+ }
+}
+
+
+static void check_minmax(HistoBox *h, double val)
+{
+ if ( val > h->max ) h->max = val;
+ if ( val < h->min ) h->min = val;
+}
+
+
+static void set_minmax(HistoBox *h)
+{
+ multihistogram_set_min(h->h, h->min);
+ multihistogram_set_max(h->h, h->max);
+}
+
+
+static void scan_minmax(CellWindow *w)
+{
+ int i;
+
+ for ( i=0; i<w->n_cells; i++ ) {
+
+ double a, b, c, al, be, ga;
+ int j;
+ int found = 0;
+
+ cell_get_parameters(w->cells[i], &a, &b, &c, &al, &be, &ga);
+ a *= 1e10; b *= 1e10; c *= 1e10;
+ al = rad2deg(al); be = rad2deg(be); ga = rad2deg(ga);
+
+ check_minmax(w->hist_a, a);
+ check_minmax(w->hist_b, b);
+ check_minmax(w->hist_c, c);
+ check_minmax(w->hist_al, al);
+ check_minmax(w->hist_be, be);
+ check_minmax(w->hist_ga, ga);
+
+ for ( j=0; j<w->n_unique_indms; j++ ) {
+ if ( w->indms[i] == w->unique_indms[j] ) {
+ found = 1;
+ break;
+ }
+ }
+ if ( !found ) {
+ if ( w->n_unique_indms > 255 ) {
+ fprintf(stderr, "Too many indexing methods\n");
+ } else {
+ IndexingMethod m = w->indms[i];
+ w->unique_indms[w->n_unique_indms++] = m;
+ }
+ }
+
+ }
+
+ set_minmax(w->hist_a);
+ set_minmax(w->hist_b);
+ set_minmax(w->hist_c);
+ set_minmax(w->hist_al);
+ set_minmax(w->hist_be);
+ set_minmax(w->hist_ga);
+}
+
+
+static void add_ui_sig(GtkUIManager *ui, GtkWidget *widget,
+ GtkContainer *container)
+{
+ gtk_box_pack_start(GTK_BOX(container), widget, FALSE, FALSE, 0);
+ if ( GTK_IS_TOOLBAR(widget) ) {
+ gtk_toolbar_set_show_arrow(GTK_TOOLBAR(widget), TRUE);
+ }
+}
+
+
+static gint quit_sig(GtkWidget *widget, CellWindow *w)
+{
+ gtk_main_quit();
+ return FALSE;
+}
+
+
+static gint about_sig(GtkWidget *widget, CellWindow *w)
+{
+ GtkWidget *window;
+
+ const gchar *authors[] = {
+ "Thomas White <taw@physics.org>",
+ NULL
+ };
+
+ window = gtk_about_dialog_new();
+ gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(w->window));
+
+ gtk_about_dialog_set_name(GTK_ABOUT_DIALOG(window),
+ "Unit Cell Explorer");
+ gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(window), "0.0.1");
+ gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(window),
+ "© 2014 Deutsches Elektronen-Synchrotron DESY, "
+ "a research centre of the Helmholtz Association.");
+ gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(window),
+ "Examine unit cell distributions");
+ gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(window),
+ "http://www.desy.de/~twhite/crystfel");
+ gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(window), authors);
+
+ g_signal_connect(window, "response", G_CALLBACK(gtk_widget_destroy),
+ NULL);
+
+ gtk_widget_show_all(window);
+
+ return 0;
+}
+
+
+static void add_menu_bar(CellWindow *w, GtkWidget *vbox)
+{
+ GError *error = NULL;
+
+ const char *ui = "<ui> <menubar name=\"cellwindow\">"
+ "<menu name=\"file\" action=\"FileAction\">"
+ " <menuitem name=\"quit\" action=\"QuitAction\" />"
+ "</menu>"
+ "<menu name=\"help\" action=\"HelpAction\">"
+ " <menuitem name=\"about\" action=\"AboutAction\" />"
+ "</menu>"
+ "</menubar></ui>";
+
+ GtkActionEntry entries[] = {
+
+ { "FileAction", NULL, "_File", NULL, NULL, NULL },
+ { "QuitAction", GTK_STOCK_QUIT, "_Quit", NULL, NULL,
+ G_CALLBACK(quit_sig) },
+
+ { "HelpAction", NULL, "_Help", NULL, NULL, NULL },
+ { "AboutAction", GTK_STOCK_ABOUT, "_About", NULL, NULL,
+ G_CALLBACK(about_sig) },
+
+ };
+ guint n_entries = G_N_ELEMENTS(entries);
+
+ w->action_group = gtk_action_group_new("cellwindow");
+ gtk_action_group_add_actions(w->action_group, entries, n_entries, w);
+
+ w->ui = gtk_ui_manager_new();
+ gtk_ui_manager_insert_action_group(w->ui, w->action_group, 0);
+ g_signal_connect(w->ui, "add_widget", G_CALLBACK(add_ui_sig), vbox);
+ if ( gtk_ui_manager_add_ui_from_string(w->ui, ui, -1, &error) == 0 )
+ {
+ fprintf(stderr, "Error loading message window menu bar: %s\n",
+ error->message);
+ return;
+ }
+
+ gtk_window_add_accel_group(GTK_WINDOW(w->window),
+ gtk_ui_manager_get_accel_group(w->ui));
+ gtk_ui_manager_ensure_update(w->ui);
+}
+
+
+static void reset_axes(HistoBox *h)
+{
+ /* Fudge factor makes sure that the tic interval calculation falls
+ * clearly on one side or other of its tests */
+ h->dmin = (nearbyint(h->min/10.0)-1.001)*10.0;
+ h->dmax = (nearbyint(h->max/10.0)+1.001)*10.0;
+
+}
+
+
+static gint press_sig(GtkWidget *widget, GdkEventButton *event, HistoBox *h)
+{
+ h->press_x = event->x;
+ h->press_y = event->y;
+ h->press_min = h->dmin;
+ if ( event->state & GDK_SHIFT_MASK ) {
+ h->sel = 1;
+ h->show_sel = 0;
+ } else {
+ h->sel = 0;
+ }
+ gtk_widget_grab_focus(GTK_WIDGET(h->da));
+ return TRUE;
+}
+
+
+static gint release_sig(GtkWidget *widget, GdkEventButton *event, HistoBox *h)
+{
+ if ( h->sel ) {
+ scan_cells(h->parent);
+ redraw_all(h->parent);
+ }
+ return TRUE;
+}
+
+
+static gint motion_sig(GtkWidget *da, GdkEventMotion *event, HistoBox *h)
+{
+ double span = h->dmax - h->dmin;
+
+ if ( !h->sel ) {
+
+ h->dmin = h->press_min - span*(event->x - h->press_x)/h->width;
+ h->dmax = h->dmin + span;
+
+ } else {
+
+ h->sel1 = h->dmin + span*h->press_x / h->width;
+ h->sel2 = h->dmin + span*event->x / h->width;
+ h->show_sel = 1;
+
+ }
+
+ gtk_widget_queue_draw(h->da);
+
+ if ( event->is_hint ) gdk_window_get_pointer(da->window,
+ NULL, NULL, NULL);
+ return TRUE;
+}
+
+
+static gint scroll_sig(GtkWidget *widget, GdkEventScroll *event, HistoBox *h)
+{
+ double span = h->dmax - h->dmin;
+ double pos = h->dmin + span*event->x/h->width;;
+
+ if ( event->direction == GDK_SCROLL_UP ) {
+ h->dmin = pos - (pos-h->dmin)*0.9;
+ h->dmax = pos + (h->dmax-pos)*0.9;
+ } else if ( event->direction == GDK_SCROLL_DOWN ) {
+ h->dmin = pos - (pos-h->dmin)*1.1;
+ h->dmax = pos + (h->dmax-pos)*1.1;
+ } else {
+ return FALSE;
+ }
+
+ gtk_widget_grab_focus(GTK_WIDGET(h->da));
+ gtk_widget_queue_draw(h->da);
+
+ return TRUE;
+}
+
+
+static gint keypress_sig(GtkWidget *widget, GdkEventKey *event, HistoBox *h)
+{
+ if ( (event->keyval == GDK_R) || (event->keyval == GDK_r) ) {
+ reset_axes(h);
+ gtk_widget_queue_draw(h->da);
+ }
+
+ /* I'm too lazy to press shift */
+ if ( (event->keyval == GDK_plus) || (event->keyval == GDK_equal) ) {
+ h->n *= 2;
+ scan_cells(h->parent);
+ gtk_widget_queue_draw(h->da);
+ }
+
+ if ( (event->keyval == GDK_minus) && (h->n > 1) ) {
+ h->n /= 2;
+ scan_cells(h->parent);
+ gtk_widget_queue_draw(h->da);
+ }
+
+ return FALSE;
+}
+
+
+static HistoBox *histobox_new(CellWindow *w, const char *units)
+{
+ HistoBox *h;
+
+ h = calloc(1, sizeof(HistoBox));
+ if ( h == NULL ) return NULL;
+
+ h->show_sel = 0;
+ h->units = units;
+ h->parent = w;
+ h->min = +INFINITY;
+ h->max = -INFINITY;
+ h->n = 100; /* Number of bins */
+
+ h->h = multihistogram_new();
+
+ h->da = gtk_drawing_area_new();
+
+ g_object_set(G_OBJECT(h->da), "can-focus", TRUE, NULL);
+ gtk_widget_set_size_request(GTK_WIDGET(h->da), 400, 200);
+ gtk_widget_add_events(GTK_WIDGET(h->da),
+ GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK
+ | GDK_BUTTON1_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK
+ | GDK_SCROLL_MASK
+ | GDK_KEY_PRESS_MASK);
+
+ g_signal_connect(G_OBJECT(h->da), "expose_event", G_CALLBACK(draw_sig),
+ h);
+ g_signal_connect(G_OBJECT(h->da), "button-press-event",
+ G_CALLBACK(press_sig), h);
+ g_signal_connect(G_OBJECT(h->da), "button-release-event",
+ G_CALLBACK(release_sig), h);
+ g_signal_connect(G_OBJECT(h->da), "motion-notify-event",
+ G_CALLBACK(motion_sig), h);
+ g_signal_connect(G_OBJECT(h->da), "scroll-event",
+ G_CALLBACK(scroll_sig), h);
+ g_signal_connect(G_OBJECT(h->da), "key-press-event",
+ G_CALLBACK(keypress_sig), h);
+
+ return h;
+}
+
+
+struct toggle_method
+{
+ int *active;
+ CellWindow *w;
+};
+
+
+static gint indm_toggle_sig(GtkWidget *widget, struct toggle_method *tm)
+{
+ *tm->active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+ scan_cells(tm->w);
+ redraw_all(tm->w);
+ return FALSE;
+}
+
+
+static void indexing_method_list(CellWindow *w, GtkWidget *vbox)
+{
+ GtkWidget *key;
+ int j;
+
+ w->indmlist = gtk_hbox_new(FALSE, 5.0);
+ gtk_box_pack_start(GTK_BOX(vbox), w->indmlist, FALSE, FALSE, 5.0);
+ gtk_box_pack_start(GTK_BOX(w->indmlist),
+ gtk_label_new("Show results from:"),
+ FALSE, FALSE, 5.0);
+
+ key = gtk_drawing_area_new();
+ gtk_box_pack_end(GTK_BOX(w->indmlist), key, FALSE, FALSE, 5.0);
+ gtk_widget_add_events(GTK_WIDGET(key), GDK_BUTTON_PRESS_MASK);
+ g_signal_connect(G_OBJECT(key), "expose_event", G_CALLBACK(keydraw_sig),
+ w);
+ g_signal_connect(G_OBJECT(key), "configure-event",
+ G_CALLBACK(keyconf_sig), w);
+ g_signal_connect(G_OBJECT(key), "button-press-event",
+ G_CALLBACK(keyclick_sig), w);
+
+ for ( j=0; j<w->n_unique_indms; j++ ) {
+
+ GtkWidget *button;
+ char *label;
+ struct toggle_method *tm = malloc(sizeof(struct toggle_method));
+
+ if ( tm == NULL ) {
+ fprintf(stderr, "Failed to allocate toggle method\n");
+ continue;
+ }
+
+ label = indexer_str(w->unique_indms[j]);
+ button = gtk_toggle_button_new_with_label(label);
+ free(label);
+
+ gtk_box_pack_start(GTK_BOX(w->indmlist), button,
+ FALSE, FALSE, 5.0);
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+
+ tm->w = w;
+ tm->active = &w->active_indms[j];
+ g_signal_connect(G_OBJECT(button), "toggled",
+ G_CALLBACK(indm_toggle_sig), tm);
+
+ w->active_indms[j] = 1;
+
+ }
+
+
+}
+
+
+int main(int argc, char *argv[])
+{
+ int c;
+ Stream *st;
+ int max_cells = 0;
+ char *stream_filename;
+ GtkWidget *box, *vbox;
+ char title[1024];
+ char *bn;
+ CellWindow w;
+ int n_chunks = 0;
+ int i;
+
+ /* Long options */
+ const struct option longopts[] = {
+ {"help", 0, NULL, 'h'},
+ {0, 0, NULL, 0}
+ };
+
+ /* Short options */
+ while ((c = getopt_long(argc, argv, "h",
+ longopts, NULL)) != -1) {
+
+ switch (c) {
+
+ case 'h' :
+ show_help(argv[0]);
+ return 0;
+
+ default :
+ return 1;
+
+ }
+
+ }
+
+ /* This isn't great, but necessary to make the command-line UI and file
+ * formats consistent with the other programs, which all use the C
+ * locale. Better would be to have all the programs call
+ * setlocale(LC_ALL, "") and use the C locale temporarily when reading
+ * or writing a stream, reflection file, geometry file etc. */
+ gtk_disable_setlocale();
+
+ gtk_init(&argc, &argv);
+
+ if ( argc != (optind+1) ) {
+ fprintf(stderr, "Please provide exactly one stream filename.\n");
+ return 1;
+ }
+ stream_filename = strdup(argv[optind]);
+ st = open_stream_for_read(stream_filename);
+ if ( st == NULL ) {
+ fprintf(stderr, "Failed to open '%s'\n", stream_filename);
+ return 1;
+ }
+
+ w.cells = NULL;
+ w.indms = NULL;
+ w.n_cells = 0;
+ do {
+
+ struct image cur;
+ int i;
+
+ cur.det = NULL;
+
+ if ( read_chunk(st, &cur) != 0 ) {
+ break;
+ }
+
+ image_feature_list_free(cur.features);
+
+ for ( i=0; i<cur.n_crystals; i++ ) {
+
+ RefList *cr_refl;
+ Crystal *cr;
+
+ cr = cur.crystals[i];
+
+ cr_refl = crystal_get_reflections(cr);
+ reflist_free(cr_refl);
+
+ if ( w.n_cells == max_cells ) {
+
+ UnitCell **cells_new;
+ IndexingMethod *indms_new;
+ size_t nsz;
+
+
+ nsz = (max_cells+1024)*sizeof(UnitCell *);
+ cells_new = realloc(w.cells, nsz);
+ if ( cells_new == NULL ) {
+ fprintf(stderr, "Failed to allocate "
+ "memory for cells.\n");
+ break;
+ }
+
+ nsz = (max_cells+1024)*sizeof(IndexingMethod);
+ indms_new = realloc(w.indms, nsz);
+ if ( indms_new == NULL ) {
+ fprintf(stderr, "Failed to allocate "
+ "memory for methods.\n");
+ break;
+ }
+
+ max_cells += 1024;
+ w.cells = cells_new;
+ w.indms = indms_new;
+
+ }
+
+ w.cells[w.n_cells] = crystal_get_cell(cr);
+ w.indms[w.n_cells] = cur.indexed_by;
+ w.n_cells++;
+
+ crystal_free(cr);
+
+ }
+
+ fprintf(stderr, "Loaded %i cells from %i chunks\r", w.n_cells,
+ ++n_chunks);
+
+ } while ( 1 );
+
+ fprintf(stderr, "\n");
+
+ close_stream(st);
+
+ w.cols_on[0] = 1;
+ for ( i=1; i<8; i++ ) w.cols_on[i] = 2;
+
+ w.hist_a = histobox_new(&w, " A");
+ w.hist_b = histobox_new(&w, " A");
+ w.hist_c = histobox_new(&w, " A");
+ w.hist_al = histobox_new(&w, "°");
+ w.hist_be = histobox_new(&w, "°");
+ w.hist_ga = histobox_new(&w, "°");
+
+ w.n_unique_indms = 0;
+
+ scan_minmax(&w);
+ scan_cells(&w);
+ reset_axes(w.hist_a);
+ reset_axes(w.hist_b);
+ reset_axes(w.hist_c);
+ reset_axes(w.hist_al);
+ reset_axes(w.hist_be);
+ reset_axes(w.hist_ga);
+
+ w.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ bn = safe_basename(stream_filename);
+ snprintf(title, 1023, "%s - Unit Cell Explorer", bn);
+ free(bn);
+ gtk_window_set_title(GTK_WINDOW(w.window), title);
+ g_signal_connect(G_OBJECT(w.window), "destroy", G_CALLBACK(destroy_sig),
+ &w);
+
+ vbox = gtk_vbox_new(FALSE, 0.0);
+ gtk_container_add(GTK_CONTAINER(w.window), vbox);
+ add_menu_bar(&w, vbox);
+
+ indexing_method_list(&w, vbox);
+
+ box = gtk_hbox_new(FALSE, 0.0);
+ gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, TRUE, 5.0);
+
+ gtk_box_pack_start(GTK_BOX(box), w.hist_a->da, TRUE, TRUE, 5.0);
+ gtk_box_pack_start(GTK_BOX(box), w.hist_b->da, TRUE, TRUE, 5.0);
+ gtk_box_pack_start(GTK_BOX(box), w.hist_c->da, TRUE, TRUE, 5.0);
+
+ box = gtk_hbox_new(FALSE, 0.0);
+ gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, TRUE, 5.0);
+
+ gtk_box_pack_start(GTK_BOX(box), w.hist_al->da, TRUE, TRUE, 5.0);
+ gtk_box_pack_start(GTK_BOX(box), w.hist_be->da, TRUE, TRUE, 5.0);
+ gtk_box_pack_start(GTK_BOX(box), w.hist_ga->da, TRUE, TRUE, 5.0);
+
+ gtk_widget_show_all(w.window);
+ gtk_main();
+
+ return 0;
+}
diff --git a/src/multihistogram.c b/src/multihistogram.c
new file mode 100644
index 00000000..afdb1cdb
--- /dev/null
+++ b/src/multihistogram.c
@@ -0,0 +1,139 @@
+/*
+ * multimultihistogram.c
+ *
+ * MultiHistogram with categories
+ *
+ * Copyright © 2013-2014 Deutsches Elektronen-Synchrotron DESY,
+ * a research centre of the Helmholtz Association.
+ *
+ * Authors:
+ * 2013-2014 Thomas White <taw@physics.org>
+ *
+ * This program 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 <math.h>
+#include <string.h>
+#include <assert.h>
+
+#include "multihistogram.h"
+
+struct _multihistogram
+{
+ double min;
+ double max;
+
+ int n_bins;
+ double bin_width;
+
+ int *bins[32];
+};
+
+
+void multihistogram_delete_all_values(MultiHistogram *hi)
+{
+ int i;
+
+ for ( i=0; i<32; i++ ) {
+ if ( hi->bins[i] != NULL ) free(hi->bins[i]);
+ }
+
+ for ( i=0; i<32; i++ ) {
+ hi->bins[i] = calloc(hi->n_bins, sizeof(int));
+ }
+}
+
+
+MultiHistogram *multihistogram_new()
+{
+ MultiHistogram *hi;
+ int i;
+
+ hi = malloc(sizeof(struct _multihistogram));
+ if ( hi == NULL ) return NULL;
+
+ hi->max = -INFINITY;
+ hi->min = INFINITY;
+
+ hi->n_bins = 50;
+
+ for ( i=0; i<32; i++ ) hi->bins[i] = NULL;
+
+ return hi;
+}
+
+
+void multihistogram_free(MultiHistogram *hi)
+{
+ int i;
+ for ( i=0; i<32; i++ ) {
+ if ( hi->bins[i] != NULL ) free(hi->bins[i]);
+ }
+ free(hi);
+}
+
+
+void multihistogram_add_value(MultiHistogram *hi, double val, int cat)
+{
+ int i, j;
+
+ j = (val - hi->min) / hi->bin_width;
+
+ /* Tidy up rounding errors */
+ if ( j < 0 ) j = 0;
+ if ( j >= hi->n_bins ) j = hi->n_bins - 1;
+
+ for ( i=0; i<32; i++ ) {
+ if ( cat & 1<<i ) hi->bins[i][j]++;
+ }
+}
+
+
+int *multihistogram_get_data(MultiHistogram *hi, int cat)
+{
+ if ( cat < 0 ) return NULL;
+ if ( cat > 31 ) return NULL;
+ return hi->bins[cat];
+}
+
+
+void multihistogram_set_min(MultiHistogram *hi, double min)
+{
+ hi->min = min;
+ hi->bin_width = (hi->max - hi->min)/hi->n_bins;
+ multihistogram_delete_all_values(hi);
+}
+
+
+void multihistogram_set_max(MultiHistogram *hi, double max)
+{
+ hi->max = max;
+ hi->bin_width = (hi->max - hi->min)/hi->n_bins;
+ multihistogram_delete_all_values(hi);
+}
+
+
+void multihistogram_set_num_bins(MultiHistogram *hi, int n)
+{
+ hi->n_bins = n;
+ hi->bin_width = (hi->max - hi->min)/hi->n_bins;
+ multihistogram_delete_all_values(hi);
+}
diff --git a/src/multihistogram.h b/src/multihistogram.h
new file mode 100644
index 00000000..f943454a
--- /dev/null
+++ b/src/multihistogram.h
@@ -0,0 +1,50 @@
+/*
+ * multihistogram.h
+ *
+ * Histogram with categories
+ *
+ * Copyright © 2013-2014 Deutsches Elektronen-Synchrotron DESY,
+ * a research centre of the Helmholtz Association.
+ *
+ * Authors:
+ * 2013-2014 Thomas White <taw@physics.org>
+ *
+ * This program 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 MULTIHISTOGRAM_H
+#define MULTIHISTOGRAM_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+
+typedef struct _multihistogram MultiHistogram;
+
+extern MultiHistogram *multihistogram_new();
+extern void multihistogram_free(MultiHistogram *hi);
+
+extern void multihistogram_delete_all_values(MultiHistogram *hi);
+extern void multihistogram_add_value(MultiHistogram *hi, double val, int cat);
+
+extern void multihistogram_set_min(MultiHistogram *hi, double min);
+extern void multihistogram_set_max(MultiHistogram *hi, double max);
+extern void multihistogram_set_num_bins(MultiHistogram *hi, int n);
+
+extern int *multihistogram_get_data(MultiHistogram *hi, int cat);
+
+
+#endif /* HISTOGRAM_H */