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+ }
0 commit comments