Skip to content

Commit c692d03

Browse files
Add simple ImageSequence (#37)
Define an ImageSequence, allow updating of models. Move ersatz_uuid function into utils, split into header and implementation.
1 parent 2133013 commit c692d03

10 files changed

Lines changed: 211 additions & 67 deletions

File tree

dx2/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
add_library(
22
dx2
3+
utils.cxx
34
reflection.cxx
45
h5/h5read_processed.cxx
56
h5/h5write.cxx
@@ -9,6 +10,7 @@ add_library(
910
goniometer.cxx
1011
scan.cxx
1112
detector_attenuations.cxx
13+
imagesequence.cxx
1214
)
1315
target_link_libraries(
1416
dx2

dx2/detector.cxx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,12 @@ Detector::Detector(json detector_data) {
255255
}
256256
}
257257

258+
Detector::Detector(std::vector<Panel> panels) {
259+
for (auto it = panels.begin(); it != panels.end(); ++it) {
260+
_panels.push_back(*it);
261+
}
262+
}
263+
258264
json Detector::to_json() const {
259265
json detector_data;
260266
std::vector<json> panels_array;

dx2/imagesequence.cxx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#include "dx2/imagesequence.hpp"
2+
3+
using json = nlohmann::json;
4+
5+
// Constructor for MultiImage formats e.g. h5.
6+
ImageSequence::ImageSequence(std::string filename, int n_images)
7+
: filename_(filename), n_images_(n_images) {
8+
single_file_indices_.reserve(n_images_);
9+
for (std::size_t i = 0; i < n_images_; ++i) {
10+
single_file_indices_.push_back(i);
11+
}
12+
}
13+
14+
// Constructor for non-MultiImage formats e.g. cbf.
15+
ImageSequence::ImageSequence(std::string filename) : filename_(filename) {}
16+
17+
ImageSequence::ImageSequence(json imagesequence_data) {
18+
std::vector<std::string> required_keys = {"template", "__id__"};
19+
for (const auto &key : required_keys) {
20+
if (imagesequence_data.find(key) == imagesequence_data.end()) {
21+
throw std::invalid_argument("Key " + key +
22+
" is missing from the input imageset JSON");
23+
}
24+
}
25+
if (imagesequence_data["__id__"] != std::string("ImageSequence")) {
26+
throw std::runtime_error("Only ImageSequences are supported");
27+
}
28+
filename_ = imagesequence_data["template"];
29+
if (imagesequence_data.find("single_file_indices") !=
30+
imagesequence_data.end()) {
31+
// for non-multimage formats (e.g. non-h5), allow parsing.
32+
json indices = imagesequence_data["single_file_indices"];
33+
single_file_indices_ = {};
34+
if (*(indices.begin()) < 0) {
35+
throw std::runtime_error("Starting file index <0");
36+
}
37+
for (json::iterator it = indices.begin(); it != indices.end(); ++it) {
38+
single_file_indices_.push_back(*it);
39+
}
40+
n_images_ = single_file_indices_.size();
41+
}
42+
imagesequence_data_ =
43+
imagesequence_data; // To propagate during serialization/deserialization.
44+
}
45+
46+
json ImageSequence::to_json() const {
47+
json imageset_data = imagesequence_data_;
48+
imageset_data["__id__"] = "ImageSequence";
49+
imageset_data["template"] = filename_;
50+
if (single_file_indices_.size() > 0) { // i.e. MultiImage formats (h5).
51+
imageset_data["single_file_indices"] = single_file_indices_;
52+
}
53+
// Set defaults and null for now.
54+
std::vector<std::string> optional_keys = {"mask", "gain", "pedestal", "dx",
55+
"dy"};
56+
for (const auto &key : optional_keys) {
57+
if (imagesequence_data_.find(key) == imagesequence_data_.end()) {
58+
imageset_data[key] = nullptr;
59+
}
60+
}
61+
if (imagesequence_data_.find("params") == imagesequence_data_.end()) {
62+
json params;
63+
params["dynamic_shadowing"] = "Auto";
64+
params["multi_panel"] = false;
65+
imageset_data["params"] = params;
66+
}
67+
return imageset_data;
68+
}

dx2/reflection.cxx

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
#include "dx2/reflection.hpp"
7+
#include "dx2/utils.hpp"
78
#include <algorithm>
89
#include <chrono>
910
#include <unordered_set>
@@ -142,32 +143,6 @@ void ReflectionTable::merge_into_set(std::unordered_set<size_t> &set,
142143
const std::vector<size_t> &rows) const {
143144
set.insert(rows.begin(), rows.end());
144145
}
145-
146-
std::string ReflectionTable::ersatz_uuid4() const {
147-
// Generate 16 random bytes
148-
std::array<unsigned char, 16> bytes;
149-
std::random_device rd;
150-
std::uniform_int_distribution<int> dist(0, 255);
151-
for (auto &b : bytes) {
152-
b = static_cast<unsigned char>(dist(rd));
153-
}
154-
155-
// Convert bytes to a single 128-bit hex string (little endian)
156-
std::ostringstream oss;
157-
for (auto it = bytes.rbegin(); it != bytes.rend(); ++it) {
158-
oss << std::hex << std::setw(2) << std::setfill('0')
159-
<< static_cast<int>(*it);
160-
}
161-
std::string hex = oss.str();
162-
163-
// Format as UUID: 8-4-4-4-12
164-
std::ostringstream uuid;
165-
uuid << hex.substr(0, 8) << "-" << hex.substr(8, 4) << "-"
166-
<< hex.substr(12, 4) << "-" << hex.substr(16, 4) << "-"
167-
<< hex.substr(20, 12);
168-
169-
return uuid.str();
170-
}
171146
#pragma endregion
172147

173148
#pragma region Selection Methods

dx2/utils.cxx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#include "dx2/utils.hpp"
2+
#include <Eigen/Dense>
3+
#include <iomanip>
4+
#include <math.h>
5+
#include <random>
6+
#include <sstream>
7+
8+
using Eigen::Vector3d;
9+
10+
double angle_between_vectors_degrees(Vector3d v1, Vector3d v2) {
11+
double l1 = v1.norm();
12+
double l2 = v2.norm();
13+
double dot = v1.dot(v2);
14+
double normdot = dot / (l1 * l2);
15+
if (std::abs(normdot - 1.0) < 1E-6) {
16+
return 0.0;
17+
}
18+
if (std::abs(normdot + 1.0) < 1E-6) {
19+
return 180.0;
20+
}
21+
double angle = std::acos(normdot) * 180.0 / M_PI;
22+
return angle;
23+
}
24+
25+
/**
26+
* @brief Generate a pseudo-random UUID-like identifier.
27+
*
28+
* This function replicates the behaviour of the Python function
29+
* `ersatz_uuid4` from the dxtbx library. It generates a 128-bit
30+
* random value and formats it as a UUID-style string using
31+
* little-endian byte order, without enforcing RFC 4122 compliance.
32+
*
33+
* The output is a 36-character string in the format:
34+
* `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, where each `x` is a
35+
* hexadecimal digit.
36+
*
37+
* @return A string representing the generated UUID-like identifier.
38+
*
39+
* @note This function does not set the version or variant bits as
40+
* specified in RFC 4122. It is intended for internal use where
41+
* uniqueness is sufficient, and compliance with UUID standards
42+
* is unnecessary.
43+
*/
44+
std::string ersatz_uuid4() {
45+
// Generate 16 random bytes
46+
std::array<unsigned char, 16> bytes;
47+
std::random_device rd;
48+
std::uniform_int_distribution<int> dist(0, 255);
49+
for (auto &b : bytes) {
50+
b = static_cast<unsigned char>(dist(rd));
51+
}
52+
53+
// Convert bytes to a single 128-bit hex string (little endian)
54+
std::ostringstream oss;
55+
for (auto it = bytes.rbegin(); it != bytes.rend(); ++it) {
56+
oss << std::hex << std::setw(2) << std::setfill('0')
57+
<< static_cast<int>(*it);
58+
}
59+
std::string hex = oss.str();
60+
61+
// Format as UUID: 8-4-4-4-12
62+
std::ostringstream uuid;
63+
uuid << hex.substr(0, 8) << "-" << hex.substr(8, 4) << "-"
64+
<< hex.substr(12, 4) << "-" << hex.substr(16, 4) << "-"
65+
<< hex.substr(20, 12);
66+
67+
return uuid.str();
68+
}

include/dx2/detector.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ class Detector {
106106
public:
107107
Detector() = default;
108108
Detector(json detector_data);
109+
Detector(std::vector<Panel> panels);
109110
json to_json() const;
110111
std::vector<Panel> panels() const;
111112
std::optional<intersection> get_ray_intersection(const Vector3d &s1) const;

include/dx2/experiment.hpp

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
#include <dx2/crystal.hpp>
55
#include <dx2/detector.hpp>
66
#include <dx2/goniometer.hpp>
7+
#include <dx2/imagesequence.hpp>
78
#include <dx2/scan.hpp>
9+
#include <dx2/utils.hpp>
810
#include <nlohmann/json.hpp>
911

1012
using Eigen::Vector3d;
@@ -22,15 +24,21 @@ template <class BeamType> class Experiment {
2224
Detector &detector();
2325
const Crystal &crystal() const;
2426
void set_crystal(Crystal crystal);
27+
void set_beam(BeamType beam);
28+
void set_scan(Scan scan);
29+
void set_detector(Detector detector);
30+
void set_goniometer(Goniometer goniometer);
31+
void set_imagesequence(ImageSequence imagesequence);
2532
void set_identifier(std::string identifier);
33+
void generate_identifier();
2634

2735
protected:
2836
BeamType _beam{};
2937
Scan _scan{};
3038
Goniometer _goniometer{};
3139
Detector _detector{};
3240
Crystal _crystal{};
33-
json _imageset_json{};
41+
ImageSequence _imagesequence{};
3442
std::string _identifier{};
3543
};
3644

@@ -51,8 +59,9 @@ Experiment<BeamType>::Experiment(json experiment_data) {
5159
this->_goniometer = gonio;
5260
this->_detector = detector;
5361
// Save the imageset json to propagate when saving to file.
54-
json imageset_data = experiment_data["imageset"][0];
55-
this->_imageset_json = imageset_data;
62+
json imagesequence_data = experiment_data["imageset"][0];
63+
ImageSequence imagesequence(imagesequence_data);
64+
this->_imagesequence = imagesequence;
5665
try { // We don't always have a crystal model e.g. before indexing.
5766
json crystal_data = experiment_data["crystal"][0];
5867
Crystal crystal(crystal_data);
@@ -67,7 +76,6 @@ template <class BeamType> json Experiment<BeamType>::to_json() const {
6776
json elist_out; // a list of potentially multiple experiments
6877
elist_out["__id__"] = "ExperimentList";
6978
json expt_out; // our single experiment
70-
// no imageset (for now?).
7179
expt_out["__id__"] = "Experiment";
7280
expt_out["identifier"] = _identifier;
7381
expt_out["beam"] =
@@ -82,7 +90,7 @@ template <class BeamType> json Experiment<BeamType>::to_json() const {
8290
elist_out["goniometer"] = std::array<json, 1>{_goniometer.to_json()};
8391
elist_out["beam"] = std::array<json, 1>{_beam.to_json()};
8492
elist_out["detector"] = std::array<json, 1>{_detector.to_json()};
85-
elist_out["imageset"] = std::array<json, 1>{_imageset_json};
93+
elist_out["imageset"] = std::array<json, 1>{_imagesequence.to_json()};
8694

8795
if (_crystal.get_U_matrix().determinant()) {
8896
expt_out["crystal"] = 0;
@@ -115,15 +123,42 @@ void Experiment<BeamType>::set_crystal(Crystal crystal) {
115123
_crystal = crystal;
116124
}
117125

126+
template <class BeamType> void Experiment<BeamType>::set_beam(BeamType beam) {
127+
_beam = beam;
128+
}
129+
118130
template <class BeamType> BeamType &Experiment<BeamType>::beam() {
119131
return _beam;
120132
}
121133

134+
template <class BeamType> void Experiment<BeamType>::set_scan(Scan scan) {
135+
_scan = scan;
136+
}
137+
138+
template <class BeamType>
139+
void Experiment<BeamType>::set_detector(Detector detector) {
140+
_detector = detector;
141+
}
142+
143+
template <class BeamType>
144+
void Experiment<BeamType>::set_goniometer(Goniometer goniometer) {
145+
_goniometer = goniometer;
146+
}
147+
148+
template <class BeamType>
149+
void Experiment<BeamType>::set_imagesequence(ImageSequence imagesequence) {
150+
_imagesequence = imagesequence;
151+
}
152+
122153
template <class BeamType>
123154
const std::string &Experiment<BeamType>::identifier() const {
124155
return _identifier;
125156
}
126157
template <class BeamType>
127158
void Experiment<BeamType>::set_identifier(std::string identifier) {
128159
_identifier = identifier;
160+
}
161+
162+
template <class BeamType> void Experiment<BeamType>::generate_identifier() {
163+
_identifier = ersatz_uuid4();
129164
}

include/dx2/imagesequence.hpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
#include <nlohmann/json.hpp>
3+
4+
using json = nlohmann::json;
5+
6+
class ImageSequence {
7+
public:
8+
ImageSequence() = default;
9+
ImageSequence(std::string filename,
10+
int n_images); // Constructor for MultiImage formats e.g. h5.
11+
ImageSequence(
12+
std::string filename); // Constructor for non-MultiImage formats e.g. cbf.
13+
ImageSequence(json imagesequence_data);
14+
json to_json() const;
15+
16+
protected:
17+
int n_images_{};
18+
std::string filename_{};
19+
std::vector<std::size_t> single_file_indices_{};
20+
json imagesequence_data_{}; // For propagating additional metadata during
21+
// serialization/deserialization.
22+
};

include/dx2/reflection.hpp

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -316,27 +316,6 @@ class ReflectionTable {
316316
template <typename T>
317317
struct is_column_predicate<ColumnPredicate<T>> : std::true_type {};
318318

319-
/**
320-
* @brief Generate a pseudo-random UUID-like identifier.
321-
*
322-
* This function replicates the behaviour of the Python function
323-
* `ersatz_uuid4` from the dxtbx library. It generates a 128-bit
324-
* random value and formats it as a UUID-style string using
325-
* little-endian byte order, without enforcing RFC 4122 compliance.
326-
*
327-
* The output is a 36-character string in the format:
328-
* `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, where each `x` is a
329-
* hexadecimal digit.
330-
*
331-
* @return A string representing the generated UUID-like identifier.
332-
*
333-
* @note This function does not set the version or variant bits as
334-
* specified in RFC 4122. It is intended for internal use where
335-
* uniqueness is sufficient, and compliance with UUID standards
336-
* is unnecessary.
337-
*/
338-
std::string ersatz_uuid4() const;
339-
340319
public:
341320
#pragma region Constructors
342321
/// Re-exported type aliase for convenience

include/dx2/utils.hpp

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,8 @@
11
#pragma once
22
#include <Eigen/Dense>
3-
#include <math.h>
43

54
using Eigen::Vector3d;
65

7-
double angle_between_vectors_degrees(Vector3d v1, Vector3d v2) {
8-
double l1 = v1.norm();
9-
double l2 = v2.norm();
10-
double dot = v1.dot(v2);
11-
double normdot = dot / (l1 * l2);
12-
if (std::abs(normdot - 1.0) < 1E-6) {
13-
return 0.0;
14-
}
15-
if (std::abs(normdot + 1.0) < 1E-6) {
16-
return 180.0;
17-
}
18-
double angle = std::acos(normdot) * 180.0 / M_PI;
19-
return angle;
20-
}
6+
double angle_between_vectors_degrees(Vector3d v1, Vector3d v2);
7+
8+
std::string ersatz_uuid4();

0 commit comments

Comments
 (0)