Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dx2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_library(
dx2
utils.cxx
reflection.cxx
h5/h5read_processed.cxx
h5/h5write.cxx
Expand All @@ -9,6 +10,7 @@ add_library(
goniometer.cxx
scan.cxx
detector_attenuations.cxx
imagesequence.cxx
)
target_link_libraries(
dx2
Expand Down
6 changes: 6 additions & 0 deletions dx2/detector.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,12 @@ Detector::Detector(json detector_data) {
}
}

Detector::Detector(std::vector<Panel> panels) {
for (auto it = panels.begin(); it != panels.end(); ++it) {
_panels.push_back(*it);
}
}

json Detector::to_json() const {
json detector_data;
std::vector<json> panels_array;
Expand Down
68 changes: 68 additions & 0 deletions dx2/imagesequence.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include "dx2/imagesequence.hpp"

using json = nlohmann::json;

// Constructor for MultiImage formats e.g. h5.
ImageSequence::ImageSequence(std::string filename, int n_images)
: filename_(filename), n_images_(n_images) {
single_file_indices_.reserve(n_images_);
for (std::size_t i = 0; i < n_images_; ++i) {
single_file_indices_.push_back(i);
}
}

// Constructor for non-MultiImage formats e.g. cbf.
ImageSequence::ImageSequence(std::string filename) : filename_(filename) {}

ImageSequence::ImageSequence(json imagesequence_data) {
std::vector<std::string> required_keys = {"template", "__id__"};
for (const auto &key : required_keys) {
if (imagesequence_data.find(key) == imagesequence_data.end()) {
throw std::invalid_argument("Key " + key +
" is missing from the input imageset JSON");
}
}
if (imagesequence_data["__id__"] != std::string("ImageSequence")) {
throw std::runtime_error("Only ImageSequences are supported");
}
filename_ = imagesequence_data["template"];
if (imagesequence_data.find("single_file_indices") !=
imagesequence_data.end()) {
// for non-multimage formats (e.g. non-h5), allow parsing.
json indices = imagesequence_data["single_file_indices"];
single_file_indices_ = {};
if (*(indices.begin()) < 0) {
throw std::runtime_error("Starting file index <0");
}
for (json::iterator it = indices.begin(); it != indices.end(); ++it) {
single_file_indices_.push_back(*it);
}
n_images_ = single_file_indices_.size();
}
imagesequence_data_ =
imagesequence_data; // To propagate during serialization/deserialization.
}

json ImageSequence::to_json() const {
json imageset_data = imagesequence_data_;
imageset_data["__id__"] = "ImageSequence";
imageset_data["template"] = filename_;
if (single_file_indices_.size() > 0) { // i.e. MultiImage formats (h5).
imageset_data["single_file_indices"] = single_file_indices_;
}
// Set defaults and null for now.
std::vector<std::string> optional_keys = {"mask", "gain", "pedestal", "dx",
"dy"};
for (const auto &key : optional_keys) {
if (imagesequence_data_.find(key) == imagesequence_data_.end()) {
imageset_data[key] = nullptr;
}
}
if (imagesequence_data_.find("params") == imagesequence_data_.end()) {
json params;
params["dynamic_shadowing"] = "Auto";
params["multi_panel"] = false;
imageset_data["params"] = params;
}
return imageset_data;
}
27 changes: 1 addition & 26 deletions dx2/reflection.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

#include "dx2/reflection.hpp"
#include "dx2/utils.hpp"
#include <algorithm>
#include <chrono>
#include <unordered_set>
Expand Down Expand Up @@ -142,32 +143,6 @@ void ReflectionTable::merge_into_set(std::unordered_set<size_t> &set,
const std::vector<size_t> &rows) const {
set.insert(rows.begin(), rows.end());
}

std::string ReflectionTable::ersatz_uuid4() const {
// Generate 16 random bytes
std::array<unsigned char, 16> bytes;
std::random_device rd;
std::uniform_int_distribution<int> dist(0, 255);
for (auto &b : bytes) {
b = static_cast<unsigned char>(dist(rd));
}

// Convert bytes to a single 128-bit hex string (little endian)
std::ostringstream oss;
for (auto it = bytes.rbegin(); it != bytes.rend(); ++it) {
oss << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(*it);
}
std::string hex = oss.str();

// Format as UUID: 8-4-4-4-12
std::ostringstream uuid;
uuid << hex.substr(0, 8) << "-" << hex.substr(8, 4) << "-"
<< hex.substr(12, 4) << "-" << hex.substr(16, 4) << "-"
<< hex.substr(20, 12);

return uuid.str();
}
#pragma endregion

#pragma region Selection Methods
Expand Down
68 changes: 68 additions & 0 deletions dx2/utils.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include "dx2/utils.hpp"
#include <Eigen/Dense>
#include <iomanip>
#include <math.h>
#include <random>
#include <sstream>

using Eigen::Vector3d;

double angle_between_vectors_degrees(Vector3d v1, Vector3d v2) {
double l1 = v1.norm();
double l2 = v2.norm();
double dot = v1.dot(v2);
double normdot = dot / (l1 * l2);
if (std::abs(normdot - 1.0) < 1E-6) {
return 0.0;
}
if (std::abs(normdot + 1.0) < 1E-6) {
return 180.0;
}
double angle = std::acos(normdot) * 180.0 / M_PI;
return angle;
}

/**
* @brief Generate a pseudo-random UUID-like identifier.
*
* This function replicates the behaviour of the Python function
* `ersatz_uuid4` from the dxtbx library. It generates a 128-bit
* random value and formats it as a UUID-style string using
* little-endian byte order, without enforcing RFC 4122 compliance.
*
* The output is a 36-character string in the format:
* `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, where each `x` is a
* hexadecimal digit.
*
* @return A string representing the generated UUID-like identifier.
*
* @note This function does not set the version or variant bits as
* specified in RFC 4122. It is intended for internal use where
* uniqueness is sufficient, and compliance with UUID standards
* is unnecessary.
*/
std::string ersatz_uuid4() {
// Generate 16 random bytes
std::array<unsigned char, 16> bytes;
std::random_device rd;
std::uniform_int_distribution<int> dist(0, 255);
for (auto &b : bytes) {
b = static_cast<unsigned char>(dist(rd));
}

// Convert bytes to a single 128-bit hex string (little endian)
std::ostringstream oss;
for (auto it = bytes.rbegin(); it != bytes.rend(); ++it) {
oss << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(*it);
}
std::string hex = oss.str();

// Format as UUID: 8-4-4-4-12
std::ostringstream uuid;
uuid << hex.substr(0, 8) << "-" << hex.substr(8, 4) << "-"
<< hex.substr(12, 4) << "-" << hex.substr(16, 4) << "-"
<< hex.substr(20, 12);

return uuid.str();
}
1 change: 1 addition & 0 deletions include/dx2/detector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class Detector {
public:
Detector() = default;
Detector(json detector_data);
Detector(std::vector<Panel> panels);
json to_json() const;
std::vector<Panel> panels() const;
std::optional<intersection> get_ray_intersection(const Vector3d &s1) const;
Expand Down
45 changes: 40 additions & 5 deletions include/dx2/experiment.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
#include <dx2/crystal.hpp>
#include <dx2/detector.hpp>
#include <dx2/goniometer.hpp>
#include <dx2/imagesequence.hpp>
#include <dx2/scan.hpp>
#include <dx2/utils.hpp>
#include <nlohmann/json.hpp>

using Eigen::Vector3d;
Expand All @@ -22,15 +24,21 @@ template <class BeamType> class Experiment {
Detector &detector();
const Crystal &crystal() const;
void set_crystal(Crystal crystal);
void set_beam(BeamType beam);
void set_scan(Scan scan);
void set_detector(Detector detector);
void set_goniometer(Goniometer goniometer);
void set_imagesequence(ImageSequence imagesequence);
void set_identifier(std::string identifier);
void generate_identifier();

protected:
BeamType _beam{};
Scan _scan{};
Goniometer _goniometer{};
Detector _detector{};
Crystal _crystal{};
json _imageset_json{};
ImageSequence _imagesequence{};
std::string _identifier{};
};

Expand All @@ -51,8 +59,9 @@ Experiment<BeamType>::Experiment(json experiment_data) {
this->_goniometer = gonio;
this->_detector = detector;
// Save the imageset json to propagate when saving to file.
json imageset_data = experiment_data["imageset"][0];
this->_imageset_json = imageset_data;
json imagesequence_data = experiment_data["imageset"][0];
ImageSequence imagesequence(imagesequence_data);
this->_imagesequence = imagesequence;
try { // We don't always have a crystal model e.g. before indexing.
json crystal_data = experiment_data["crystal"][0];
Crystal crystal(crystal_data);
Expand All @@ -67,7 +76,6 @@ template <class BeamType> json Experiment<BeamType>::to_json() const {
json elist_out; // a list of potentially multiple experiments
elist_out["__id__"] = "ExperimentList";
json expt_out; // our single experiment
// no imageset (for now?).
expt_out["__id__"] = "Experiment";
expt_out["identifier"] = _identifier;
expt_out["beam"] =
Expand All @@ -82,7 +90,7 @@ template <class BeamType> json Experiment<BeamType>::to_json() const {
elist_out["goniometer"] = std::array<json, 1>{_goniometer.to_json()};
elist_out["beam"] = std::array<json, 1>{_beam.to_json()};
elist_out["detector"] = std::array<json, 1>{_detector.to_json()};
elist_out["imageset"] = std::array<json, 1>{_imageset_json};
elist_out["imageset"] = std::array<json, 1>{_imagesequence.to_json()};

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

template <class BeamType> void Experiment<BeamType>::set_beam(BeamType beam) {
_beam = beam;
}

template <class BeamType> BeamType &Experiment<BeamType>::beam() {
return _beam;
}

template <class BeamType> void Experiment<BeamType>::set_scan(Scan scan) {
_scan = scan;
}

template <class BeamType>
void Experiment<BeamType>::set_detector(Detector detector) {
_detector = detector;
}

template <class BeamType>
void Experiment<BeamType>::set_goniometer(Goniometer goniometer) {
_goniometer = goniometer;
}

template <class BeamType>
void Experiment<BeamType>::set_imagesequence(ImageSequence imagesequence) {
_imagesequence = imagesequence;
}

template <class BeamType>
const std::string &Experiment<BeamType>::identifier() const {
return _identifier;
}
template <class BeamType>
void Experiment<BeamType>::set_identifier(std::string identifier) {
_identifier = identifier;
}

template <class BeamType> void Experiment<BeamType>::generate_identifier() {
_identifier = ersatz_uuid4();
}
22 changes: 22 additions & 0 deletions include/dx2/imagesequence.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once
#include <nlohmann/json.hpp>

using json = nlohmann::json;

class ImageSequence {
public:
ImageSequence() = default;
ImageSequence(std::string filename,
int n_images); // Constructor for MultiImage formats e.g. h5.
ImageSequence(
std::string filename); // Constructor for non-MultiImage formats e.g. cbf.
ImageSequence(json imagesequence_data);
json to_json() const;

protected:
int n_images_{};
std::string filename_{};
std::vector<std::size_t> single_file_indices_{};
json imagesequence_data_{}; // For propagating additional metadata during
// serialization/deserialization.
};
21 changes: 0 additions & 21 deletions include/dx2/reflection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,27 +316,6 @@ class ReflectionTable {
template <typename T>
struct is_column_predicate<ColumnPredicate<T>> : std::true_type {};

/**
* @brief Generate a pseudo-random UUID-like identifier.
*
* This function replicates the behaviour of the Python function
* `ersatz_uuid4` from the dxtbx library. It generates a 128-bit
* random value and formats it as a UUID-style string using
* little-endian byte order, without enforcing RFC 4122 compliance.
*
* The output is a 36-character string in the format:
* `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, where each `x` is a
* hexadecimal digit.
*
* @return A string representing the generated UUID-like identifier.
*
* @note This function does not set the version or variant bits as
* specified in RFC 4122. It is intended for internal use where
* uniqueness is sufficient, and compliance with UUID standards
* is unnecessary.
*/
std::string ersatz_uuid4() const;

public:
#pragma region Constructors
/// Re-exported type aliase for convenience
Expand Down
18 changes: 3 additions & 15 deletions include/dx2/utils.hpp
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
#pragma once
#include <Eigen/Dense>
#include <math.h>

using Eigen::Vector3d;

double angle_between_vectors_degrees(Vector3d v1, Vector3d v2) {
double l1 = v1.norm();
double l2 = v2.norm();
double dot = v1.dot(v2);
double normdot = dot / (l1 * l2);
if (std::abs(normdot - 1.0) < 1E-6) {
return 0.0;
}
if (std::abs(normdot + 1.0) < 1E-6) {
return 180.0;
}
double angle = std::acos(normdot) * 180.0 / M_PI;
return angle;
}
double angle_between_vectors_degrees(Vector3d v1, Vector3d v2);

std::string ersatz_uuid4();
Loading