Files
Jungfraujoch/image_analysis/structure_refinement/DensityBlobs.h
T
leonarski_f 2e296e6bed jfjoch_writer: the live refinement's model, maps and blobs are written beside the master file
In broker live mode the refined model, its map coefficients and the difference-map blobs stayed in
the broker for the viewer routes and were lost at the next run. They now go to the writer in the END
message as the placed model and maps already do - EndMessage::refined_model (PlacedModelMessage),
refined_map_coefficients (MtzTable) and blobs (DensityBlobsMessage, one vector per field) - and the
writer writes <prefix>_refined_model.cif (+ .pdb), <prefix>_refined_maps.mtz and <prefix>_blobs.json,
the names the CLI gives them. The blobs' JSON is made by one function (DensityBlobsJson) that the CLI
now writes through as well, so the two files are byte-identical. Optional CBOR fields, no version bump.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit c65220d96a96d2b1897f3c6632c94f9379b670a3)
2026-10-26 15:50:40 +01:00

47 lines
2.5 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <string>
#include <vector>
#include "gemmi/grid.hpp"
#include "gemmi/model.hpp"
#include "../../common/JFJochMessages.h"
// What a model leaves unexplained: the connected regions of positive mFo-DFc density above a cutoff ("blobs"),
// each placed beside the model, measured and given a likely reading - a water, an ion, something ligand-sized,
// or an error of the model itself (a misplaced or missing atom, an alternative conformation).
//
// The readings are rules on what is measured, meant to point a person at the density, not to identify a
// compound: a blob called LIGAND is density of ligand size and shape where the model has nothing, which a
// buffer molecule or an unmodelled side chain can be as well as the compound that was soaked.
struct DensityBlob {
enum class Kind { Water, Ion, Ligand, ModelError, Unassigned };
Kind kind = Kind::Unassigned;
gemmi::Position centroid; // the copy nearest the model
double peak_sigma = 0; // the highest point, in the map's rms
double volume = 0; // A^3 above the cutoff
double score = 0; // the density above the cutoff integrated, sigma A^3
double nearest_atom_distance = 0;
std::string nearest_atom; // e.g. "OG1 THR A42"
int polar_contacts = 0; // N or O of the model at 2.4-3.4 A
int metal_contacts = 0; // N, O or S of the model at 1.8-2.6 A
double anomalous_sigma = 0; // the anomalous difference map at the peak, 0 where there is none
};
const char *DensityBlobKindName(DensityBlob::Kind kind);
// The blobs as the end message carries them to the writer, for <prefix>_blobs.json.
DensityBlobsMessage DensityBlobsToMessage(const std::vector<DensityBlob> &blobs);
// The blobs of `difference` (an mFo-DFc map of the whole cell, rms `rms`) above 3 sigma that peak above 4 sigma,
// strongest (by score) first. `model`: what the map was made from, in the map's cell. `anomalous`: the anomalous
// difference map on any grid of the same cell, or null. Following the flood fill of Coot's unmodelled blobs as
// gemmi implements it (find_blobs_by_flood_fill).
std::vector<DensityBlob> FindDensityBlobs(const gemmi::Grid<float> &difference, double rms, const gemmi::Structure &model,
double d_min, const gemmi::Grid<float> *anomalous = nullptr,
double anomalous_rms = 0);