diff --git a/broker/OpenAPIConvert.cpp b/broker/OpenAPIConvert.cpp index c87b96d7c..2daabccf5 100644 --- a/broker/OpenAPIConvert.cpp +++ b/broker/OpenAPIConvert.cpp @@ -1186,9 +1186,12 @@ org::openapitools::server::model::Analysis_settings Convert(const AnalysisSettin GridScanAnalysisSettings Convert(const org::openapitools::server::model::Grid_scan_analysis_settings &input) { GridScanAnalysisSettings ret; ret.ProteinScoreThreshold(input.getProteinScoreThreshold()); + ret.GrowScoreThreshold(input.getGrowScoreThreshold()); ret.MinBlobCells(input.getMinBlobCells()); ret.DecisiveSingleCellScore(input.getDecisiveSingleCellScore()); - ret.MaxCrystals(input.getMaxCrystals()); + // Absent means no cap, so an unset property must not become a cap of zero. + if (input.maxCrystalsIsSet()) + ret.MaxCrystals(input.getMaxCrystals()); ret.Indexing(input.isIndexing()); return ret; } @@ -1196,9 +1199,11 @@ GridScanAnalysisSettings Convert(const org::openapitools::server::model::Grid_sc org::openapitools::server::model::Grid_scan_analysis_settings Convert(const GridScanAnalysisSettings &input) { org::openapitools::server::model::Grid_scan_analysis_settings ret; ret.setProteinScoreThreshold(input.GetProteinScoreThreshold()); + ret.setGrowScoreThreshold(input.GetGrowScoreThreshold()); ret.setMinBlobCells(input.GetMinBlobCells()); ret.setDecisiveSingleCellScore(input.GetDecisiveSingleCellScore()); - ret.setMaxCrystals(input.GetMaxCrystals()); + if (const auto cap = input.GetMaxCrystals()) + ret.setMaxCrystals(*cap); ret.setIndexing(input.IsIndexing()); return ret; } diff --git a/broker/gen/model/Grid_scan_analysis_settings.cpp b/broker/gen/model/Grid_scan_analysis_settings.cpp index 102cf4565..840cf7b3b 100644 --- a/broker/gen/model/Grid_scan_analysis_settings.cpp +++ b/broker/gen/model/Grid_scan_analysis_settings.cpp @@ -22,9 +22,11 @@ namespace org::openapitools::server::model Grid_scan_analysis_settings::Grid_scan_analysis_settings() { m_Protein_score_threshold = 0.5f; + m_Grow_score_threshold = 0.35f; m_Min_blob_cells = 3L; - m_Decisive_single_cell_score = 0.9f; - m_Max_crystals = 10L; + m_Decisive_single_cell_score = 0.6f; + m_Max_crystals = 0L; + m_Max_crystalsIsSet = false; m_Indexing = true; } @@ -69,6 +71,25 @@ bool Grid_scan_analysis_settings::validate(std::stringstream& msg, const std::st } + /* Grow_score_threshold */ { + const float& value = m_Grow_score_threshold; + const std::string currentValuePath = _pathPrefix + ".growScoreThreshold"; + + + if (value < static_cast(0)) + { + success = false; + msg << currentValuePath << ": must be greater than or equal to 0;"; + } + if (value > static_cast(1)) + { + success = false; + msg << currentValuePath << ": must be less than or equal to 1;"; + } + + } + + /* Min_blob_cells */ { const int64_t& value = m_Min_blob_cells; const std::string currentValuePath = _pathPrefix + ".minBlobCells"; @@ -101,8 +122,8 @@ bool Grid_scan_analysis_settings::validate(std::stringstream& msg, const std::st } - - /* Max_crystals */ { + if (maxCrystalsIsSet()) + { const int64_t& value = m_Max_crystals; const std::string currentValuePath = _pathPrefix + ".maxCrystals"; @@ -126,14 +147,17 @@ bool Grid_scan_analysis_settings::operator==(const Grid_scan_analysis_settings& (getProteinScoreThreshold() == rhs.getProteinScoreThreshold()) && + (getGrowScoreThreshold() == rhs.getGrowScoreThreshold()) + && + (getMinBlobCells() == rhs.getMinBlobCells()) && (getDecisiveSingleCellScore() == rhs.getDecisiveSingleCellScore()) && - (getMaxCrystals() == rhs.getMaxCrystals()) - && + + ((!maxCrystalsIsSet() && !rhs.maxCrystalsIsSet()) || (maxCrystalsIsSet() && rhs.maxCrystalsIsSet() && getMaxCrystals() == rhs.getMaxCrystals())) && (isIndexing() == rhs.isIndexing()) @@ -150,9 +174,11 @@ void to_json(nlohmann::json& j, const Grid_scan_analysis_settings& o) { j = nlohmann::json::object(); j["protein_score_threshold"] = o.m_Protein_score_threshold; + j["grow_score_threshold"] = o.m_Grow_score_threshold; j["min_blob_cells"] = o.m_Min_blob_cells; j["decisive_single_cell_score"] = o.m_Decisive_single_cell_score; - j["max_crystals"] = o.m_Max_crystals; + if(o.maxCrystalsIsSet()) + j["max_crystals"] = o.m_Max_crystals; j["indexing"] = o.m_Indexing; } @@ -160,9 +186,14 @@ void to_json(nlohmann::json& j, const Grid_scan_analysis_settings& o) void from_json(const nlohmann::json& j, Grid_scan_analysis_settings& o) { j.at("protein_score_threshold").get_to(o.m_Protein_score_threshold); + j.at("grow_score_threshold").get_to(o.m_Grow_score_threshold); j.at("min_blob_cells").get_to(o.m_Min_blob_cells); j.at("decisive_single_cell_score").get_to(o.m_Decisive_single_cell_score); - j.at("max_crystals").get_to(o.m_Max_crystals); + if(j.find("max_crystals") != j.end()) + { + j.at("max_crystals").get_to(o.m_Max_crystals); + o.m_Max_crystalsIsSet = true; + } j.at("indexing").get_to(o.m_Indexing); } @@ -175,6 +206,14 @@ void Grid_scan_analysis_settings::setProteinScoreThreshold(float const value) { m_Protein_score_threshold = value; } +float Grid_scan_analysis_settings::getGrowScoreThreshold() const +{ + return m_Grow_score_threshold; +} +void Grid_scan_analysis_settings::setGrowScoreThreshold(float const value) +{ + m_Grow_score_threshold = value; +} int64_t Grid_scan_analysis_settings::getMinBlobCells() const { return m_Min_blob_cells; @@ -198,6 +237,15 @@ int64_t Grid_scan_analysis_settings::getMaxCrystals() const void Grid_scan_analysis_settings::setMaxCrystals(int64_t const value) { m_Max_crystals = value; + m_Max_crystalsIsSet = true; +} +bool Grid_scan_analysis_settings::maxCrystalsIsSet() const +{ + return m_Max_crystalsIsSet; +} +void Grid_scan_analysis_settings::unsetMax_crystals() +{ + m_Max_crystalsIsSet = false; } bool Grid_scan_analysis_settings::isIndexing() const { diff --git a/broker/gen/model/Grid_scan_analysis_settings.h b/broker/gen/model/Grid_scan_analysis_settings.h index 2b5a7c553..be2c46fef 100644 --- a/broker/gen/model/Grid_scan_analysis_settings.h +++ b/broker/gen/model/Grid_scan_analysis_settings.h @@ -63,20 +63,27 @@ public: float getProteinScoreThreshold() const; void setProteinScoreThreshold(float const value); /// + /// A patch is grown out to this score once it has started, so a crystal is not broken in two by a single cell that fell just under protein_score_threshold. Growth can never start on its own - a patch that never reaches the seed threshold is discarded - so lowering this cannot turn weak background into a crystal. + /// + float getGrowScoreThreshold() const; + void setGrowScoreThreshold(float const value); + /// /// How many cells above that threshold make a shape rather than a coincidence. Two cells can be the two ends of a single hit lying on a cell boundary; three is the smallest patch that is not. /// int64_t getMinBlobCells() const; void setMinBlobCells(int64_t const value); /// - /// ...unless one cell on its own is decisive. The rule above is about coincidences, and a lone cell scoring near the top of a saturating score is not one - a crystal smaller than the grid step lights exactly one cell, and refusing it would lose precisely the samples a fine raster is run to find. Set well above protein_score_threshold: this admits the obvious case, it does not lower the general threshold by the back door. + /// ...unless one cell on its own is decisive. The rule above is about coincidences, and a lone cell scoring near the top of a saturating score is not one - a crystal smaller than the grid step lights exactly one cell, and refusing it would lose precisely the samples a fine raster is run to find. The bar is the patch PEAK, not its mean. 0.6 is measured: over 67 labelled rasters the peak-score populations do not overlap - no water raster reaches 0.15 and no ice raster 0.50, while the weakest confirmed-protein raster peaks at 0.67 - so 0.6 is the middle of that gap. /// float getDecisiveSingleCellScore() const; void setDecisiveSingleCellScore(float const value); /// - /// Most crystals reported. A raster over a loop full of shards can label dozens of blobs, and past the first few the list is no longer a ranking anyone acts on. Crystals are sorted by score, so this keeps the best. + /// Most crystals reported, best first. ABSENT MEANS NO CAP, which is the default: a crystal that was found and then dropped is information the caller cannot get back. Set it where a loop full of shards would otherwise label dozens of blobs that nobody acts on. /// int64_t getMaxCrystals() const; void setMaxCrystals(int64_t const value); + bool maxCrystalsIsSet() const; + void unsetMax_crystals(); /// /// Whether each raster cell is indexed as well as scored. On by default: a raster runs at up to 100 Hz, which the FFT indexer keeps up with, and it is additive - blobs are still found on the protein score, so indexing changes nothing about which cells are called crystals and only adds what was found in them. The lattice count per cell is the cheapest multi-lattice or cracked-crystal signal there is, and on a fixed-target serial experiment with a known cell a raster that indexes is most of the measurement. Turn it off for a very large raster where the GPU is the constraint. /// @@ -88,12 +95,14 @@ public: protected: float m_Protein_score_threshold; + float m_Grow_score_threshold; + int64_t m_Min_blob_cells; float m_Decisive_single_cell_score; int64_t m_Max_crystals; - + bool m_Max_crystalsIsSet; bool m_Indexing; diff --git a/broker/jfjoch_api.yaml b/broker/jfjoch_api.yaml index 6adf5c891..ffb0ee0fc 100644 --- a/broker/jfjoch_api.yaml +++ b/broker/jfjoch_api.yaml @@ -2769,7 +2769,7 @@ components: - protein_score_threshold - min_blob_cells - decisive_single_cell_score - - max_crystals + - grow_score_threshold - indexing properties: protein_score_threshold: @@ -2781,6 +2781,17 @@ components: description: | A raster cell counts as protein above this. The per-image protein score saturates, so this only has to separate "something diffracted here" from "nothing did". + grow_score_threshold: + type: number + format: float + minimum: 0 + maximum: 1 + default: 0.35 + description: | + A patch is grown out to this score once it has started, so a crystal is not broken in two + by a single cell that fell just under protein_score_threshold. Growth can never start on + its own - a patch that never reaches the seed threshold is discarded - so lowering this + cannot turn weak background into a crystal. min_blob_cells: type: integer format: int64 @@ -2795,22 +2806,23 @@ components: format: float minimum: 0 maximum: 1 - default: 0.9 + default: 0.6 description: | ...unless one cell on its own is decisive. The rule above is about coincidences, and a lone cell scoring near the top of a saturating score is not one - a crystal smaller than the grid step lights exactly one cell, and refusing it would lose precisely the samples a - fine raster is run to find. Set well above protein_score_threshold: this admits the - obvious case, it does not lower the general threshold by the back door. + fine raster is run to find. The bar is the patch PEAK, not its mean. + 0.6 is measured: over 67 labelled rasters the peak-score populations do not overlap - no + water raster reaches 0.15 and no ice raster 0.50, while the weakest confirmed-protein + raster peaks at 0.67 - so 0.6 is the middle of that gap. max_crystals: type: integer format: int64 minimum: 1 - default: 10 description: | - Most crystals reported. A raster over a loop full of shards can label dozens of blobs, and - past the first few the list is no longer a ranking anyone acts on. Crystals are sorted by - score, so this keeps the best. + Most crystals reported, best first. ABSENT MEANS NO CAP, which is the default: a crystal + that was found and then dropped is information the caller cannot get back. Set it where a + loop full of shards would otherwise label dozens of blobs that nobody acts on. indexing: type: boolean default: true diff --git a/broker/redoc-static.html b/broker/redoc-static.html index e32fdfaaf..7d5afb1ab 100644 --- a/broker/redoc-static.html +++ b/broker/redoc-static.html @@ -682,17 +682,23 @@ this API cannot express it. Rotation data is collected here and processed offlin Persistent - not reset by a data collection.

Request Body schema: application/json
protein_score_threshold
required
number <float> [ 0 .. 1 ]
Default: 0.5

A raster cell counts as protein above this. The per-image protein score saturates, so this only has to separate "something diffracted here" from "nothing did".

+
grow_score_threshold
required
number <float> [ 0 .. 1 ]
Default: 0.35

A patch is grown out to this score once it has started, so a crystal is not broken in two +by a single cell that fell just under protein_score_threshold. Growth can never start on +its own - a patch that never reaches the seed threshold is discarded - so lowering this +cannot turn weak background into a crystal.

min_blob_cells
required
integer <int64> >= 1
Default: 3

How many cells above that threshold make a shape rather than a coincidence. Two cells can be the two ends of a single hit lying on a cell boundary; three is the smallest patch that is not.

-
decisive_single_cell_score
required
number <float> [ 0 .. 1 ]
Default: 0.9

...unless one cell on its own is decisive. The rule above is about coincidences, and a +

decisive_single_cell_score
required
number <float> [ 0 .. 1 ]
Default: 0.6

...unless one cell on its own is decisive. The rule above is about coincidences, and a lone cell scoring near the top of a saturating score is not one - a crystal smaller than the grid step lights exactly one cell, and refusing it would lose precisely the samples a -fine raster is run to find. Set well above protein_score_threshold: this admits the -obvious case, it does not lower the general threshold by the back door.

-
max_crystals
required
integer <int64> >= 1
Default: 10

Most crystals reported. A raster over a loop full of shards can label dozens of blobs, and -past the first few the list is no longer a ranking anyone acts on. Crystals are sorted by -score, so this keeps the best.

+fine raster is run to find. The bar is the patch PEAK, not its mean. +0.6 is measured: over 67 labelled rasters the peak-score populations do not overlap - no +water raster reaches 0.15 and no ice raster 0.50, while the weakest confirmed-protein +raster peaks at 0.67 - so 0.6 is the middle of that gap.

+
max_crystals
integer <int64> >= 1

Most crystals reported, best first. ABSENT MEANS NO CAP, which is the default: a crystal +that was found and then dropped is information the caller cannot get back. Set it where a +loop full of shards would otherwise label dozens of blobs that nobody acts on.

indexing
required
boolean
Default: true

Whether each raster cell is indexed as well as scored. On by default: a raster runs at up to 100 Hz, which the FFT indexer keeps up with, and it is additive - blobs are still found on the protein score, so indexing changes nothing about which cells are called crystals @@ -704,10 +710,10 @@ large raster where the GPU is the constraint.

Request samples

Content type
application/json
{
  • "protein_score_threshold": 0.5,
  • "min_blob_cells": 3,
  • "decisive_single_cell_score": 0.9,
  • "max_crystals": 10,
  • "indexing": true
}

Response samples

Content type
application/json
{
  • "msg": "Detector in wrong state",
  • "reason": "WrongDAQState"
}

Get grid scan analysis configuration

Can be done anytime

+
http://localhost:5232/config/grid_scan_analysis

Request samples

Content type
application/json
{
  • "protein_score_threshold": 0.5,
  • "grow_score_threshold": 0.35,
  • "min_blob_cells": 3,
  • "decisive_single_cell_score": 0.6,
  • "max_crystals": 1,
  • "indexing": true
}

Response samples

Content type
application/json
{
  • "msg": "Detector in wrong state",
  • "reason": "WrongDAQState"
}

Get grid scan analysis configuration

Can be done anytime

Responses

Response samples

Content type
application/json
{
  • "protein_score_threshold": 0.5,
  • "min_blob_cells": 3,
  • "decisive_single_cell_score": 0.9,
  • "max_crystals": 10,
  • "indexing": true
}

Change powder calibration settings

This can only be done when detector is Idle, Error or Inactive states. +

http://localhost:5232/config/grid_scan_analysis

Response samples

Content type
application/json
{
  • "protein_score_threshold": 0.5,
  • "grow_score_threshold": 0.35,
  • "min_blob_cells": 3,
  • "decisive_single_cell_score": 0.6,
  • "max_crystals": 1,
  • "indexing": true
}

Change powder calibration settings

This can only be done when detector is Idle, Error or Inactive states. Persistent - not reset by a data collection.

Request Body schema: application/json
calibrant
string

Which calibrant's ring d-spacings the geometry is fitted against. Empty selects the processing program's own default rather than meaning "no calibrant".

@@ -950,7 +956,7 @@ This can only be done when detector is Idle, Error or

Request samples

Content type
application/json
{
  • "box": {
    },
  • "circle": {
    },
  • "azim": {
    }
}

Response samples

Content type
application/json
{
  • "msg": "Detector in wrong state",
  • "reason": "WrongDAQState"
}

Get general statistics

Responses

Response samples

Content type
application/json
{
  • "detector": {
    },
  • "detector_list": {
    },
  • "detector_settings": {
    },
  • "image_format_settings": {
    },
  • "instrument_metadata": {
    },
  • "file_writer_settings": {
    },
  • "data_processing_settings": {
    },
  • "measurement": {
    },
  • "broker": {
    },
  • "fpga": [
    ],
  • "calibration": [
    ],
  • "zeromq_preview": {
    },
  • "zeromq_metadata": {
    },
  • "dark_mask": {
    },
  • "pixel_mask": {
    },
  • "roi": {
    },
  • "az_int": {
    },
  • "buffer": {
    },
  • "indexing": {
    },
  • "bragg_integration": {
    },
  • "analysis": {
    },
  • "grid_scan_analysis": {
    },
  • "calibration_settings": {
    },
  • "image_pusher": {
    }
}

Get data collection statistics

Results of the last data collection

+
http://localhost:5232/statistics

Response samples

Content type
application/json
{
  • "detector": {
    },
  • "detector_list": {
    },
  • "detector_settings": {
    },
  • "image_format_settings": {
    },
  • "instrument_metadata": {
    },
  • "file_writer_settings": {
    },
  • "data_processing_settings": {
    },
  • "measurement": {
    },
  • "broker": {
    },
  • "fpga": [
    ],
  • "calibration": [
    ],
  • "zeromq_preview": {
    },
  • "zeromq_metadata": {
    },
  • "dark_mask": {
    },
  • "pixel_mask": {
    },
  • "roi": {
    },
  • "az_int": {
    },
  • "buffer": {
    },
  • "indexing": {
    },
  • "bragg_integration": {
    },
  • "analysis": {
    },
  • "grid_scan_analysis": {
    },
  • "calibration_settings": {
    },
  • "image_pusher": {
    }
}

Get data collection statistics

Results of the last data collection

Responses