Tutorial 7: Operations with peaks

The peak list can be modified using the mlgidGUI. This interactive interface allows users to visualize, adjust, and save peak selections directly to a NeXus file.

In addition, mlgidBASE provides basic functionality to delete and add peaks. First, run the analysis:

from mlgidbase import mlgidBASE
filename = '../../example/BA2PbI4.h5'
analysis = mlgidBASE(filename=filename)
analysis.run_detection()
analysis.run_fitting()
analysis.run_matching(
    cif_prepr = r'../../example/prepr_cifs.pickle',
    peaks_type='segments')

analysis.run_matching(
    cif_prepr = r'../../example/prepr_cifs.pickle',
    peaks_type='rings')
2026-07-21 15:48:06.585488598 [W:onnxruntime:Default, device_discovery.cc:283 GetGpuDevices] Failed to detect devices under "/sys/class/drm/card0": device_discovery.cc:93 ReadFileContents Failed to open file: "/sys/class/drm/card0/device/vendor"
INFO - Loading model
---------------------------------------------------------------------------
InvalidProtobuf                           Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/mlgidbase/envs/latest/lib/python3.11/site-packages/mlgidbase/mlgiddetect_functions.py:68, in load_inference(analysis)
     67 try:
---> 68     analysis.imp_detect = Inference(analysis.config_detect)
     69 except:

File ~/checkouts/readthedocs.org/user_builds/mlgidbase/envs/latest/lib/python3.11/site-packages/mlgiddetect/inference/inference.py:32, in Inference.__init__(self, config)
     31     sess_options.intra_op_num_threads = 1
---> 32 self.sess = rt.InferenceSession(model_path, providers=preferred_providers, sess_options=sess_options)

File ~/checkouts/readthedocs.org/user_builds/mlgidbase/envs/latest/lib/python3.11/site-packages/onnxruntime/capi/onnxruntime_inference_collection.py:528, in InferenceSession.__init__(self, path_or_bytes, sess_options, providers, provider_options, **kwargs)
    527 try:
--> 528     self._create_inference_session(providers, provider_options, disabled_optimizers)
    529 except (ValueError, RuntimeError) as e:

File ~/checkouts/readthedocs.org/user_builds/mlgidbase/envs/latest/lib/python3.11/site-packages/onnxruntime/capi/onnxruntime_inference_collection.py:623, in InferenceSession._create_inference_session(self, providers, provider_options, disabled_optimizers)
    622 if self._model_path:
--> 623     sess = C.InferenceSession(session_options, self._model_path, True, self._read_config_from_model)
    624 else:

InvalidProtobuf: [ONNXRuntimeError] : 7 : INVALID_PROTOBUF : Load model from /home/docs/.local/share/mlgiddetect/dino.onnx failed:Protobuf parsing failed.

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
Cell In[1], line 4
      1 from mlgidbase import mlgidBASE
      2 filename = '../../example/BA2PbI4.h5'
      3 analysis = mlgidBASE(filename=filename)
----> 4 analysis.run_detection()
      5 analysis.run_fitting()
      6 analysis.run_matching(
      7     cif_prepr = r'../../example/prepr_cifs.pickle',

File ~/checkouts/readthedocs.org/user_builds/mlgidbase/envs/latest/lib/python3.11/site-packages/mlgidbase/main.py:194, in mlgidBASE.run_detection(self, entry, frame_num, config_detect, model_type)
    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 
   (...)    192         Type of detection model to use (e.g., 'faster_rcnn', 'dino').
    193     """
--> 194     _run_detection(self, entry, frame_num, config_detect, model_type)

File ~/checkouts/readthedocs.org/user_builds/mlgidbase/envs/latest/lib/python3.11/site-packages/mlgidbase/mlgiddetect_functions.py:48, in _run_detection(analysis, entry, frame_num, config_detect, model_type)
     43 # if model_type is not None:
     44 #     if analysis.config_detect.MODEL_TYPE != model_type:
     45 #         analysis.config_detect.MODEL_TYPE = model_type
     46 #         analysis.imp_detect = None
     47 if analysis.imp_detect is None:
---> 48     load_inference(analysis)
     50 if not analysis.from_nexus:
     51     if frame_num != 1 and not frame_num is None:

File ~/checkouts/readthedocs.org/user_builds/mlgidbase/envs/latest/lib/python3.11/site-packages/mlgidbase/mlgiddetect_functions.py:70, in load_inference(analysis)
     68     analysis.imp_detect = Inference(analysis.config_detect)
     69 except:
---> 70     raise ValueError("Detection failed. Couldn't load the model.")

ValueError: Detection failed. Couldn't load the model.

Segments

Visualize the matched peaks

# Visualization settings for matched peaks (multiple structures in solution)
matched_params = {
    'solution': None,              # list of structures to display (None = all)
    'plot_segments': True,         # show matched peak positions
    'marker': ['s', 's', 's'],     # marker shapes for each structure
    'marker_size': [50, 50, 50],   # marker sizes for each structure
    'marker_facecolor': ["none", "none", "none"],  # marker fill colors
    'marker_edgecolor': ["winter", 'blue', 'green'], # marker edge colors per structure
    'plot_rings': False,            # draw matched rings
    'line_width': [1, 1, 1],       # line widths for each structure
    'line_style': ["--", "--", "--"], # line styles for each structure
    'line_color': ["bone", 'blue', 'bone'], # line colors per structure
    'plot_id': True,              # display peak IDs
    'text_color': 'white',         # color of ID labels
    'text_size': 10,                # font size of ID labels
    'legend': True,                # show legend for multiple structures
    'plot': True,                 # enable/disable plotting of matched peaks
}

# Plot analysis results with selected matched peak visualization
analysis.plot_analysis_results(
    matched_params=matched_params,
    entry='entry_0000',  # select entry
    frame_num=0,          # select frame
    clims=(50, 5e2),     # color map
)
../_images/35fea8b41140b59177afd0bfae333285921fdf1be469b0f6df64ead8e5edd9da.png

Delete the peak #94

analysis.delete_peak(
    entry='entry_0000',
    frame_num=0,
    peak_id=94 # peak number
)

The peak dissapears from all: detected, fitted and matched (all solutions and structures) peak lists.

analysis.plot_analysis_results(
    matched_params=matched_params,
    entry='entry_0000',  # select entry
    frame_num=0,          # select frame
    clims=(50, 5e2),     # color map
)
../_images/35fea8b41140b59177afd0bfae333285921fdf1be469b0f6df64ead8e5edd9da.png

Add a box for the missing peak at q_xy = 2.25, q_z=2.5. NOTE: it appears only in detected peaks

analysis.add_peak(
    entry='entry_0000',
    frame_num=0,
    q_xy = 2.25, # box center in horizontal direction (Å⁻¹)
    dq_xy=0.1,   # box wight in horizontal direction (Å⁻¹)
    q_z=2.5,     # box center in vertical direction (Å⁻¹)
    dq_z=0.06    # box wight in vertical direction (Å⁻¹)
)
INFO - Peak id#93 has been added

Run fitting and matching:

analysis.run_fitting()
analysis.run_matching(
    cif_prepr = r'../../example/prepr_cifs.pickle',
    peaks_type='segments')

analysis.run_matching(
    cif_prepr = r'../../example/prepr_cifs.pickle',
    peaks_type='rings')
INFO - Saved fitted peaks to file: ../../example/BA2PbI4.h5, entry: entry_0000, frame: 0
INFO - cif_prepr is already set. The previous cif_prepr is to be used
INFO - Saved matched peaks to file: ../../example/BA2PbI4.h5, entry: entry_0000, frame: 0
INFO - cif_prepr is already set. The previous cif_prepr is to be used
INFO - Saved matched peaks to file: ../../example/BA2PbI4.h5, entry: entry_0000, frame: 0
analysis.plot_analysis_results(
    matched_params=matched_params,
    entry='entry_0000',  # select entry
    frame_num=0,          # select frame
    clims=(50, 5e2),     # color map
)
../_images/189b7ca1ae05b961e80303a968c789060f46b29c8379561ffd11ec4c48eb6928.png

Note: The datasets in the NeXus file are modified when peaks are added or deleted.

Rings

Repeat the procedure for rings. Visualize the matched rings:

# Visualization settings for matched peaks (multiple structures in solution)
matched_params = {
    'solution': None,              # list of structures to display (None = all)
    'plot_segments': False,         # show matched peak positions
    'marker': ['s', 's', 's'],     # marker shapes for each structure
    'marker_size': [50, 50, 50],   # marker sizes for each structure
    'marker_facecolor': ["none", "none", "none"],  # marker fill colors
    'marker_edgecolor': ["winter", 'blue', 'green'], # marker edge colors per structure
    'plot_rings': True,            # draw matched rings
    'line_width': [1, 1, 1],       # line widths for each structure
    'line_style': ["--", "--", "--"], # line styles for each structure
    'line_color': ["bone", 'blue', 'bone'], # line colors per structure
    'plot_id': True,              # display peak IDs
    'text_color': 'black',         # color of ID labels
    'text_size': 12,                # font size of ID labels
    'legend': True,                # show legend for multiple structures
    'plot': True,                 # enable/disable plotting of matched peaks
}

# Plot analysis results with selected matched peak visualization
analysis.plot_analysis_results(
    matched_params=matched_params,
    entry='entry_0000',  # select entry
    frame_num=0,          # select frame
    clims=(50, 5e2),     # color map
)
../_images/c91c2bdfb0a52a66bd4b80ccc498ae51839a593f467f224b4cef395a7154ff36.png

Delete the peak #17

analysis.delete_peak(
    entry='entry_0000',
    frame_num=0,
    peak_id=17
)

Plot:

analysis.plot_analysis_results(
    matched_params=matched_params,
    entry='entry_0000',  # select entry
    frame_num=0,          # select frame
    clims=(50, 5e2),     # color map
)
../_images/b76bd3663b87a4c64b496d5430bd15228b448ef60838c233b490a6233783fe5d.png

Add a ring box:

analysis.add_peak(
    entry='entry_0000',
    frame_num=0,
    angle=45,       # angular center of the box (in deg). 45 for rings.
    angle_width=90, # angular width of the box (in deg). 90 for rings.
    radius=2.5,     # radial center of the box (Å⁻¹).
    radius_width=0.05, # radial width of the box (Å⁻¹)
)
INFO - Peak id#93 has been added

Run fitting and matching:

analysis.run_fitting()
analysis.run_matching(
    cif_prepr = r'../../example/prepr_cifs.pickle',
    peaks_type='segments')

analysis.run_matching(
    cif_prepr = r'../../example/prepr_cifs.pickle',
    peaks_type='rings')
INFO - Saved fitted peaks to file: ../../example/BA2PbI4.h5, entry: entry_0000, frame: 0
INFO - cif_prepr is already set. The previous cif_prepr is to be used
INFO - Saved matched peaks to file: ../../example/BA2PbI4.h5, entry: entry_0000, frame: 0
INFO - cif_prepr is already set. The previous cif_prepr is to be used
INFO - No solutions for (../../example/BA2PbI4.h5, entry: entry_0000, frame: 0) was found. Try to decrease threshold

Plot:

analysis.plot_analysis_results(
    matched_params=matched_params,
    entry='entry_0000',  # select entry
    frame_num=0,          # select frame
    clims=(50, 5e2),     # color map
)
../_images/d46e34e1fc61379add3cef6af5e47ddc44c5cea07822097c1cd6ca688a2aba61.png

Peak tracking with mlgidBASE

The track_peaks method in mlgidBASE provides functionality for temporal tracking of fitted peaks across frames using an IoU-based graph construction approach. Peaks are linked between frames based on geometric overlap in polar space, and resulting trajectories are extracted as connected components of a graph.


First, load the dataset and initialize the analysis object:

from mlgidbase import mlgidBASE

filename = '../../example/BA2PbI4.h5'
analysis = mlgidBASE(filename=filename)

Peak tracking is performed using the track_peaks method. The method requires a similarity threshold (IoU), a minimum track length, and a choice of physical quantity to be extracted along the trajectories.

plot_params = {
    'plot_result': False,              # enable visualization of tracked peaks
    'save_fig': False,               # save figure to disk
    'path_to_save_fig': 'peak_tracking.png',  # output path (if saving enabled)
    'line_width': 1,                 # line width for tracks
    'line_style': 'o-',              # line style for trajectories
    'marker_size': 3,                # marker size for peak points
}

axis_arr, amplitude, frame_num = analysis.track_peaks(
    threshold=0.1,     # IoU threshold for peak connectivity
    length=1,         # minimum number of linked frames per track
    axis='q_z',        # quantity to extract along trajectories
    plot_params=plot_params
)

Output

The method returns:

  • axis : list[ndarray] – List of arrays with the selected tracked quantity corresponding to all peak instances.

  • amplitude : list[ndarray] – List of arrays with corresponding amplitudes values for all tracked peaks.

  • frame_num : list[ndarray] – List of arrays with frame numbers where peak are present.

Notes on tracking algorithm

  • Peaks are represented as bounding boxes in (angle, radius) space.

  • Pairwise overlap is quantified using Intersection over Union (IoU).

  • A graph is constructed where edges correspond to IoU ≥ threshold.

  • Connected components of the graph define peak trajectories.

  • Only tracks with length ≥ length are retained.


Separate visualization:

import matplotlib as plt
for i in range(len(axis_arr)):
    plt.scatter(frame_num[i],axis_arr[i])