Skip to content

Commit de595c0

Browse files
authored
Merge pull request #2524 from kif/2512_pilx_zigzag
Implement the storage of the `map_ptr` in
2 parents 4dcf37e + 90f7ae2 commit de595c0

File tree

7 files changed

+145
-41
lines changed

7 files changed

+145
-41
lines changed

src/pyFAI/diffmap.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
__contact__ = "Jerome.Kieffer@ESRF.eu"
3131
__license__ = "MIT"
3232
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
33-
__date__ = "28/04/2025"
33+
__date__ = "14/05/2025"
3434
__status__ = "development"
3535
__docformat__ = 'restructuredtext'
3636

@@ -56,8 +56,7 @@
5656
from .io.ponifile import PoniFile
5757
from .worker import Worker
5858
from .utils.decorators import deprecated, deprecated_warning
59-
60-
DIGITS = [str(i) for i in range(10)]
59+
from string import digits as DIGITS
6160
Position = collections.namedtuple('Position', 'index slow fast')
6261

6362

@@ -105,6 +104,7 @@ def __init__(self, nbpt_fast=0, nbpt_slow=1, nbpt_rad=1000, nbpt_azim=None,
105104
self.nxdata_grp = None
106105
self.dataset = None
107106
self.dataset_error = None
107+
self.map_ptr = None
108108
self.inputfiles = []
109109
self.timing = []
110110
self.stats = False
@@ -524,6 +524,11 @@ def makeHDF5(self, rewrite=False):
524524
fast_motor_ds.attrs["interpretation"] = "scalar"
525525
fast_motor_ds.attrs["long_name"] = self.fast_motor_name
526526

527+
self.map_ptr = self.nxdata_grp.create_dataset("map_ptr", shape=(self.nbpt_slow,self.nbpt_fast), dtype=numpy.int32)
528+
self.map_ptr.attrs["interpretation"] = "image"
529+
self.map_ptr.attrs["long_name"] = "Frame index for given map position"
530+
531+
527532
if self.worker.do_2D():
528533
self.dataset = self.nxdata_grp.create_dataset(
529534
name="intensity",
@@ -719,24 +724,33 @@ def set_hdf5_input_dataset(self, dataset):
719724
return
720725
else:
721726
self.stored_input.add(id_)
722-
# Process 0: measurement group
727+
# Process 0: measurement group & source group
723728
if "measurement" in self.entry_grp:
724729
measurement_grp = self.entry_grp["measurement"]
725730
else:
726731
measurement_grp = self.nxs.new_class(self.entry_grp, "measurement", "NXdata")
732+
733+
if "source" in self.nxdata_grp.parent:
734+
source_grp = self.nxdata_grp.parent["source"]
735+
else:
736+
source_grp = self.nxs.new_class(self.nxdata_grp.parent, "source", "NXcollection")
737+
727738
here = os.path.dirname(os.path.abspath(self.nxs.filename))
728739
there = os.path.abspath(dataset.file.filename)
729740
name = f"images_{len(self.stored_input):04d}"
730-
measurement_grp[name] = h5py.ExternalLink(os.path.relpath(there, here), dataset.name)
741+
source_grp[name] = measurement_grp[name] = h5py.ExternalLink(os.path.relpath(there, here), dataset.name)
742+
731743
if "signal" not in measurement_grp.attrs:
732744
measurement_grp.attrs["signal"] = name
733745

746+
734747
def process_one_frame(self, frame):
735748
"""
736749
:param frame: 2d numpy array with an image to process
737750
"""
738751
self._idx += 1
739752
pos = self.get_pos(None, self._idx)
753+
self.map_ptr[pos.slow, pos.fast] = self._idx
740754
shape = self.dataset.shape
741755
if pos.slow + 1 > shape[0]:
742756
self.dataset.resize((pos.slow + 1,) + shape[1:])

src/pyFAI/gui/diffmap_widget.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
__contact__ = "Jerome.Kieffer@ESRF.eu"
3131
__license__ = "MIT"
3232
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
33-
__date__ = "13/05/2025"
33+
__date__ = "14/05/2025"
3434
__status__ = "development"
3535
__docformat__ = 'restructuredtext'
3636

@@ -704,7 +704,7 @@ def update_slice(self, *args):
704704
"""
705705
Update the slice
706706
"""
707-
logger.info("update_slice", args)
707+
logger.info("DiffMapWidget.update_slice %s", args)
708708
if self.radial_data is None:
709709
return
710710
try:

src/pyFAI/gui/matplotlib.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# coding: utf-8
22
# /*##########################################################################
33
#
4-
# Copyright (C) 2016-2018 European Synchrotron Radiation Facility
4+
# Copyright (C) 2016-2025 European Synchrotron Radiation Facility
55
#
66
# Permission is hereby granted, free of charge, to any person obtaining a copy
77
# of this software and associated documentation files (the "Software"), to deal
@@ -33,7 +33,7 @@
3333

3434
__authors__ = ["T. Vincent"]
3535
__license__ = "MIT"
36-
__date__ = "13/05/2025"
36+
__date__ = "16/05/2025"
3737

3838
import sys
3939
import logging
@@ -54,22 +54,18 @@ def _configure(backend, backend_qt4=None, check=False):
5454
valid = valid and matplotlib.rcParams['backend.qt4'] == backend_qt4
5555

5656
if not valid:
57-
_logger.warning('matplotlib already loaded, setting its backend may not work')
57+
msg = f'Matplotlib already loaded with backend `{matplotlib.rcParams["backend"]}`, setting its backend to `{backend}` may not work!'
58+
_logger.warning(msg)
5859
return
5960
matplotlib.rcParams['backend'] = backend
6061
if backend_qt4 is not None:
6162
matplotlib.rcParams['backend.qt4'] = backend_qt4
6263

63-
6464
if qt.BINDING in ('PySide', 'PyQt4'):
6565
_configure('Qt4Agg', qt.BINDING, check=_check_matplotlib)
6666
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg # noqa
6767

68-
elif qt.BINDING in ('PyQt5', 'PySide2'):
69-
_configure('Qt5Agg', check=_check_matplotlib)
70-
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg # noqa
71-
72-
elif qt.BINDING in ('PyQt6', 'PySide6'):
68+
elif qt.BINDING in ('PyQt6', 'PySide6', 'PyQt5', 'PySide2'):
7369
_configure('QtAgg', check=_check_matplotlib)
7470
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg # noqa
7571

src/pyFAI/gui/pilx/MainWindow.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@
3333
__contact__ = "loic.huder@ESRF.eu"
3434
__license__ = "MIT"
3535
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
36-
__date__ = "20/02/2025"
36+
__date__ = "16/05/2025"
3737
__status__ = "development"
3838

3939
from typing import Tuple
40+
from string import digits
4041
import json
4142
import h5py
4243
import logging
@@ -117,6 +118,9 @@ def __init__(self) -> None:
117118
self._background_point = None
118119
self.worker_config = None
119120

121+
# declaration of instance variables
122+
self._map_ptr = None # This is the map of the indices of input frame
123+
120124
def initData(self,
121125
file_name: str,
122126
dataset_path: str="/entry_0000/measurement/images_0001",
@@ -134,14 +138,14 @@ def initData(self,
134138
map_data = get_dataset(nxprocess, "result/intensity")[()].sum(axis=-1)
135139
try:
136140
slow = get_dataset(nxprocess, "result/slow")
137-
except Exception:
141+
except (KeyError, RuntimeError):
138142
slow_label = slow_values = None
139143
else:
140144
slow_label = slow.attrs.get("long_name", "Y")
141145
slow_values = slow[()]
142146
try:
143147
fast = get_dataset(nxprocess, "result/fast")
144-
except Exception:
148+
except (KeyError, RuntimeError):
145149
fast_values = fast_label = None
146150
else:
147151
fast_label = fast.attrs.get("long_name", "X")
@@ -164,15 +168,14 @@ def initData(self,
164168
else:
165169
self._offset = 0
166170

167-
# Find source dataset paths
168-
cnt = 0
169-
for char in dataset_path[-1::-1]:
170-
if char.isdigit():
171-
cnt += 1
172-
else:
173-
break
171+
try:
172+
self._map_ptr = get_dataset(nxprocess, "result/map_ptr")[()]
173+
except (KeyError, RuntimeError):
174+
logger.warning("No `map_ptr` dataset in NXdata: guessing the frame indices !")
175+
self._map_ptr = numpy.arange(self._offset, self._offset + map_data.size)
176+
self._map_ptr.shape = map_data.shape
174177

175-
_dataset_path = dataset_path[:-cnt]
178+
_dataset_path = dataset_path.rstrip(digits)
176179
path, base = posixpath.split(_dataset_path)
177180

178181
try:
@@ -282,7 +285,12 @@ def displayImageAtIndices(self, indices: ImageIndices):
282285
with h5py.File(self._file_name, "r") as h5file:
283286
nxprocess = h5file[self._nxprocess_path]
284287
map_shape = get_dataset(nxprocess, "result/intensity").shape
285-
image_index = row * map_shape[1] + col + self._offset
288+
if self._map_ptr is None:
289+
logger.warning("No `map_ptr` defined: guessing the frame indices !")
290+
image_index = row * map_shape[1] + col + self._offset
291+
else:
292+
image_index = self._map_ptr[row, col]
293+
286294
if self._dataset_paths:
287295
for dataset_path, size in self._dataset_paths.items():
288296
if image_index < size:

src/pyFAI/io/diffmap_config.py

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,19 @@
3131
__contact__ = "Jerome.Kieffer@ESRF.eu"
3232
__license__ = "MIT"
3333
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
34-
__date__ = "28/04/2025"
34+
__date__ = "14/05/2025"
3535
__status__ = "development"
3636
__docformat__ = 'restructuredtext'
3737

3838
import os
39+
import posixpath
3940
import copy
4041
import json
4142
import logging
4243
logger = logging.getLogger(__name__)
4344
from collections import namedtuple
45+
import numpy
46+
import h5py
4447
from .tree import TreeItem
4548
from .integration_config import dataclass, ClassVar, WorkerConfig, fields, asdict
4649
from .nexus import is_hdf5
@@ -55,7 +58,7 @@ class MotorRange:
5558
5659
:param start: Begining of the movement
5760
:param stop: End of the movement, included
58-
:param step: Number of points (i.e. numberof steps + 1)
61+
:param points: Number of points (i.e. numberof steps + 1)
5962
:param name: Name of the motor
6063
"""
6164
start: float = None
@@ -83,9 +86,13 @@ def __repr__(self):
8386

8487
@property
8588
def step_size(self):
86-
if self.points < 1:
89+
if self.points < 2:
8790
return
88-
return (self.stop-self.start)/self.points
91+
return (self.stop-self.start)/self.steps
92+
93+
@property
94+
def steps(self):
95+
return self.points-1
8996

9097
@classmethod
9198
def _parse_old_config(cls, dico, prefix="slow"):
@@ -104,6 +111,74 @@ def _parse_old_config(cls, dico, prefix="slow"):
104111
self.stop = float(rng[-1])
105112
return self
106113

114+
115+
def parse_bliss(filename, motors, transpose=False):
116+
"""Parse a bliss master file (HDF5) for 2 motors which were scanned
117+
and calculate the frame index of each pixel, returned as a map.
118+
119+
:param filename: name of the Bliss master file
120+
:param motors: 2-tuple of 1d-datasets names in the masterfile. Both have the same shape.
121+
:param transpose: set to True to have the fast dimention along y instead of x.
122+
:return: map_ptr, [MotorRange_y, MotorRange_x]
123+
"""
124+
def build_dict(posi):
125+
"histogram like procedure with dict"
126+
dico = {}
127+
for i in posi:
128+
if i in dico:
129+
dico[i]+=1
130+
else:
131+
dico[i]=0
132+
return dico
133+
134+
if len(motors) != 2:
135+
raise RuntimeError("Expected a list of 2 path pointing to 1d datasets, got {motors}!")
136+
137+
#read all motor position datasets
138+
motor_raw = {}
139+
with h5py.File(filename) as h:
140+
for name in motors:
141+
motor_raw[name] = h[name][()]
142+
143+
if (motor_raw[motors[0]].shape != motor_raw[motors[1]].shape):
144+
raise RuntimeError("Expected a list of 2 1d datasets, with the same shape!")
145+
146+
ranges = {}
147+
for name in motor_raw:
148+
dico = build_dict(motor_raw[name])
149+
mr = MotorRange(name=posixpath.basename(name))
150+
mr.start = min(dico.keys())
151+
mr.stop = max(dico.keys())
152+
mr.points = len(dico.keys())
153+
ranges[name] = mr
154+
155+
# The slow motor is often still, so its speed is null, much more often than the fast motor.
156+
ordered_names = []
157+
dm1 = numpy.diff(motor_raw[motors[0]])
158+
dm2 = numpy.diff(motor_raw[motors[1]])
159+
if (dm1==0).sum()>(dm2==0).sum():
160+
ordered_names = motors
161+
else:
162+
ordered_names = motors[-1::-1]
163+
ordered_ranges = [ranges[i] for i in ordered_names]
164+
165+
slow = ordered_ranges[0]
166+
fast = ordered_ranges[1]
167+
slow_pos = motor_raw[ordered_names[0]]
168+
fast_pos = motor_raw[ordered_names[1]]
169+
170+
#Build the map
171+
map_ptr = numpy.zeros((slow.points, fast.points), dtype=numpy.int32)
172+
slow_idx = numpy.round((slow_pos - slow.start) / slow.step_size).astype(int)
173+
fast_idx = numpy.round((fast_pos - fast.start) / fast.step_size).astype(int)
174+
map_ptr[slow_idx, fast_idx] = numpy.arange(slow.points*fast.points, dtype=int)
175+
176+
if transpose:
177+
return map_ptr.T, ordered_ranges[-1::-1]
178+
else:
179+
return map_ptr, ordered_ranges
180+
181+
107182
DataSetNT = namedtuple("DataSet", ("path", "h5", "nframes", "shape"), defaults=[None, None, None])
108183

109184
@dataclass
@@ -302,7 +377,6 @@ def from_dict(cls, dico, inplace=False):
302377
if value is None:
303378
to_init[key] = value
304379
elif isinstance(value, (list, tuple)):
305-
print(klass, value)
306380
if "from_serialized" in dir(klass):
307381
to_init[key] = klass.from_serialized(value)
308382
else:
@@ -324,8 +398,6 @@ def from_dict(cls, dico, inplace=False):
324398
self.fast_motor = fast
325399
self.fast_motor = slow
326400

327-
# print(self)
328-
329401
for key in cls.GUESSED:
330402
if key in dico:
331403
dico.pop(key)

src/pyFAI/test/test_bug_regression.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
__contact__ = "Jerome.Kieffer@esrf.fr"
3737
__license__ = "MIT"
3838
__copyright__ = "2015-2024 European Synchrotron Radiation Facility, Grenoble, France"
39-
__date__ = "16/05/2025"
39+
__date__ = "26/05/2025"
4040

4141
import sys
4242
import os
@@ -662,9 +662,6 @@ def test_bug_2525(self):
662662
self.assertEqual(sp.shape, sp_dp.shape)
663663

664664

665-
666-
667-
668665
class TestBug1703(unittest.TestCase):
669666
"""
670667
Check the normalization affect propely the propagated errors/intensity

0 commit comments

Comments
 (0)