The minimum patch size was a flat floor, so a crystal that covers one or two grid points was thrown away however strong its diffraction. It is now a floor OR a piece of evidence: a patch is reported when it has min_blob_cells cells, or when its best cell reaches decisive_protein_score. One condition, and both halves are parameters. The bar is the patch PEAK, not the patch mean. A two-cell patch with one strong cell and one marginal one is the case this exists for, and the mean averages exactly that evidence away. Over the 67 labelled rasters the two populations do not overlap: no water raster reaches a peak of 0.15 and no ice raster reaches 0.50, while the weakest protein raster peaks at 0.67 - so 0.6, the middle of that gap, is the default. The peak is reported beside the mean, in the table and in the JSON, so an admission can be checked against the number that decided it. max_crystals caps the returned list after the sort, best first; 0, the default, is all of them, because a crystal that was found and then dropped is information the caller cannot get back. The four parameters now travel as one GridScanAnalysisParameters, which is the shape the GridScanAnalysisSettings class in common/ is to take: when it lands it replaces this struct in the signature and nothing else changes. On the corpus this reaches 17/17 protein loops (it was 16/17 - the miss was a crystal covering two grid points of a 4x4 raster) with water still 0/4 and ice still 0/3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
44 lines
2.4 KiB
C++
44 lines
2.4 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
// One crystal found in a grid scan. Positions are in the display grid of GridScanSettings -
|
|
// column 0 is the lowest x, row 0 the lowest y, whatever direction the stage actually moved in -
|
|
// so they match the per-image positions the scan writes with GetXContainer_m/GetYContainer_m.
|
|
struct GridScanCrystal {
|
|
float nx = 0, ny = 0; // centre, grid coords, fractional, 0-based
|
|
float x_um = 0, y_um = 0; // centre, signed offset from centre of cell (0,0), along grid axes
|
|
int64_t image_number = -1; // nearest COLLECTED image, for the DAQ to address
|
|
// Extent along the crystal's own principal axes. major_um >= minor_um always, and
|
|
// angle_deg points along major_um - so a consumer can draw a major_um by minor_um frame
|
|
// rotated by angle_deg without checking which of the two is the longer.
|
|
float major_um = 0, minor_um = 0;
|
|
// Major axis from the +x grid axis, counter-clockwise. This is an AXIS, not a direction, so it
|
|
// lives in [0,180) and wraps there: 179 deg is adjacent to 0 deg, and code comparing two angles
|
|
// has to fold the difference into [0,90]. On a round blob the axis is arbitrary and the value is
|
|
// whatever the numerics produced - major_um/minor_um near 1 is what says so.
|
|
float angle_deg = 0;
|
|
float score = 0, ice_score = 0; // 0-1, the patch MEAN
|
|
// The best protein score in the patch. The mean above ranks the crystals; this says how strong
|
|
// the evidence in the patch ever gets, and it is what admits a patch too small to be a shape
|
|
// (AnalyzeGridScan). Reported so that admission can be checked against the number that decided it.
|
|
float peak_score = 0;
|
|
float res_A = NAN; // robust best resolution in the blob, NaN if none was measured
|
|
int64_t n_images = 0;
|
|
};
|
|
|
|
// Crystals found in one completed grid scan, sorted by score descending: element 0 is the one to
|
|
// collect. Empty when the raster hit nothing.
|
|
struct GridScanResult {
|
|
std::vector<GridScanCrystal> crystals;
|
|
|
|
// The beam the sizes above were measured with, along the grid axes. The crystal extents still
|
|
// contain it (see AnalyzeGridScan), so this says what a consumer has to take back out.
|
|
float beam_size_x_um = 0, beam_size_y_um = 0;
|
|
};
|