Skip to content

Commit b2017c8

Browse files
Merge branch 'main' into add_live_indexing_to_service
2 parents 67b6e52 + de7539e commit b2017c8

File tree

12 files changed

+1042
-0
lines changed

12 files changed

+1042
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ _build/
1414
*ENV/
1515
*ENV2/
1616
*.pyc
17+
*.so
1718
build*/
1819
.vscode/
1920
compile_commands.json

CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,19 @@ include(AlwaysColourCompilation)
2121

2222
include_directories(include)
2323

24+
# Find Python
25+
find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
26+
include_directories(${Python3_INCLUDE_DIRS})
27+
2428
# Dependency fetching
2529
set(FETCHCONTENT_QUIET OFF)
2630
include(FetchContent)
31+
FetchContent_Declare(
32+
nanobind
33+
GIT_REPOSITORY https://github.com/wjakob/nanobind.git
34+
GIT_TAG v2.8.0
35+
)
36+
FetchContent_MakeAvailable(nanobind)
2737
FetchContent_Declare(
2838
nlohmann_json
2939
GIT_REPOSITORY https://github.com/nlohmann/json.git
@@ -117,3 +127,5 @@ enable_testing()
117127
add_subdirectory(h5read)
118128
add_subdirectory(baseline)
119129
add_subdirectory(spotfinder)
130+
131+
configure_file(${CMAKE_SOURCE_DIR}/scripts/ssx_index ${CMAKE_BINARY_DIR}/bin/ssx_index COPYONLY)

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@ cmake -DPIXEL_DATA_32BIT=ON .. # Enable 32-bit pixel data support
111111
make # Compile with the new settings
112112
```
113113

114+
### Installing the python module (for indexing)
115+
This project defines a small python module, to provide functionality for indexing.
116+
To run indexing code, this needs to be installed into the python environment by
117+
running this command in the root directory:
118+
```bash
119+
pip install .
120+
```
121+
114122
## Usage
115123
### Environment Variables
116124
The service uses the following environment variables:

baseline/indexer/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,13 @@ target_link_libraries(baseline_indexer
5656
ffs_common
5757
${CMAKE_THREAD_LIBS_INIT}
5858
)
59+
60+
# Create Python module
61+
nanobind_add_module(index index_module.cpp)
62+
target_include_directories(index PRIVATE ${CMAKE_SOURCE_DIR}/baseline/predictor)
63+
target_link_libraries(index PRIVATE Eigen3::Eigen dx2 ${Python3_LIBRARIES})
64+
65+
set(PYTHON_MODULE_DIR ${CMAKE_SOURCE_DIR}/src/ffs)
66+
add_custom_command(TARGET index POST_BUILD
67+
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:index> ${PYTHON_MODULE_DIR}
68+
)

baseline/indexer/index_module.cpp

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
#include <nanobind/eigen/dense.h>
2+
#include <nanobind/nanobind.h>
3+
#include <nanobind/stl/array.h>
4+
#include <nanobind/stl/tuple.h>
5+
#include <nanobind/stl/vector.h>
6+
7+
#include <dx2/crystal.hpp>
8+
#include <dx2/detector.hpp>
9+
#include <dx2/reflection.hpp>
10+
#include <experimental/mdspan>
11+
#include <tuple>
12+
13+
#include "assign_indices.cc"
14+
#include "stills_predictor.cc"
15+
#include "xyz_to_rlp.cc"
16+
17+
namespace nb = nanobind;
18+
19+
Panel make_panel(double distance,
20+
double beam_center_x,
21+
double beam_center_y,
22+
double pixel_size_x,
23+
double pixel_size_y,
24+
int image_size_x,
25+
int image_size_y,
26+
double thickness,
27+
double mu) {
28+
std::array<double, 2> beam_center = {beam_center_x, beam_center_y};
29+
std::array<double, 2> pixel_size = {pixel_size_x, pixel_size_y};
30+
std::array<int, 2> image_size = {image_size_x, image_size_y};
31+
Panel panel(
32+
distance, beam_center, pixel_size, image_size, "x", "-y", thickness, mu);
33+
return panel;
34+
}
35+
36+
struct IndexingResult {
37+
std::vector<double> cell_parameters;
38+
Eigen::Matrix3d A_matrix;
39+
std::vector<int> miller_indices;
40+
std::vector<double> xyzobs_px;
41+
std::vector<double> xyzcal_px;
42+
std::vector<double> s1;
43+
std::vector<double> delpsi;
44+
std::vector<double> rmsds;
45+
};
46+
47+
IndexingResult index_from_ssx_cells(const std::vector<double> &crystal_vectors,
48+
std::vector<double> rlp_data,
49+
std::vector<double> xyzobs_px_data,
50+
Eigen::Vector3d s0,
51+
Panel panel) {
52+
// Convert the raw input data arrays to spans
53+
mdspan_type<double> xyzobs_px =
54+
mdspan_type<double>(xyzobs_px_data.data(), xyzobs_px_data.size() / 3, 3);
55+
mdspan_type<double> rlp =
56+
mdspan_type<double>(rlp_data.data(), rlp_data.size() / 3, 3);
57+
58+
// first convert the cells vector to crystals
59+
std::vector<Crystal> crystals{};
60+
for (int i = 0; i < crystal_vectors.size() / 9; ++i) {
61+
int start = i * 9;
62+
Vector3d a = {crystal_vectors[start],
63+
crystal_vectors[start + 1],
64+
crystal_vectors[start + 2]};
65+
Vector3d b = {crystal_vectors[start + 3],
66+
crystal_vectors[start + 4],
67+
crystal_vectors[start + 5]};
68+
Vector3d c = {crystal_vectors[start + 6],
69+
crystal_vectors[start + 7],
70+
crystal_vectors[start + 8]};
71+
Crystal crystal(a, b, c, *gemmi::find_spacegroup_by_name("P1"));
72+
crystals.push_back(crystal);
73+
}
74+
75+
// Choose the best crystal based on number of indexed.
76+
std::vector<double> n_indexed{};
77+
std::vector<std::vector<double>> cells{};
78+
std::vector<Matrix3d> A_matrices{};
79+
std::vector<std::vector<int>> miller_indices{};
80+
std::vector<std::vector<std::size_t>> selections{};
81+
82+
for (auto &crystal : crystals) {
83+
// Note xyzobs not actually needed for stills assignment.
84+
assign_indices_results results =
85+
assign_indices_global(crystal.get_A_matrix(), rlp, xyzobs_px);
86+
n_indexed.push_back(results.number_indexed);
87+
gemmi::UnitCell cell = crystal.get_unit_cell();
88+
std::vector<double> cell_parameters = {
89+
cell.a, cell.b, cell.c, cell.alpha, cell.beta, cell.gamma};
90+
cells.push_back(cell_parameters);
91+
Matrix3d U = crystal.get_U_matrix();
92+
Matrix3d B = crystal.get_B_matrix();
93+
A_matrices.push_back(U * B);
94+
std::vector<int> nonzero_miller_indices;
95+
std::vector<std::size_t> selection;
96+
for (std::size_t j = 0; j < results.miller_indices.extent(0); j++) {
97+
const Eigen::Map<Vector3i> hkl_j(&results.miller_indices(j, 0));
98+
if ((hkl_j[0] != 0) || (hkl_j[1] != 0) || (hkl_j[2] != 0)) {
99+
nonzero_miller_indices.push_back(hkl_j[0]);
100+
nonzero_miller_indices.push_back(hkl_j[1]);
101+
nonzero_miller_indices.push_back(hkl_j[2]);
102+
selection.push_back(j);
103+
}
104+
}
105+
miller_indices.push_back(nonzero_miller_indices);
106+
selections.push_back(selection);
107+
}
108+
auto max_iter = std::max_element(n_indexed.begin(), n_indexed.end());
109+
int max_index = std::distance(n_indexed.begin(), max_iter);
110+
std::vector<size_t> selection = selections[max_index];
111+
std::vector<int> best_miller_indices = miller_indices[max_index];
112+
int best_n_indexed = n_indexed[max_index];
113+
Matrix3d A = A_matrices[max_index];
114+
115+
// Select on the input xyzobs_px to get the values for only indexed reflections
116+
std::vector<double> xyzobs_px_indexed_data;
117+
for (auto &i : selection) {
118+
xyzobs_px_indexed_data.push_back(xyzobs_px(i, 0));
119+
xyzobs_px_indexed_data.push_back(xyzobs_px(i, 1));
120+
xyzobs_px_indexed_data.push_back(xyzobs_px(i, 2));
121+
}
122+
mdspan_type<double> xyzobs_px_indexed = mdspan_type<double>(
123+
xyzobs_px_indexed_data.data(), xyzobs_px_indexed_data.size() / 3, 3);
124+
125+
// Now predict (generates xyzcal.px and delpsi).
126+
ReflectionTable refls;
127+
refls.add_column(
128+
"miller_index", best_miller_indices.size() / 3, 3, best_miller_indices);
129+
simple_still_reflection_predictor(s0, A, panel, refls);
130+
131+
// now do some outlier rejection and return only the good data
132+
auto delpsi_ = refls.column<double>("delpsical.rad");
133+
auto &delpsi = delpsi_.value();
134+
auto xyzcal_px_ = refls.column<double>("xyzcal.px");
135+
auto &xyzcal_px = xyzcal_px_.value();
136+
137+
std::vector<bool> sel_for_indexed(best_n_indexed, false);
138+
double rmsd_x = 0;
139+
double rmsd_y = 0;
140+
double rmsd_psi = 0;
141+
int n = 0;
142+
for (std::size_t i = 0; i < best_n_indexed; i++) {
143+
double dx = std::pow(xyzobs_px_indexed(i, 0) - xyzcal_px(i, 0), 2);
144+
double dy = std::pow(xyzobs_px_indexed(i, 1) - xyzcal_px(i, 1), 2);
145+
double delta_r = std::sqrt(dx + dy);
146+
if (delta_r < 2.0) { // crude filter for now.
147+
rmsd_x += dx;
148+
rmsd_y += dy;
149+
rmsd_psi += std::pow(delpsi(i, 0), 2);
150+
sel_for_indexed[i] = true;
151+
n += 1;
152+
}
153+
}
154+
std::vector<double> rmsds;
155+
if (n > 0) {
156+
rmsd_x = std::sqrt(rmsd_x / n);
157+
rmsd_y = std::sqrt(rmsd_y / n);
158+
rmsd_psi = std::sqrt(rmsd_psi / n);
159+
rmsds.push_back(rmsd_x);
160+
rmsds.push_back(rmsd_y);
161+
rmsds.push_back(rmsd_psi);
162+
}
163+
164+
// select on the reflection table.
165+
refls.add_column(
166+
"xyzobs.px.value", xyzobs_px_indexed.extent(0), 3, xyzobs_px_indexed_data);
167+
ReflectionTable filtered = refls.select(sel_for_indexed);
168+
169+
// Get the data arrays to return.
170+
delpsi_ = filtered.column<double>("delpsical.rad");
171+
delpsi = delpsi_.value();
172+
xyzcal_px_ = filtered.column<double>("xyzcal.px");
173+
xyzcal_px = xyzcal_px_.value();
174+
auto xyzobs_px_filtered_ = filtered.column<double>("xyzobs.px.value");
175+
auto &xyzobs_px_filtered = xyzobs_px_filtered_.value();
176+
auto s1_ = filtered.column<double>("s1");
177+
auto &s1 = s1_.value();
178+
auto midx_ = filtered.column<int>("miller_index");
179+
auto &midx = midx_.value();
180+
std::vector<int> miller_index_vec(midx.data_handle(),
181+
midx.data_handle() + midx.size());
182+
std::vector<double> s1_vec(s1.data_handle(), s1.data_handle() + s1.size());
183+
std::vector<double> xyzcal_px_vec(xyzcal_px.data_handle(),
184+
xyzcal_px.data_handle() + xyzcal_px.size());
185+
std::vector<double> xyzobs_px_vec(
186+
xyzobs_px_filtered.data_handle(),
187+
xyzobs_px_filtered.data_handle() + xyzobs_px_filtered.size());
188+
std::vector<double> delpsi_vec(delpsi.data_handle(),
189+
delpsi.data_handle() + delpsi.size());
190+
191+
return IndexingResult(cells[max_index],
192+
A,
193+
std::move(miller_index_vec),
194+
std::move(xyzobs_px_vec),
195+
std::move(xyzcal_px_vec),
196+
std::move(s1_vec),
197+
std::move(delpsi_vec),
198+
rmsds);
199+
}
200+
201+
NB_MODULE(index, m) {
202+
nb::class_<Panel>(m, "Panel")
203+
.def(nb::init<double,
204+
std::array<double, 2>,
205+
std::array<double, 2>,
206+
std::array<int, 2>,
207+
std::string,
208+
std::string,
209+
double,
210+
double>());
211+
nb::class_<IndexingResult>(m, "IndexingResult")
212+
.def_prop_ro("cell_parameters",
213+
[](const IndexingResult &r) { return r.cell_parameters; })
214+
.def_prop_ro("A_matrix", [](const IndexingResult &r) { return r.A_matrix; })
215+
.def_prop_ro("miller_indices",
216+
[](const IndexingResult &r) { return r.miller_indices; })
217+
.def_prop_ro("xyzobs_px", [](const IndexingResult &r) { return r.xyzobs_px; })
218+
.def_prop_ro("xyzcal_px", [](const IndexingResult &r) { return r.xyzcal_px; })
219+
.def_prop_ro("s1", [](const IndexingResult &r) { return r.s1; })
220+
.def_prop_ro("delpsi", [](const IndexingResult &r) { return r.delpsi; })
221+
.def_prop_ro("rmsds", [](const IndexingResult &r) { return r.rmsds; });
222+
m.def("make_panel", &make_panel, "Create a configured Panel object");
223+
m.def("ssx_xyz_to_rlp",
224+
&ssx_xyz_to_rlp,
225+
nb::arg("xyzobs_px"),
226+
nb::arg("wavelength"),
227+
nb::arg("panel"),
228+
"Convert detector pixel positions to reciprocal lattice points");
229+
m.def("index_from_ssx_cells",
230+
&index_from_ssx_cells,
231+
nb::arg("crystal_vectors"),
232+
nb::arg("rlp"),
233+
nb::arg("xyzobs_px"),
234+
nb::arg("s0"),
235+
nb::arg("panel"),
236+
"Assign miller indices to the best crystal, predict reflections and "
237+
"calculate rnmsds");
238+
}

baseline/indexer/xyz_to_rlp.cc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,47 @@ xyz_to_rlp_results xyz_to_rlp(const mdspan_type<double> &xyzobs_px,
108108
return results; // Return the data and spans.
109109
}
110110

111+
/**
112+
* @brief Transform detector pixel coordinates into reciprocal space coordinates, for a simplified
113+
* serial crystallogrphy experiment (perpendicular beam).
114+
* @param xyzobs_px A 1D array of detector pixel coordinates from a single panel.
115+
* @param wavelength The wavelength of the beam.
116+
* @param panel A dx2 Panel object defining the corresponding detector panel.
117+
* @returns A vector containing reciprocal space coordinates.
118+
*/
119+
std::vector<double> ssx_xyz_to_rlp(const std::vector<double> &xyzobs_px,
120+
double wavelength,
121+
const Panel &panel) {
122+
// Input and output data arrays are 1D, i.e [x_0, y_0, z_0, x_1, y_1, z_1, ... etc]
123+
double s0_z =
124+
-1.0
125+
/ wavelength; // s0 = {0.0,0.0,-1.0/wavelength} - but we only need the z component.
126+
std::vector<double> rlp(xyzobs_px.size(), 0);
127+
Matrix3d d_matrix = panel.get_d_matrix();
128+
double rot_angle = 0.0;
129+
for (int i = 0; i < xyzobs_px.size() / 3; ++i) {
130+
int x_idx = i * 3;
131+
int y_idx = i * 3 + 1;
132+
int z_idx = i * 3 + 2;
133+
// first convert detector pixel positions into mm
134+
double x1 = xyzobs_px[x_idx];
135+
double x2 = xyzobs_px[y_idx];
136+
std::array<double, 2> xymm = panel.px_to_mm(x1, x2);
137+
138+
// calculate the s1 vector using the detector d matrix
139+
Vector3d m = {xymm[0], xymm[1], 1.0};
140+
Vector3d s1_i = d_matrix * m;
141+
s1_i.normalize();
142+
// convert into inverse ansgtroms
143+
Vector3d s1_this = s1_i / wavelength;
144+
// calculation is rlp_this = s1_this - s0;
145+
rlp[x_idx] = s1_this[0];
146+
rlp[y_idx] = s1_this[1];
147+
rlp[z_idx] = s1_this[2] - s0_z;
148+
}
149+
return rlp;
150+
}
151+
111152
void px_to_mm(const mdspan_type<double> &px_input,
112153
mdspan_type<double> &mm_output,
113154
const Scan &scan,

0 commit comments

Comments
 (0)