mlgidbase 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
docs/conf.py ADDED
@@ -0,0 +1,30 @@
1
+ import os
2
+ import sys
3
+ sys.path.insert(0, os.path.abspath('..'))
4
+
5
+ project = 'mlgidBASE'
6
+ author = 'Ainur Abukaev'
7
+ release = '0.1.0'
8
+
9
+ extensions = [
10
+ 'sphinx.ext.autodoc',
11
+ 'sphinx.ext.napoleon',
12
+ 'nbsphinx',
13
+ 'myst_nb',
14
+ ]
15
+
16
+ templates_path = ['_templates']
17
+ exclude_patterns = ['_build', '**.ipynb_checkpoints']
18
+
19
+ html_theme = 'sphinx_rtd_theme'
20
+ html_static_path = ['_static']
21
+
22
+ tutorial_dir = os.path.join(os.path.dirname(__file__), 'tutorials')
23
+ tutorials = sorted(f for f in os.listdir(tutorial_dir) if f.endswith('.ipynb'))
24
+ tutorial_names = [os.path.splitext(f)[0] for f in tutorials]
25
+
26
+ toctree_path = os.path.join(os.path.dirname(__file__), 'tutorials_toctree.rst')
27
+ with open(toctree_path, 'w', encoding='utf-8') as f:
28
+ f.write("Tutorials\n=========\n\n.. toctree::\n :maxdepth: 2\n\n")
29
+ for name in tutorial_names:
30
+ f.write(f" tutorials/{name}\n")
mlgidbase/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ # from .expparams import ExpParams
2
+ # from .dataloader import DataLoader
3
+ # from .datasaver import DataSaver, ExpMetadata, SampleMetadata
4
+ # from .coordmaps import CoordMaps
5
+ # from .conversion import Conversion
6
+ from .main import mlgidBASE
7
+ __version__ = "0.0.1"
mlgidbase/main.py ADDED
@@ -0,0 +1,491 @@
1
+ import os
2
+ from typing import Any, Optional
3
+ import logging
4
+
5
+ from .pygid_functions import (get_nexus, save_pipeline, det2pol_gid_pygid, det2q_gid_pygid)
6
+ from .mlgiddetect_functions import _run_detection
7
+ from .pygidfit_functions import _run_fitting
8
+ from .mlgidmatch_functions import _run_matching
9
+ from .visualization import get_plot_params, _plot_analysis_results
10
+ from .peak_operations import _delete_peak, _add_peak, _draw_box
11
+ from .nexus_operations import _get_detected_peaks, _get_fitted_peaks, _get_matched_peaks
12
+ from mlgidmatch.preprocess.cif_preprocess import CifPattern
13
+
14
+
15
+ class mlgidBASE:
16
+ """
17
+ High-level pipeline for GID data analysis including detection, fitting, and matching.
18
+
19
+ This class provides a unified interface to process grazing-incidence diffraction (GID)
20
+ data either from a NeXus file or directly from a `pygid.Conversion` object. It integrates
21
+ three main stages:
22
+
23
+ 1. Peak detection (mlgiddetect)
24
+ 2. Peak fitting (pygidfit)
25
+ 3. Structure matching (mlgidmatch)
26
+
27
+ Parameters
28
+ ----------
29
+ filename : str, optional
30
+ Path to a NeXus file containing GID data. Mutually exclusive with `pygid_conversion`.
31
+ pygid_conversion : object, optional
32
+ Precomputed `pygid.Conversion` instance. Mutually exclusive with `filename`.
33
+ imp_detect : object, optional
34
+ Preloaded inference model for peak detection.
35
+ config_detect : object or str, optional
36
+ Detection configuration or path to configuration file.
37
+ cif_prepr : CifPattern, optional
38
+ Preprocessed CIF patterns used for matching.
39
+ path_to_save : str, default "result.h5"
40
+ Output file path for saving results.
41
+ overwrite_file : bool, default True
42
+ Whether to overwrite the output file if it exists.
43
+ h5_group : str, default "entry_0000"
44
+ Target HDF5 group for saving results.
45
+ overwrite_group : bool, default False
46
+ Whether to overwrite an existing group in the HDF5 file.
47
+ smpl_metadata : Any, optional
48
+ Sample metadata to store in output.
49
+ exp_metadata : Any, optional
50
+ Experimental metadata to store in output.
51
+ plot_params : dict, optional
52
+ Matplotlib configuration parameters for plotting.
53
+
54
+ Attributes
55
+ ----------
56
+ from_nexus : bool
57
+ Indicates whether data source is a NeXus file.
58
+ nexus : object
59
+ Loaded NeXus file handler.
60
+ entry_dict : dict
61
+ Structure of entries in the NeXus file.
62
+ img_container_detect_list : list
63
+ Results of detection stage.
64
+ img_container_fit_list : list
65
+ Results of fitting stage.
66
+ container_match_list : list
67
+ Results of matching stage.
68
+ logger : logging.Logger
69
+ Logger instance for pipeline messages.
70
+
71
+ Notes
72
+ -----
73
+ Exactly one of `filename` or `pygid_conversion` must be provided.
74
+
75
+ Workflow
76
+ --------
77
+ Typical usage:
78
+
79
+ >>> analysis = mlgidBASE(filename="data.h5")
80
+ >>> analysis.run_detection()
81
+ >>> analysis.run_fitting()
82
+ >>> analysis.run_matching()
83
+ >>> analysis.save_result()
84
+
85
+ The pipeline can also operate fully in-memory using a `pygid.Conversion` object.
86
+
87
+ """
88
+ def __init__(
89
+ self,
90
+ *,
91
+ filename: Optional[str] = None,
92
+ pygid_conversion: Optional[Any] = None,
93
+ imp_detect: Optional[Any] = None,
94
+ config_detect: Optional[Any] = None,
95
+ cif_prepr: Optional[CifPattern] = None,
96
+ path_to_save: str = "result.h5",
97
+ overwrite_file: bool = True,
98
+ h5_group: str = "entry_0000",
99
+ overwrite_group: bool = False,
100
+ smpl_metadata: Any = None,
101
+ exp_metadata: Any = None,
102
+ plot_params: Any = None
103
+ ):
104
+ self.filename = filename
105
+ self.pygid_conversion = pygid_conversion
106
+
107
+ self.imp_detect = imp_detect
108
+ self.config_detect = config_detect
109
+
110
+ self.cif_prepr = cif_prepr
111
+
112
+ self.path_to_save = path_to_save
113
+ self.overwrite_file = overwrite_file
114
+ self.h5_group = h5_group
115
+ self.overwrite_group = overwrite_group
116
+ self.smpl_metadata = smpl_metadata
117
+ self.exp_metadata = exp_metadata
118
+
119
+ self.from_nexus: Optional[bool] = None
120
+ self.nexus: Any = None
121
+ self.entry_dict: Any = None
122
+
123
+ self.img_container_detect_list = None
124
+ self.img_container_fit_list = None
125
+ self.container_match_list = None
126
+
127
+ self.logger = logging.getLogger(self.__class__.__name__)
128
+ self.logger.setLevel(logging.INFO)
129
+
130
+ self._initialize_source()
131
+ self.plot_params = get_plot_params()
132
+
133
+ def _validate_input(self) -> None:
134
+ """
135
+ Validate that the source data is correctly specified.
136
+
137
+ Raises
138
+ ------
139
+ AttributeError
140
+ If neither `pygid_conversion` nor `filename` is provided, or if both are provided.
141
+ """
142
+ if self.pygid_conversion is None and self.filename is None:
143
+ raise AttributeError(
144
+ "Need to specify either pygid.Conversion instance or Nexus filename"
145
+ )
146
+
147
+ if self.pygid_conversion is not None and self.filename is not None:
148
+ raise AttributeError(
149
+ "Need to specify either pygid.Conversion instance or Nexus filename"
150
+ )
151
+
152
+ def _initialize_source(self) -> None:
153
+ """
154
+ Initialize the data source and precompute key arrays.
155
+
156
+ Sets attributes for:
157
+ - GID reciprocal space arrays (`q_abs`, `chi`, `q_xy`, `q_z`)
158
+ - Polar images (`img_pol`)
159
+ - NeXus file handler and entry dictionary if reading from file.
160
+
161
+ Notes
162
+ -----
163
+ Only one source should be provided: `pygid_conversion` or `filename`.
164
+ """
165
+ self._validate_input()
166
+ if self.pygid_conversion is not None:
167
+ # check_valid_conversion(self.pygid_conversion)
168
+ self.from_nexus = False
169
+ dq_init = float(self.pygid_conversion.matrix[0].dq)
170
+ self.q_abs, self.chi, self.img_pol = det2pol_gid_pygid(self.pygid_conversion)
171
+ det2q_gid_pygid(self.pygid_conversion, dq_init)
172
+ self.q_xy = self.pygid_conversion.matrix[0].q_xy
173
+ self.q_z = self.pygid_conversion.matrix[0].q_z
174
+ if self.filename is not None:
175
+ self.nexus = get_nexus(self.filename)
176
+ self.entry_dict = self.nexus.entry_dict
177
+ self.from_nexus = True
178
+
179
+ def run_detection(self, entry=None, frame_num=None, config_detect=None, model_type=None):
180
+ """
181
+ Run peak detection on the dataset.
182
+
183
+ Parameters
184
+ ----------
185
+ entry : str, optional
186
+ Entry name in the NeXus file. Defaults to all entries.
187
+ frame_num : int, optional
188
+ Frame index to process. Defaults to all frames.
189
+ config_detect : Config or str, optional
190
+ Detection configuration object or path to configuration file.
191
+ model_type : str, optional
192
+ Type of detection model to use (e.g., 'faster_rcnn', 'dino').
193
+ """
194
+ _run_detection(self, entry, frame_num, config_detect, model_type)
195
+
196
+ def run_fitting(self, entry=None, frame_num=None, crit_angle=0,
197
+ clustering_distance_peaks=10, clustering_distance_rings=10,
198
+ clustering_extend=2, theta_fixed = True, use_pool=False, debug=False):
199
+ """
200
+ Fit detected peaks to clusters for structural analysis.
201
+
202
+ Parameters
203
+ ----------
204
+ entry : str, optional
205
+ Entry name in the NeXus file. Defaults to all entries.
206
+ frame_num : int, optional
207
+ Frame index to process. Defaults to all frames.
208
+ crit_angle : float, default 0
209
+ Maximum allowed misorientation angle between peaks.
210
+ clustering_distance_peaks : float, default 10
211
+ Distance threshold for peak clustering.
212
+ clustering_distance_rings : float, default 10
213
+ Distance threshold for ring clustering.
214
+ clustering_extend : int, default 2
215
+ Number of neighboring peaks to include in cluster expansion.
216
+ theta_fixed : bool, default True
217
+ Whether the polar angle theta is fixed during clustering.
218
+ use_pool : bool, default False
219
+ Whether to use multiprocessing for fitting.
220
+ debug : bool, default False
221
+ Whether to print debugging information.
222
+ """
223
+ _run_fitting(self, entry=entry, frame_num=frame_num, crit_angle=crit_angle,
224
+ clustering_distance_peaks=clustering_distance_peaks,
225
+ clustering_distance_rings=clustering_distance_rings,
226
+ clustering_extend=clustering_extend,
227
+ theta_fixed = theta_fixed, use_pool=use_pool,
228
+ debug=debug)
229
+
230
+ def run_matching(self, entry=None, frame_num=None, cif_prepr=None,
231
+ probability_threshold=0.5, peaks_type='segments', device=None, intensity_threshold=0,
232
+ threshold=None):
233
+ """
234
+ Match fitted peaks to preprocessed CIF patterns.
235
+
236
+ Parameters
237
+ ----------
238
+ entry : str, optional
239
+ Entry name in the NeXus file. Defaults to all entries.
240
+ frame_num : int, optional
241
+ Frame index to process. Defaults to all frames.
242
+ cif_prepr : CifPattern, optional
243
+ Preprocessed CIF patterns for matching.
244
+ probability_threshold : float, default 0.5
245
+ Minimum probability threshold for a match.
246
+ peaks_type : {'segments', 'rings'}, default 'segments'
247
+ Type of peaks to match.
248
+ device : str, optional
249
+ Device to use for matching ('cpu' or 'cuda').
250
+ intensity_threshold : float, default 0
251
+ Minimum peak intensity to consider.
252
+ threshold : float, optional
253
+ Alternative threshold value for matching probability.
254
+ """
255
+ _run_matching(self, entry=entry, frame_num=frame_num, cif_prepr=cif_prepr,
256
+ probability_threshold=probability_threshold, peaks_type=peaks_type,
257
+ device=device, intensity_threshold=intensity_threshold,
258
+ threshold=threshold)
259
+
260
+ def save_result(self, path_to_save = 'result.h5', overwrite_file = True, h5_group = 'entry_0000',
261
+ overwrite_group = False, smpl_metadata = None, exp_metadata = None,
262
+ save_polar = False, h5_group_polar = 'entry_polar_0000'):
263
+ """
264
+ Save the full analysis pipeline results to an HDF5 file.
265
+
266
+ Parameters
267
+ ----------
268
+ path_to_save : str, default 'result.h5'
269
+ Path of the output file.
270
+ overwrite_file : bool, default True
271
+ Whether to overwrite an existing file.
272
+ h5_group : str, default 'entry_0000'
273
+ HDF5 group for the main results.
274
+ overwrite_group : bool, default False
275
+ Whether to overwrite an existing HDF5 group.
276
+ smpl_metadata : dict or object, optional
277
+ Sample metadata to include in the saved file.
278
+ exp_metadata : dict or object, optional
279
+ Experimental metadata to include in the saved file.
280
+ save_polar : bool, default False
281
+ Whether to save polar images in a separate HDF5 group.
282
+ h5_group_polar : str, default 'entry_polar_0000'
283
+ HDF5 group name for polar images if `save_polar=True`.
284
+ """
285
+ save_pipeline(self.pygid_conversion, self.img_container_detect_list,
286
+ self.img_container_fit_list, self.container_match_list,
287
+ path_to_save, overwrite_file, h5_group, overwrite_group,
288
+ smpl_metadata, exp_metadata)
289
+ if save_polar:
290
+ self.pygid_conversion.img_gid_pol = self.img_pol
291
+ self.pygid_conversion.matrix[0].ang_gid_pol = self.chi
292
+ self.pygid_conversion.matrix[0].q_gid_pol = self.q_abs
293
+ save_pipeline(self.pygid_conversion, None,
294
+ None, None,
295
+ path_to_save, False, h5_group_polar, overwrite_group,
296
+ smpl_metadata, exp_metadata)
297
+
298
+ def set_plot_defaults(self, font_size=14, axes_titlesize=14, axes_labelsize=18, grid=False, grid_color='gray',
299
+ grid_linestyle='--', grid_linewidth=0.5, xtick_labelsize=14, ytick_labelsize=14,
300
+ legend_fontsize=12, legend_loc='best', legend_frameon=True, legend_borderpad=1.0,
301
+ legend_borderaxespad=1.0, figure_titlesize=16, figsize=(6.4, 4.8), axes_linewidth=0.5,
302
+ savefig_dpi=600, savefig_transparent=False, savefig_bbox_inches=None,
303
+ savefig_pad_inches=0.1, line_linewidth=2, line_color='blue', line_linestyle='-',
304
+ line_marker=None, scatter_marker='o', scatter_edgecolors='black',
305
+ cmap='inferno'):
306
+ """
307
+ Sets the default settings for various parts of a Matplotlib plot, including font sizes, gridlines,
308
+ legend, figure properties, and line styles. The function configures the default style for future
309
+ plots created with Matplotlib.
310
+
311
+ Parameters:
312
+ - font_size (int): Default font size for text elements (e.g., title, labels, ticks).
313
+ - axes_titlesize (int): Font size for axes titles.
314
+ - axes_labelsize (int): Font size for axes labels (x and y).
315
+ - grid (bool): Whether or not to display gridlines (True/False).
316
+ - grid_color (str): Color of the gridlines (e.g., 'gray', 'black').
317
+ - grid_linestyle (str): Line style of the gridlines (e.g., '--', '-', ':').
318
+ - grid_linewidth (float): Width of the gridlines.
319
+ - xtick_labelsize (int): Font size for x-axis tick labels.
320
+ - ytick_labelsize (int): Font size for y-axis tick labels.
321
+ - legend_fontsize (int): Font size for the legend text.
322
+ - legend_loc (str): Location of the legend (e.g., 'best', 'upper right', 'lower left').
323
+ - legend_frameon (bool): Whether to display a frame around the legend.
324
+ - legend_borderpad (float): Padding between the legend's content and the legend's frame.
325
+ - legend_borderaxespad (float): Padding between the legend and axes.
326
+ - figure_titlesize (int): Font size for the figure title.
327
+ - figsize (tuple): Size of the figure in inches (e.g., (6, 6)).
328
+ - savefig_dpi (int): DPI for saving the figure (higher DPI means better quality).
329
+ - savefig_transparent (bool): Whether the saved figure should have a transparent background.
330
+ - savefig_bbox_inches (str): Defines what part of the plot to save (e.g., 'tight' to crop extra whitespace).
331
+ - savefig_pad_inches (float): Padding added around the figure when saving.
332
+ - line_linewidth (float): Line width for plot lines.
333
+ - line_color (str): Color of the plot lines (e.g., 'blue', 'red').
334
+ - line_linestyle (str): Line style (e.g., '-', '--', ':').
335
+ - line_marker (str): Marker style for plot lines (e.g., 'o', 'x').
336
+ - scatter_marker (str): Marker style for scatter plots (e.g., 'o', 'x').
337
+ - scatter_edgecolors (str): Color for the edges of scatter plot markers (e.g., 'black').
338
+ - cmap (str): Image colormap
339
+ """
340
+ self.plot_params.update(get_plot_params(font_size, axes_titlesize, axes_labelsize, grid, grid_color,
341
+ grid_linestyle, grid_linewidth, xtick_labelsize,
342
+ ytick_labelsize,
343
+ legend_fontsize, legend_loc, legend_frameon, legend_borderpad,
344
+ legend_borderaxespad, figure_titlesize, figsize,
345
+ axes_linewidth,
346
+ savefig_dpi, savefig_transparent, savefig_bbox_inches,
347
+ savefig_pad_inches, line_linewidth, line_color, line_linestyle,
348
+ line_marker, scatter_marker, scatter_edgecolors,
349
+ cmap))
350
+
351
+ def plot_analysis_results(self,
352
+ detected_params={'line_width': 0.5,
353
+ 'line_style': "--",
354
+ 'line_color': "black",
355
+ 'plot_id': True,
356
+ 'text_color': 'white',
357
+ 'text_size': 8,
358
+ 'plot': False},
359
+ fitted_params={'plot_segments': True,
360
+ 'marker': 'o',
361
+ 'marker_size': 50,
362
+ 'marker_facecolor': "none",
363
+ 'marker_edgecolor': "bone",
364
+ 'plot_rings': True,
365
+ 'line_width': 1,
366
+ 'line_style': "--",
367
+ 'line_color': "bone",
368
+ 'plot_id': False,
369
+ 'text_color': 'white',
370
+ 'text_size': 8,
371
+ 'plot': False},
372
+ matched_params={
373
+ 'solution': None,
374
+ 'plot_segments': True,
375
+ 'marker': ['o', 'o', 'o'],
376
+ 'marker_size': [50, 50, 50],
377
+ 'marker_facecolor': ["none", "none", "none"],
378
+ 'marker_edgecolor': ["bone", 'blue', 'green'],
379
+ 'plot_rings': True,
380
+ 'line_width': [1, 1, 1],
381
+ 'line_style': ["--", "--", "--"],
382
+ 'line_color': ["bone", 'blue', 'green'],
383
+ 'plot_id': False,
384
+ 'text_color': 'white',
385
+ 'text_size': 8,
386
+ 'legend': True,
387
+ 'plot': False,},
388
+ frame_num=None, entry=None,
389
+ return_result=False, plot_result=True,
390
+ clims=None, xlim=(None, None), ylim=(None, None),
391
+ save_fig=False, path_to_save_fig="img.png"):
392
+ """
393
+ Visualize the results of detection, fitting, and matching stages.
394
+
395
+ Parameters
396
+ ----------
397
+ detected_params : dict, optional
398
+ Plotting options for detected peaks.
399
+ fitted_params : dict, optional
400
+ Plotting options for fitted peaks and clusters.
401
+ matched_params : dict, optional
402
+ Plotting options for matched peaks to CIF patterns.
403
+ frame_num : int, optional
404
+ Frame index to plot. Defaults to all frames.
405
+ entry : str, optional
406
+ Entry name in the NeXus file.
407
+ return_result : bool, default False
408
+ Whether to return the matplotlib figure object.
409
+ plot_result : bool, default True
410
+ Whether to display the plot interactively.
411
+ clims : tuple, optional
412
+ Color limits for the plot.
413
+ xlim : tuple, default (None, None)
414
+ X-axis limits.
415
+ ylim : tuple, default (None, None)
416
+ Y-axis limits.
417
+ save_fig : bool, default False
418
+ Whether to save the figure to a file.
419
+ path_to_save_fig : str, default 'img.png'
420
+ Path to save the figure if `save_fig=True`.
421
+ """
422
+ _plot_analysis_results(self,
423
+ detected_params=detected_params,
424
+ fitted_params=fitted_params,
425
+ matched_params=matched_params,
426
+ frame_num=frame_num, entry=entry,
427
+ return_result=return_result, plot_result=plot_result,
428
+ clims=clims, xlim=xlim, ylim=ylim,
429
+ save_fig=save_fig, path_to_save_fig=path_to_save_fig)
430
+
431
+ def check_valid_data(self, detected_params, fitted_params, matched_params):
432
+ """
433
+ Check that the pipeline has valid data for plotting.
434
+
435
+ This function disables plotting for stages that were not run
436
+ and provides informative logger messages.
437
+
438
+ Parameters
439
+ ----------
440
+ detected_params : dict
441
+ Plotting configuration for detection stage.
442
+ fitted_params : dict
443
+ Plotting configuration for fitting stage.
444
+ matched_params : dict
445
+ Plotting configuration for matching stage.
446
+
447
+ Raises
448
+ ------
449
+ ValueError
450
+ If required data for plotting (`img_gid_q`) is missing.
451
+ """
452
+ if self.img_container_detect_list is None and detected_params.get('plot', True):
453
+ self.logger.info("No detected peaks. Use run_detection() first.")
454
+ detected_params['plot'] = False
455
+ fitted_params['plot'] = False
456
+ matched_params['plot'] = False
457
+ if self.img_container_fit_list is None and fitted_params.get('plot', True):
458
+ self.logger.info("No fitted peaks. Use run_fitting() first.")
459
+ fitted_params['plot'] = False
460
+ matched_params['plot'] = False
461
+ if self.container_match_list is None and matched_params.get('plot', True):
462
+ self.logger.info("No matched peaks. Use run_matching() first.")
463
+ matched_params['plot'] = False
464
+ if not hasattr(self.pygid_conversion, 'img_gid_q'):
465
+ raise ValueError("img_gid_q is not available in pygid.Conversion."
466
+ "Use plotting before saving.")
467
+ def delete_peak(self, entry=None, frame_num=None, peak_id = None):
468
+ _delete_peak(self, entry=entry, frame_num=frame_num, peak_id=peak_id)
469
+
470
+ def get_detected_peaks(self, entry=None, frame_num=None):
471
+ return _get_detected_peaks(self.nexus, entry=entry, frame_num=frame_num)
472
+
473
+ def get_fitted_peaks(self, entry=None, frame_num=None):
474
+ return _get_fitted_peaks(self.nexus, entry=entry, frame_num=frame_num)
475
+
476
+ def get_matched_peaks(self, entry=None, frame_num=None):
477
+ return _get_matched_peaks(self.nexus, entry=entry, frame_num=frame_num)
478
+
479
+ def add_peak(self, entry=None, frame_num=None,
480
+ angle=None, angle_width = None,
481
+ radius = None, radius_width = None,
482
+ q_xy = None, q_z = None,
483
+ dq_xy = None, dq_z = None,):
484
+ _add_peak(self, entry=entry, frame_num=frame_num,
485
+ angle=angle, angle_width=angle_width,
486
+ radius=radius, radius_width=radius_width,
487
+ q_xy=q_xy, q_z=q_z,
488
+ dq_xy=dq_xy, dq_z=dq_z,
489
+ )
490
+ def draw_box(self, entry=None, frame_num=None):
491
+ _draw_box(self, entry=entry, frame_num=frame_num)