/* * detector.c * * Detector properties * * (c) 2006-2011 Thomas White * * Part of CrystFEL - crystallography with a FEL * */ #include #include #include #include #include #include "image.h" #include "utils.h" #include "diffraction.h" #include "detector.h" #include "beam-parameters.h" #include "hdf5-file.h" static int atob(const char *a) { if ( strcasecmp(a, "true") == 0 ) return 1; if ( strcasecmp(a, "false") == 0 ) return 0; return atoi(a); } static int dir_conv(const char *a, double *sx, double *sy) { if ( strcmp(a, "-x") == 0 ) { *sx = -1; *sy = 0; return 0; } if ( strcmp(a, "x") == 0 ) { *sx = 1; *sy = 0; return 0; } if ( strcmp(a, "+x") == 0 ) { *sx = 1; *sy = 0; return 0; } if ( strcmp(a, "-y") == 0 ) { *sx = 0; *sy = -1; return 0; } if ( strcmp(a, "y") == 0 ) { *sx = 0; *sy = 1; return 0; } if ( strcmp(a, "+y") == 0 ) { *sx = 0; *sy = 1; return 0; } return 1; } struct rvec get_q(struct image *image, double fs, double ss, double *ttp, double k) { struct rvec q; double twotheta, r, az; double rx, ry; struct panel *p; double xs, ys; /* Determine which panel to use */ const unsigned int x = fs; const unsigned int y = ss; p = find_panel(image->det, x, y); assert(p != NULL); /* Convert xs and ys, which are in fast scan/slow scan coordinates, * to x and y */ xs = (fs-(double)p->min_fs)*p->fsx + (ss-(double)p->min_ss)*p->ssx; ys = (fs-(double)p->min_fs)*p->fsy + (ss-(double)p->min_ss)*p->ssy; rx = (xs + p->cnx) / p->res; ry = (ys + p->cny) / p->res; /* Calculate q-vector for this sub-pixel */ r = sqrt(pow(rx, 2.0) + pow(ry, 2.0)); twotheta = atan2(r, p->clen); az = atan2(ry, rx); if ( ttp != NULL ) *ttp = twotheta; q.u = k * sin(twotheta)*cos(az); q.v = k * sin(twotheta)*sin(az); q.w = k * (cos(twotheta) - 1.0); return q; } int in_bad_region(struct detector *det, double fs, double ss) { double rx, ry; struct panel *p; double xs, ys; int i; /* Determine which panel to use */ const unsigned int x = fs; const unsigned int y = ss; p = find_panel(det, x, y); /* No panel found -> definitely bad! */ if ( p == NULL ) return 1; /* Convert xs and ys, which are in fast scan/slow scan coordinates, * to x and y */ xs = (fs-(double)p->min_fs)*p->fsx + (ss-(double)p->min_ss)*p->ssx; ys = (fs-(double)p->min_fs)*p->fsy + (ss-(double)p->min_ss)*p->ssy; rx = (xs + p->cnx) / p->res; ry = (ys + p->cny) / p->res; for ( i=0; in_bad; i++ ) { struct badregion *b = &det->bad[i]; if ( rx < b->min_x ) continue; if ( rx > b->max_x ) continue; if ( ry < b->min_y ) continue; if ( ry > b->max_y ) continue; return 1; } return 0; } double get_tt(struct image *image, double fs, double ss) { double r, rx, ry; struct panel *p; double xs, ys; p = find_panel(image->det, fs, ss); /* Convert xs and ys, which are in fast scan/slow scan coordinates, * to x and y */ xs = (fs-p->min_fs)*p->fsx + (ss-p->min_ss)*p->ssx; ys = (fs-p->min_fs)*p->fsy + (ss-p->min_ss)*p->ssy; rx = (xs + p->cnx) / p->res; ry = (ys + p->cny) / p->res; r = sqrt(pow(rx, 2.0) + pow(ry, 2.0)); return atan2(r, p->clen); } void record_image(struct image *image, int do_poisson) { int x, y; double total_energy, energy_density; double ph_per_e; double area; double max_tt = 0.0; int n_inf1 = 0; int n_neg1 = 0; int n_nan1 = 0; int n_inf2 = 0; int n_neg2 = 0; int n_nan2 = 0; /* How many photons are scattered per electron? */ area = M_PI*pow(image->beam->beam_radius, 2.0); total_energy = image->beam->fluence * ph_lambda_to_en(image->lambda); energy_density = total_energy / area; ph_per_e = (image->beam->fluence /area) * pow(THOMSON_LENGTH, 2.0); STATUS("Fluence = %8.2e photons, " "Energy density = %5.3f kJ/cm^2, " "Total energy = %5.3f microJ\n", image->beam->fluence, energy_density/1e7, total_energy*1e6); for ( x=0; xwidth; x++ ) { for ( y=0; yheight; y++ ) { double counts; double cf; double intensity, sa; double pix_area, Lsq; double xs, ys, rx, ry; double dsq, proj_area; struct panel *p; intensity = (double)image->data[x + image->width*y]; if ( isinf(intensity) ) n_inf1++; if ( intensity < 0.0 ) n_neg1++; if ( isnan(intensity) ) n_nan1++; p = find_panel(image->det, x, y); /* Area of one pixel */ pix_area = pow(1.0/p->res, 2.0); Lsq = pow(p->clen, 2.0); /* Area of pixel as seen from crystal (approximate) */ proj_area = pix_area * cos(image->twotheta[x + image->width*y]); /* Calculate distance from crystal to pixel */ xs = (x-p->min_fs)*p->fsx + (y-p->min_ss)*p->ssx; ys = (x-p->min_fs)*p->fsy + (y-p->min_ss)*p->ssy; rx = (xs + p->cnx) / p->res; ry = (ys + p->cny) / p->res; dsq = sqrt(pow(rx, 2.0) + pow(ry, 2.0)); /* Projected area of pixel divided by distance squared */ sa = proj_area / (dsq + Lsq); if ( do_poisson ) { counts = poisson_noise(intensity * ph_per_e * sa * image->beam->dqe ); } else { cf = intensity * ph_per_e * sa * image->beam->dqe; counts = cf; } image->data[x + image->width*y] = counts * image->beam->adu_per_photon; /* Sanity checks */ if ( isinf(image->data[x+image->width*y]) ) n_inf2++; if ( isnan(image->data[x+image->width*y]) ) n_nan2++; if ( image->data[x+image->width*y] < 0.0 ) n_neg2++; if ( image->twotheta[x + image->width*y] > max_tt ) { max_tt = image->twotheta[x + image->width*y]; } } progress_bar(x, image->width-1, "Post-processing"); } STATUS("Max 2theta = %.2f deg, min d = %.2f nm\n", rad2deg(max_tt), (image->lambda/(2.0*sin(max_tt/2.0)))/1e-9); double tt_side = image->twotheta[(image->width/2)+image->width*0]; STATUS("At middle of bottom edge: %.2f deg, min d = %.2f nm\n", rad2deg(tt_side), (image->lambda/(2.0*sin(tt_side/2.0)))/1e-9); tt_side = image->twotheta[0+image->width*(image->height/2)]; STATUS("At middle of left edge: %.2f deg, min d = %.2f nm\n", rad2deg(tt_side), (image->lambda/(2.0*sin(tt_side/2.0)))/1e-9); STATUS("Halve the d values to get the voxel size for a synthesis.\n"); if ( n_neg1 + n_inf1 + n_nan1 + n_neg2 + n_inf2 + n_nan2 ) { ERROR("WARNING: The raw calculation produced %i negative" " values, %i infinities and %i NaNs.\n", n_neg1, n_inf1, n_nan1); ERROR("WARNING: After processing, there were %i negative" " values, %i infinities and %i NaNs.\n", n_neg2, n_inf2, n_nan2); } } struct panel *find_panel(struct detector *det, double fs, double ss) { int p; for ( p=0; pn_panels; p++ ) { if ( (fs >= det->panels[p].min_fs) && (fs <= det->panels[p].max_fs) && (ss >= det->panels[p].min_ss) && (ss <= det->panels[p].max_ss) ) { return &det->panels[p]; } } return NULL; } void fill_in_values(struct detector *det, struct hdfile *f) { int i; for ( i=0; in_panels; i++ ) { struct panel *p = &det->panels[i]; if ( p->clen_from != NULL ) { p->clen = get_value(f, p->clen_from) * 1.0e-3; free(p->clen_from); p->clen_from = NULL; } } } static struct panel *new_panel(struct detector *det, const char *name) { struct panel *new; det->n_panels++; det->panels = realloc(det->panels, det->n_panels*sizeof(struct panel)); new = &det->panels[det->n_panels-1]; new->min_fs = -1; new->min_ss = -1; new->max_fs = -1; new->max_ss = -1; new->cnx = NAN; new->cny = NAN; new->clen = -1.0; new->res = -1.0; new->badrow = '-'; new->no_index = 0; new->peak_sep = 50.0; new->fsx = 1.0; new->fsy = 0.0; new->ssx = 0.0; new->ssy = 1.0; strcpy(new->name, name); return new; } static struct badregion *new_bad_region(struct detector *det, const char *name) { struct badregion *new; det->n_bad++; det->bad = realloc(det->bad, det->n_bad*sizeof(struct badregion)); new = &det->bad[det->n_bad-1]; new->min_x = NAN; new->max_x = NAN; new->min_y = NAN; new->max_y = NAN; strcpy(new->name, name); return new; } static struct panel *find_panel_by_name(struct detector *det, const char *name) { int i; for ( i=0; in_panels; i++ ) { if ( strcmp(det->panels[i].name, name) == 0 ) { return &det->panels[i]; } } return NULL; } static struct badregion *find_bad_region_by_name(struct detector *det, const char *name) { int i; for ( i=0; in_bad; i++ ) { if ( strcmp(det->bad[i].name, name) == 0 ) { return &det->bad[i]; } } return NULL; } static int parse_field_for_panel(struct panel *panel, const char *key, const char *val) { int reject = 0; if ( strcmp(key, "min_fs") == 0 ) { panel->min_fs = atof(val); } else if ( strcmp(key, "max_fs") == 0 ) { panel->max_fs = atof(val); } else if ( strcmp(key, "min_ss") == 0 ) { panel->min_ss = atof(val); } else if ( strcmp(key, "max_ss") == 0 ) { panel->max_ss = atof(val); } else if ( strcmp(key, "corner_x") == 0 ) { panel->cnx = atof(val); } else if ( strcmp(key, "corner_y") == 0 ) { panel->cny = atof(val); } else if ( strcmp(key, "clen") == 0 ) { char *end; double v = strtod(val, &end); if ( end == val ) { /* This means "fill in later" */ panel->clen = -1.0; panel->clen_from = strdup(val); } else { panel->clen = v; panel->clen_from = NULL; } } else if ( strcmp(key, "res") == 0 ) { panel->res = atof(val); } else if ( strcmp(key, "peak_sep") == 0 ) { panel->peak_sep = atof(val); } else if ( strcmp(key, "badrow_direction") == 0 ) { panel->badrow = val[0]; /* First character only */ if ( (panel->badrow != 'x') && (panel->badrow != 'y') && (panel->badrow != 'f') && (panel->badrow != 's') && (panel->badrow != '-') ) { ERROR("badrow_direction must be x, y, f, s or '-'\n"); ERROR("Assuming '-'\n."); panel->badrow = '-'; } if ( panel->badrow == 'x' ) panel->badrow = 'f'; if ( panel->badrow == 'y' ) panel->badrow = 's'; } else if ( strcmp(key, "no_index") == 0 ) { panel->no_index = atob(val); } else if ( strcmp(key, "fs") == 0 ) { if ( dir_conv(val, &panel->fsx, &panel->fsy) != 0 ) { ERROR("Invalid fast scan direction '%s'\n", val); reject = 1; } } else if ( strcmp(key, "ss") == 0 ) { if ( dir_conv(val, &panel->ssx, &panel->ssy) != 0 ) { ERROR("Invalid slow scan direction '%s'\n", val); reject = 1; } } else { ERROR("Unrecognised field '%s'\n", key); } return reject; } static int parse_field_bad(struct badregion *panel, const char *key, const char *val) { int reject = 0; if ( strcmp(key, "min_x") == 0 ) { panel->min_x = atof(val); } else if ( strcmp(key, "max_x") == 0 ) { panel->max_x = atof(val); } else if ( strcmp(key, "min_y") == 0 ) { panel->min_y = atof(val); } else if ( strcmp(key, "max_y") == 0 ) { panel->max_y = atof(val); } else { ERROR("Unrecognised field '%s'\n", key); } return reject; } static void parse_toplevel(struct detector *det, const char *key, const char *val) { if ( strcmp(key, "mask") == 0 ) { det->mask = strdup(val); } else if ( strcmp(key, "mask_bad") == 0 ) { char *end; double v = strtod(val, &end); if ( end != val ) { det->mask_bad = v; } } else if ( strcmp(key, "mask_good") == 0 ) { char *end; double v = strtod(val, &end); if ( end != val ) { det->mask_good = v; } } else { ERROR("Unrecognised top level field '%s'\n", key); } } struct detector *get_detector_geometry(const char *filename) { FILE *fh; struct detector *det; char *rval; char **bits; int i; int reject = 0; int x, y, max_fs, max_ss; fh = fopen(filename, "r"); if ( fh == NULL ) return NULL; det = malloc(sizeof(struct detector)); if ( det == NULL ) { fclose(fh); return NULL; } det->n_panels = 0; det->panels = NULL; det->n_bad = 0; det->bad = NULL; do { int n1, n2; char **path; char line[1024]; struct badregion *badregion = NULL; struct panel *panel = NULL; char *key; rval = fgets(line, 1023, fh); if ( rval == NULL ) break; chomp(line); n1 = assplode(line, " \t", &bits, ASSPLODE_NONE); if ( n1 < 3 ) { for ( i=0; in_panels == -1 ) { ERROR("No panel descriptions in geometry file.\n"); fclose(fh); free(det); return NULL; } max_fs = 0; max_ss = 0; for ( i=0; in_panels; i++ ) { if ( det->panels[i].min_fs < 0 ) { ERROR("Please specify the minimum FS coordinate for" " panel %s\n", det->panels[i].name); reject = 1; } if ( det->panels[i].max_fs < 0 ) { ERROR("Please specify the maximum FS coordinate for" " panel %s\n", det->panels[i].name); reject = 1; } if ( det->panels[i].min_ss < 0 ) { ERROR("Please specify the minimum SS coordinate for" " panel %s\n", det->panels[i].name); reject = 1; } if ( det->panels[i].max_ss < 0 ) { ERROR("Please specify the maximum SS coordinate for" " panel %s\n", det->panels[i].name); reject = 1; } if ( isnan(det->panels[i].cnx) ) { ERROR("Please specify the corner X coordinate for" " panel %s\n", det->panels[i].name); reject = 1; } if ( isnan(det->panels[i].cny) ) { ERROR("Please specify the corner Y coordinate for" " panel %s\n", det->panels[i].name); reject = 1; } if ( (det->panels[i].clen < 0.0) && (det->panels[i].clen_from == NULL) ) { ERROR("Please specify the camera length for" " panel %s\n", det->panels[i].name); reject = 1; } if ( det->panels[i].res < 0 ) { ERROR("Please specify the resolution for" " panel %s\n", det->panels[i].name); reject = 1; } /* It's OK if the badrow direction is '0' */ /* It's not a problem if "no_index" is still zero */ /* The default peak_sep is OK (maybe) */ /* The default transformation matrix is at least valid */ if ( det->panels[i].max_fs > max_fs ) { max_fs = det->panels[i].max_fs; } if ( det->panels[i].max_ss > max_ss ) { max_ss = det->panels[i].max_ss; } } for ( i=0; in_bad; i++ ) { if ( isnan(det->bad[i].min_x) ) { ERROR("Please specify the minimum x coordinate for" " bad region %s\n", det->bad[i].name); reject = 1; } if ( isnan(det->bad[i].min_y) ) { ERROR("Please specify the minimum y coordinate for" " bad region %s\n", det->bad[i].name); reject = 1; } if ( isnan(det->bad[i].max_x) ) { ERROR("Please specify the maximum x coordinate for" " bad region %s\n", det->bad[i].name); reject = 1; } if ( isnan(det->bad[i].max_y) ) { ERROR("Please specify the maximum y coordinate for" " bad region %s\n", det->bad[i].name); reject = 1; } } for ( x=0; x<=max_fs; x++ ) { for ( y=0; y<=max_ss; y++ ) { if ( find_panel(det, x, y) == NULL ) { ERROR("Detector geometry invalid: contains gaps.\n"); reject = 1; goto out; } } } out: det->max_fs = max_fs; det->max_ss = max_ss; /* Calculate matrix inverse */ for ( i=0; in_panels; i++ ) { struct panel *p; double d; p = &det->panels[i]; if ( p->fsx*p->ssy == p->ssx*p->fsy ) { ERROR("Panel %i transformation singular.\n", i); reject = 1; } d = (double)p->fsx*p->ssy - p->ssx*p->fsy; p->xfs = p->ssy / d; p->yfs = -p->ssx / d; p->xss = -p->fsy / d; p->yss = p->fsx / d; } if ( reject ) return NULL; fclose(fh); return det; } void free_detector_geometry(struct detector *det) { free(det->panels); free(det->bad); free(det); } struct detector *copy_geom(const struct detector *in) { struct detector *out; int i; out = malloc(sizeof(struct detector)); memcpy(out, in, sizeof(struct detector)); out->panels = malloc(out->n_panels * sizeof(struct panel)); memcpy(out->panels, in->panels, out->n_panels * sizeof(struct panel)); out->bad = malloc(out->n_bad * sizeof(struct badregion)); memcpy(out->bad, in->bad, out->n_bad * sizeof(struct badregion)); for ( i=0; in_panels; i++ ) { struct panel *p; p = &out->panels[i]; if ( p->clen_from != NULL ) { /* Make a copy of the clen_from fields unique to this * copy of the structure. */ p->clen_from = strdup(p->clen_from); } } return out; } struct detector *simple_geometry(const struct image *image) { struct detector *geom; geom = calloc(1, sizeof(struct detector)); geom->n_panels = 1; geom->panels = calloc(1, sizeof(struct panel)); geom->panels[0].min_fs = 0; geom->panels[0].max_fs = image->width-1; geom->panels[0].min_ss = 0; geom->panels[0].max_ss = image->height-1; geom->panels[0].cnx = -image->width / 2.0; geom->panels[0].cny = -image->height / 2.0; geom->panels[0].fsx = 1; geom->panels[0].fsy = 0; geom->panels[0].ssx = 0; geom->panels[0].ssy = 1; geom->panels[0].xfs = 1; geom->panels[0].xss = 0; geom->panels[0].yfs = 0; geom->panels[0].yss = 1; return geom; } int reverse_2d_mapping(double x, double y, double *pfs, double *pss, struct detector *det) { int i; for ( i=0; in_panels; i++ ) { struct panel *p = &det->panels[i]; double cx, cy, fs, ss; /* Get position relative to corner */ cx = x - p->cnx; cy = y - p->cny; /* Reverse the transformation matrix */ fs = cx*p->xfs + cy*p->yfs; ss = cx*p->xss + cy*p->yss; /* In range? */ if ( fs < 0 ) continue; if ( ss < 0 ) continue; if ( fs > (p->max_fs-p->min_fs+1) ) continue; if ( ss > (p->max_ss-p->min_ss+1) ) continue; *pfs = fs + p->min_fs; *pss = ss + p->min_ss; return 0; } return 1; } static void check_extents(struct panel p, double *min_x, double *min_y, double *max_x, double *max_y, double fs, double ss) { double xs, ys, rx, ry; xs = fs*p.fsx + ss*p.ssx; ys = fs*p.fsy + ss*p.ssy; rx = xs + p.cnx; ry = ys + p.cny; if ( rx > *max_x ) *max_x = rx; if ( ry > *max_y ) *max_y = ry; if ( rx < *min_x ) *min_x = rx; if ( ry < *min_y ) *min_y = ry; } double largest_q(struct image *image) { int fs, ss; double ttm = 0.0; double qmax = 0.0; for ( fs=0; fswidth; fs++ ) { for ( ss=0; ssheight; ss++ ) { struct rvec q; double tt; q = get_q(image, fs, ss, &tt, 1.0/image->lambda); if ( tt > ttm ) { qmax = modulus(q.u, q.v, q.w); ttm = tt; } } } return qmax; } void get_pixel_extents(struct detector *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; in_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].max_ss-det->panels[i].min_ss+1); check_extents(det->panels[i], min_x, min_y, max_x, max_y, det->panels[i].max_fs-det->panels[i].min_fs+1, 0.0); check_extents(det->panels[i], min_x, min_y, max_x, max_y, det->panels[i].max_fs-det->panels[i].min_fs+1, det->panels[i].max_ss-det->panels[i].min_ss+1); } }