/* * gui_fom.c * * Figures of merit via CrystFEL GUI * * Copyright © 2020-2021 Deutsches Elektronen-Synchrotron DESY, * a research centre of the Helmholtz Association. * * Authors: * 2020-2021 Thomas White * * 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 . * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "gui_project.h" #include "crystfel_gui.h" #include "gtk-util-routines.h" #include "crystfelfomgraph.h" #define MAX_DATASETS (64) struct fom_window { struct crystfelproject *proj; GtkWidget *min_res; GtkWidget *max_res; GtkWidget *num_bins; GtkWidget *min_snr; GtkWidget *min_meas; GtkWidget *cell_chooser; GtkWidget *graph; GtkWidget *input_combo; int n_foms; GtkWidget *fom_checkboxes[16]; enum fom_type fom_types[16]; struct fom_shells *calc_shells; int calc_n_foms; enum fom_type *calc_fom_types; double **calc_fom_values; double *calc_fom_overall; }; static int menu_selected(GtkWidget *w) { return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)); } static int fom_selected(struct fom_window *f, int i) { return menu_selected(f->fom_checkboxes[i]); } static int anomalous_foms_selected(struct fom_window *f) { int i; for ( i=0; in_foms; i++ ) { if ( fom_selected(f, i) ) { if ( fom_is_anomalous(f->fom_types[i]) ) return 1; } } return 0; } static double *make_shell_centers(struct fom_shells *shells) { int i; double *vals; vals = malloc(shells->nshells*sizeof(double)); if ( vals == NULL ) return NULL; for ( i=0; inshells; i++ ) { vals[i] = fom_shell_centre(shells, i); } return vals; } static double *make_fom_vals(struct fom_context *fctx, struct fom_shells *shells) { int i; double *vals; vals = malloc(shells->nshells*sizeof(double)); if ( vals == NULL ) return NULL; for ( i=0; inshells; i++ ) { vals[i] = fom_shell_value(fctx, i); } return vals; } static int load_dataset(struct gui_merge_result *result, int need_ano, UnitCell *cell, double min_res, double max_res, int min_meas, double min_snr, SymOpList **psym, RefList **pall_refls, RefList **pall_refls_anom, RefList **ppart1, RefList **ppart2, RefList **ppart1_anom, RefList **ppart2_anom) { RefList *raw_refl; RefList *raw_part1; RefList *raw_part2; RefList *all_refls = NULL; RefList *all_refls_anom = NULL; RefList *part1 = NULL; RefList *part2 = NULL; RefList *part1_anom = NULL; RefList *part2_anom = NULL; SymOpList *sym; char *sym_str; char *sym_str_part1; char *sym_str_part2; raw_refl = read_reflections_2(result->hkl, &sym_str); if ( raw_refl == NULL ) { ERROR("Failed to load dataset %s (%s)\n", result->name, result->hkl); return 1; } raw_part1 = read_reflections_2(result->hkl1, &sym_str_part1); if ( raw_part1 == NULL ) { ERROR("Failed to load part 1 dataset %s (%s)\n", result->name, result->hkl1); return 1; } raw_part2 = read_reflections_2(result->hkl2, &sym_str_part2); if ( raw_part2 == NULL ) { ERROR("Failed to load part 2 dataset %s (%s)\n", result->name, result->hkl2); return 1; } if ( (sym_str == NULL) || (sym_str_part1 == NULL) || (sym_str_part2 == NULL) ) { ERROR("Reflection list has no point group\n"); reflist_free(raw_refl); reflist_free(raw_part1); reflist_free(raw_part2); return 1; } if ( (strcmp(sym_str, sym_str_part1) != 0) || (strcmp(sym_str, sym_str_part2) != 0) ) { ERROR("Datasets do not have the same point group!\n"); free(sym_str); free(sym_str_part1); free(sym_str_part2); reflist_free(raw_refl); reflist_free(raw_part1); reflist_free(raw_part2); return 1; } sym = get_pointgroup(sym_str); free(sym_str); free(sym_str_part1); free(sym_str_part2); fom_select_reflections(raw_refl, &all_refls, cell, sym, 1e10/min_res, 1e10/max_res, min_snr, 0, 0, min_meas); if ( all_refls == NULL ) { ERROR("Failed to select reflections for dataset '%s'\n", result->name); reflist_free(raw_refl); reflist_free(raw_part1); reflist_free(raw_part2); return 1; } fom_select_reflection_pairs(raw_part1, raw_part2, &part1, &part2, cell, sym, 0, 1e10/min_res, 1e10/max_res, min_snr, 0, 0, min_meas); if ( (part1 == NULL) || (part2 == NULL) ) { ERROR("Failed to select reflection pairs for dataset '%s'\n", result->name); reflist_free(all_refls); reflist_free(raw_refl); reflist_free(raw_part1); reflist_free(raw_part2); return 1; } if ( need_ano ) { fom_select_reflections(raw_refl, &all_refls_anom, cell, sym, 1e10/min_res, 1e10/max_res, min_snr, 0, 0, min_meas); if ( all_refls_anom == NULL ) { ERROR("Failed to load dataset '%s'\n", result->name); reflist_free(raw_refl); return 1; } fom_select_reflection_pairs(raw_part1, raw_part2, &part1_anom, &part2_anom, cell, sym, 1, 1e10/min_res, 1e10/max_res, min_snr, 0, 0, min_meas); if ( (part1_anom == NULL) || (part2_anom == NULL) ) { ERROR("Failed to select anomalous reflection pairs " "for dataset '%s'\n", result->name); reflist_free(part1); reflist_free(part2); reflist_free(all_refls); reflist_free(raw_refl); reflist_free(raw_part1); reflist_free(raw_part2); return 1; } } reflist_free(raw_refl); reflist_free(raw_part1); reflist_free(raw_part2); *pall_refls = all_refls; *pall_refls_anom = all_refls_anom; *ppart1 = part1; *ppart2 = part2; *ppart1_anom = part1_anom; *ppart2_anom = part2_anom; *psym = sym; return 0; } static struct fom_context *dispatch_fom(RefList *all_refls, RefList *all_refls_anom, RefList *part1, RefList *part2, RefList *part1_anom, RefList *part2_anom, UnitCell *cell, struct fom_shells *shells, const SymOpList *sym, enum fom_type fom) { if ( fom_is_anomalous(fom) ) { if ( fom_is_comparison(fom) ) { if ( part1_anom == NULL ) return NULL; if ( part2_anom == NULL ) return NULL; return fom_calculate(part1_anom, part2_anom, cell, shells, fom, 1, sym); } else { if ( all_refls_anom == NULL ) return NULL; return fom_calculate(all_refls_anom, NULL, cell, shells, fom, 1, sym); } } else { if ( fom_is_comparison(fom) ) { if ( part1 == NULL ) return NULL; if ( part2 == NULL ) return NULL; return fom_calculate(part1, part2, cell, shells, fom, 1, sym); } else { if ( all_refls == NULL ) return NULL; return fom_calculate(all_refls, NULL, cell, shells, fom, 1, sym); } } } static void fom_export_response_sig(GtkWidget *dialog, gint resp, struct fom_window *f) { char *filename; FILE *fh; int i; if ( resp != GTK_RESPONSE_OK ) { gtk_widget_destroy(dialog); return; } filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); STATUS("Saving to %s\n", filename); fh = fopen(filename, "w"); if ( fh == NULL ) return; fprintf(fh, "\"1/d min/nm^-1\",\"1/d center/nm^-1\",\"1/d max/nm^-1\",\"d center/A\""); for ( i=0; icalc_n_foms; i++ ) { fprintf(fh, ",\"%s\"", fom_name(f->calc_fom_types[i])); } fprintf(fh, "\n"); for ( i=0; icalc_shells->nshells; i++ ) { int j; fprintf(fh, "%f,%f,%f,%f", f->calc_shells->rmins[i]/1e9, fom_shell_centre(f->calc_shells, i)/1e9, f->calc_shells->rmaxs[i]/1e9, 1e10/fom_shell_centre(f->calc_shells, i)); for ( j=0; jcalc_n_foms; j++ ) { fprintf(fh, ",%f", f->calc_fom_values[j][i]); } fprintf(fh, "\n"); } fprintf(fh, "\n\n"); for ( i=0; icalc_n_foms; i++ ) { fprintf(fh, "Overall %s = %f\n", fom_name(f->calc_fom_types[i]), f->calc_fom_overall[i]); } fprintf(fh, "\n\n"); fprintf(fh, "Note: R-factors are given as decimal numbers (usually <1), not as percentages.\n"); fclose(fh); g_free(filename); gtk_widget_destroy(dialog); } static void fom_response_sig(GtkWidget *dialog, gint resp, struct fom_window *f) { if ( resp == GTK_RESPONSE_CLOSE ) { gtk_widget_destroy(dialog); return; } if ( resp == GTK_RESPONSE_ACCEPT ) { GtkWidget *w; w = gtk_file_chooser_dialog_new("Export filename", GTK_WINDOW(f->proj->window), GTK_FILE_CHOOSER_ACTION_SAVE, "Export", GTK_RESPONSE_OK, NULL); gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(w), TRUE); g_signal_connect(G_OBJECT(w), "response", G_CALLBACK(fom_export_response_sig), f); gtk_widget_show_all(w); } } static void update_fom(GtkWidget *widget, struct fom_window *f) { int fom; int need_ano; UnitCell *cell; struct fom_shells *shells; f->proj->fom_res_min = get_float(f->min_res); f->proj->fom_res_max = get_float(f->max_res); f->proj->fom_min_snr = get_float(f->min_snr); f->proj->fom_min_meas = get_uint(f->min_meas); f->proj->fom_nbins = get_uint(f->num_bins); if ( isnan(f->proj->fom_res_min) || isnan(f->proj->fom_res_max) || isnan(f->proj->fom_min_snr) || isinf(f->proj->fom_res_min) || isinf(f->proj->fom_res_max) ) { ERROR("Invalid parameters\n"); return; } /* "Minimum resolution" should be the bigger number */ if ( f->proj->fom_res_min < f->proj->fom_res_max ) { double tmp = f->proj->fom_res_min; f->proj->fom_res_min = f->proj->fom_res_max; f->proj->fom_res_max = tmp; } shells = fom_make_resolution_shells(1e10/f->proj->fom_res_min, 1e10/f->proj->fom_res_max, f->proj->fom_nbins); if ( shells == NULL ) { ERROR("Failed to make resolution shells\n"); return; } f->proj->fom_cell_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(f->cell_chooser)); cell = load_cell_from_file(f->proj->fom_cell_filename); if ( cell == NULL ) { ERROR("Invalid cell file '%s'\n", f->proj->fom_cell_filename); return; } need_ano = anomalous_foms_selected(f); const char *name; struct gui_merge_result *result; SymOpList *sym = NULL; RefList *all_refls = NULL; RefList *all_refls_anom = NULL; RefList *part1 = NULL; RefList *part2 = NULL; RefList *part1_anom = NULL; RefList *part2_anom = NULL; /* Load dataset */ name = gtk_combo_box_get_active_id(GTK_COMBO_BOX(f->input_combo)); result = find_merge_result_by_name(f->proj, name); if ( load_dataset(result, need_ano, cell, f->proj->fom_res_min, f->proj->fom_res_max, f->proj->fom_min_meas, f->proj->fom_min_snr, &sym, &all_refls, &all_refls_anom, &part1, &part2, &part1_anom, &part2_anom) ) { return; } double *shell_centers = malloc(shells->nshells*sizeof(double)); double **fom_values = malloc(f->n_foms*sizeof(double *)); double *overall_values = malloc(f->n_foms*sizeof(double)); enum fom_type *fom_types = malloc(f->n_foms*sizeof(enum fom_type)); int fomi = 0; for ( fom=0; fomn_foms; fom++ ) { struct fom_context *fctx; if ( !fom_selected(f, fom) ) continue; fctx = dispatch_fom(all_refls, all_refls_anom, part1, part2, part1_anom, part2_anom, cell, shells, sym, f->fom_types[fom]); if ( fctx == NULL ) { ERROR("Failed to calculate FoM %i for dataset %s\n", f->fom_types[fom], name); continue; } fom_types[fomi] = f->fom_types[fom]; fom_values[fomi] = make_fom_vals(fctx, shells); overall_values[fomi] = fom_overall_value(fctx); fomi++; } free(f->calc_fom_overall); /* All the other old memory is freed by CrystFELFoMGraph * during the set_data call */ shell_centers = make_shell_centers(shells); crystfel_fom_graph_set_data(CRYSTFEL_FOM_GRAPH(f->graph), shell_centers, shells->nshells, fom_types, fom_values, fomi); f->calc_shells = shells; f->calc_n_foms = fomi; f->calc_fom_types = fom_types; f->calc_fom_values = fom_values; f->calc_fom_overall = overall_values; reflist_free(all_refls); reflist_free(all_refls_anom); free_symoplist(sym); } static GtkWidget *add_item(GtkWidget *menu, const char *text, const char *markup, struct fom_window *f) { GtkWidget *label; GtkWidget *item; item = gtk_check_menu_item_new(); label = gtk_label_new(text); if ( markup != NULL ) { gtk_label_set_markup(GTK_LABEL(label), markup); } gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); gtk_container_add(GTK_CONTAINER(item), label); gtk_widget_show_all(item); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(update_fom), f); return item; } static void add_separator(GtkWidget *menu) { GtkWidget *item; item = gtk_separator_menu_item_new(); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); gtk_widget_show_all(item); } static GtkWidget *make_fom_menu(struct fom_window *fom) { GtkWidget *menu; GtkWidget *item; menu = gtk_menu_new(); item = gtk_tearoff_menu_item_new(); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); gtk_widget_show_all(item); /* Order of FoMs must match list below */ fom->fom_checkboxes[0] = add_item(menu, "I/σ(I)", NULL, fom); fom->fom_checkboxes[1] = add_item(menu, "Completeness", NULL, fom); fom->fom_checkboxes[2] = add_item(menu, "Redundancy", NULL, fom); add_separator(menu); fom->fom_checkboxes[3] = add_item(menu, "Rsplit", "Rsplit", fom); fom->fom_checkboxes[4] = add_item(menu, "CC", "CC½", fom); fom->fom_checkboxes[5] = add_item(menu, "CC*", "CC*", fom); add_separator(menu); fom->fom_checkboxes[6] = add_item(menu, "CCano", "CCano", fom); fom->fom_checkboxes[7] = add_item(menu, "Rano", "Rano", fom); fom->fom_checkboxes[8] = add_item(menu, "Rano ÷ Rsplit", "Rano ÷ Rsplit", fom); fom->fom_checkboxes[9] = add_item(menu, "RMS anomalous correlation ratio", NULL, fom); add_separator(menu); fom->fom_checkboxes[10] = add_item(menu, "Fraction of differences within 1σ(I)", NULL, fom); fom->fom_checkboxes[11] = add_item(menu, "Fraction of differences within 2σ(I)", NULL, fom); /* Order must match the list above */ fom->fom_types[0] = FOM_SNR; fom->fom_types[1] = FOM_COMPLETENESS; fom->fom_types[2] = FOM_REDUNDANCY; fom->fom_types[3] = FOM_RSPLIT; fom->fom_types[4] = FOM_CC; fom->fom_types[5] = FOM_CCSTAR; fom->fom_types[6] = FOM_CCANO; fom->fom_types[7] = FOM_RANO; fom->fom_types[8] = FOM_RANORSPLIT; fom->fom_types[9] = FOM_CRDANO; fom->fom_types[10] = FOM_D1SIG; fom->fom_types[11] = FOM_D2SIG; fom->n_foms = 12; return menu; } static int result_res_range(struct gui_merge_result *result, UnitCell *cell, double *lowres, double *highres) { RefList *rl; double rmin, rmax; rl = read_reflections(result->hkl); if ( rl == NULL ) return 1; resolution_limits(rl, cell, &rmin, &rmax); reflist_free(rl); *lowres = 1e10/rmin; *highres = 1e10/rmax; return 0; } static void res_range_to_data_sig(GtkButton *buton, struct fom_window *f) { const char *name; struct gui_merge_result *result; gchar *cell_filename; UnitCell *cell; char tmp[64]; double lowres; /* Angstroms */ double highres; cell_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(f->cell_chooser)); if ( cell_filename == NULL ) { ERROR("You must choose the unit cell first.\n"); return; } cell = load_cell_from_file(cell_filename); if ( cell == NULL ) { ERROR("Invalid cell file '%s'\n", cell_filename); g_free(cell_filename); return; } g_free(cell_filename); name = gtk_combo_box_get_active_id(GTK_COMBO_BOX(f->input_combo)); result = find_merge_result_by_name(f->proj, name); if ( result_res_range(result, cell, &lowres, &highres) ) return; cell_free(cell); f->proj->fom_res_min = lowres; f->proj->fom_res_max = highres; snprintf(tmp, 64, "%.2f", f->proj->fom_res_min); gtk_entry_set_text(GTK_ENTRY(f->min_res), tmp); snprintf(tmp, 64, "%.2f", f->proj->fom_res_max); gtk_entry_set_text(GTK_ENTRY(f->max_res), tmp); update_fom(NULL, f); } static void cell_file_clear_sig(GtkButton *button, struct fom_window *f) { gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(f->cell_chooser), "(none)"); update_fom(NULL, f); } gint fom_sig(GtkWidget *widget, struct crystfelproject *proj) { GtkWidget *dialog; GtkWidget *content_area; GtkWidget *vbox; GtkWidget *hbox; GtkWidget *label; GtkWidget *button; char tmp[64]; struct fom_window *f; int i; f = malloc(sizeof(struct fom_window)); if ( f == NULL ) return 0; f->proj = proj; f->n_foms = 0; f->calc_fom_overall = NULL; dialog = gtk_dialog_new_with_buttons("Calculate figures of merit", GTK_WINDOW(proj->window), GTK_DIALOG_DESTROY_WITH_PARENT, "Export", GTK_RESPONSE_ACCEPT, "Close", GTK_RESPONSE_CLOSE, NULL); g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(fom_response_sig), f); vbox = gtk_vbox_new(FALSE, 0.0); content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); gtk_box_pack_start(GTK_BOX(content_area), GTK_WIDGET(vbox), TRUE, TRUE, 0.0); gtk_container_set_border_width(GTK_CONTAINER(content_area), 8); hbox = gtk_hbox_new(FALSE, 0.0); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, FALSE, 4.0); label = gtk_label_new("Results to show:"); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 4.0); f->input_combo = gtk_combo_box_text_new(); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(f->input_combo), FALSE, FALSE, 4.0); for ( i=0; in_merge_results; i++ ) { gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(f->input_combo), proj->merge_results[i].name, proj->merge_results[i].name); } gtk_combo_box_set_active(GTK_COMBO_BOX(f->input_combo), proj->n_merge_results-1); g_signal_connect(G_OBJECT(f->input_combo), "changed", G_CALLBACK(update_fom), f); label = gtk_label_new("Figures of merit to show:"); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 4.0); button = gtk_menu_button_new(); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 4.0); gtk_menu_button_set_popup(GTK_MENU_BUTTON(button), make_fom_menu(f)); /* Unit cell */ hbox = gtk_hbox_new(FALSE, 0.0); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, FALSE, 4.0); label = gtk_label_new("Unit cell file:"); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 4.0); f->cell_chooser = gtk_file_chooser_button_new("Unit cell file", GTK_FILE_CHOOSER_ACTION_OPEN); gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(f->cell_chooser), TRUE); if ( proj->fom_cell_filename != NULL ) { gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(f->cell_chooser), proj->fom_cell_filename); } gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(f->cell_chooser), FALSE, FALSE, 4.0); g_signal_connect(G_OBJECT(f->cell_chooser), "file-set", G_CALLBACK(update_fom), f); button = gtk_button_new_from_icon_name("edit-clear", GTK_ICON_SIZE_BUTTON); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(cell_file_clear_sig), f); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 4.0); /* Resolution range */ hbox = gtk_hbox_new(FALSE, 0.0); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, FALSE, 4.0); label = gtk_label_new("Resolution range:"); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 4.0); f->min_res = gtk_entry_new(); gtk_entry_set_width_chars(GTK_ENTRY(f->min_res), 6); snprintf(tmp, 64, "%.2f", proj->fom_res_min); gtk_entry_set_text(GTK_ENTRY(f->min_res), tmp); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(f->min_res), FALSE, FALSE, 4.0); g_signal_connect(G_OBJECT(f->min_res), "activate", G_CALLBACK(update_fom), f); label = gtk_label_new("to"); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 4.0); f->max_res = gtk_entry_new(); gtk_entry_set_width_chars(GTK_ENTRY(f->max_res), 4); snprintf(tmp, 64, "%.2f", proj->fom_res_max); gtk_entry_set_text(GTK_ENTRY(f->max_res), tmp); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(f->max_res), FALSE, FALSE, 4.0); g_signal_connect(G_OBJECT(f->max_res), "activate", G_CALLBACK(update_fom), f); label = gtk_label_new("Å"); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 4.0); button = gtk_button_new_with_label("Reset to entire data"); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 4.0); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(res_range_to_data_sig), f); /* Number of resolution bins */ hbox = gtk_hbox_new(FALSE, 0.0); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, FALSE, 4.0); label = gtk_label_new("Number of resolution bins:"); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 4.0); f->num_bins = gtk_entry_new(); gtk_entry_set_width_chars(GTK_ENTRY(f->num_bins), 4); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(f->num_bins), FALSE, FALSE, 4.0); snprintf(tmp, 64, "%i", proj->fom_nbins); gtk_entry_set_text(GTK_ENTRY(f->num_bins), tmp); g_signal_connect(G_OBJECT(f->num_bins), "activate", G_CALLBACK(update_fom), f); /* Minimum I/sigI */ label = gtk_label_new("Minimum I/sigI:"); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 4.0); f->min_snr = gtk_entry_new(); gtk_entry_set_width_chars(GTK_ENTRY(f->min_snr), 4); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(f->min_snr), FALSE, FALSE, 4.0); g_signal_connect(G_OBJECT(f->min_snr), "activate", G_CALLBACK(update_fom), f); /* Min measurements per reflection */ hbox = gtk_hbox_new(FALSE, 0.0); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, FALSE, 4.0); snprintf(tmp, 64, "%.2f", proj->fom_min_snr); gtk_entry_set_text(GTK_ENTRY(f->min_snr), tmp); label = gtk_label_new("Minimum number of measurements per reflection:"); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 4.0); f->min_meas = gtk_entry_new(); gtk_entry_set_width_chars(GTK_ENTRY(f->min_meas), 4); gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(f->min_meas), FALSE, FALSE, 4.0); snprintf(tmp, 64, "%i", proj->fom_min_meas); gtk_entry_set_text(GTK_ENTRY(f->min_meas), tmp); g_signal_connect(G_OBJECT(f->min_meas), "activate", G_CALLBACK(update_fom), f); f->graph = crystfel_fom_graph_new(); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(f->graph), TRUE, TRUE, 4.0); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(f->fom_checkboxes[3]), TRUE); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(f->fom_checkboxes[5]), TRUE); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE); gtk_widget_show_all(dialog); return FALSE; }