From dce75d8db35ae48010f02e07b718ebe534bb8df7 Mon Sep 17 00:00:00 2001 From: Edgar Date: Sat, 6 Jun 2026 13:41:09 +0200 Subject: [PATCH 1/6] docs --- src/pyFAI/goniometer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pyFAI/goniometer.py b/src/pyFAI/goniometer.py index f5e3f6825..d3a48ecb6 100644 --- a/src/pyFAI/goniometer.py +++ b/src/pyFAI/goniometer.py @@ -672,11 +672,13 @@ def get_position(self): """This method is in charge of calculating the motor position from metadata/label/...""" return self.pos_function(self.metadata) - def extract_cp(self, max_rings=None, pts_per_deg=1.0, Imin=0): + def extract_cp(self, method="massif", max_rings=None, pts_per_deg=1.0, Imin=0): """Performs an automatic keypoint extraction and update the geometry refinement part + :param method: method to use for keypoint extraction, default is "massif" :param max_ring: extract at most N rings from the image :param pts_per_deg: number of control points per azimuthal degree (increase for better precision) + :param Imin: minimum intensity for a pixel to be considered control point """ if self.image is None: raise RuntimeError("To perform control point extraction, a data image must be provided: pyFAI.goniometer.SingleGeometry(image=...)") From 63bb165532d8c7ad43deaaa0144bdaf5bece2568 Mon Sep 17 00:00:00 2001 From: Edgar Date: Sat, 6 Jun 2026 13:47:55 +0200 Subject: [PATCH 2/6] generic method, only massif --- src/pyFAI/goniometer.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/pyFAI/goniometer.py b/src/pyFAI/goniometer.py index d3a48ecb6..df0fa32a1 100644 --- a/src/pyFAI/goniometer.py +++ b/src/pyFAI/goniometer.py @@ -603,6 +603,8 @@ class SingleGeometry(object): """This class represents a single geometry of a detector position on a goniometer arm """ + + AVAILABLE_METHODS = ("massif", "blob", "watershed") def __init__(self, label, image=None, metadata=None, pos_function=None, control_points=None, calibrant=None, detector=None, geometry=None): @@ -671,7 +673,23 @@ def __init__(self, label, image=None, metadata=None, pos_function=None, def get_position(self): """This method is in charge of calculating the motor position from metadata/label/...""" return self.pos_function(self.metadata) - + + def get_method(self, method): + """ + Build the method for control point extraction. + """ + if method not in self.AVAILABLE_METHODS: + return + + if method == "massif": + if self.massif is None: + if self.detector: + mask = self.detector.dynamic_mask(self.image) + else: + mask = None + self.massif = Massif(self.image, mask) + return self.massif + def extract_cp(self, method="massif", max_rings=None, pts_per_deg=1.0, Imin=0): """Performs an automatic keypoint extraction and update the geometry refinement part @@ -686,12 +704,9 @@ def extract_cp(self, method="massif", max_rings=None, pts_per_deg=1.0, Imin=0): if not self.wavelength: raise RuntimeError("To perform control point extraction, a wavelength must be provided either through the calibrant or through the geometry.") - if self.massif is None: - if self.detector: - mask = self.detector.dynamic_mask(self.image) - else: - mask = None - self.massif = Massif(self.image, mask) + self.method = self.get_method(method) + if self.method is None: + raise ValueError(f"Method {method} not found, available methods are: {self.AVAILABLE_METHODS}") tth = numpy.array([i for i in self.calibrant.get_2th() if i is not None]) tth = numpy.unique(tth) @@ -748,7 +763,7 @@ def extract_cp(self, method="massif", max_rings=None, pts_per_deg=1.0, Imin=0): logger.info("Extracting datapoint for ring %s (2theta = %.2f deg); " + "searching for %i pts out of %i with I>%.1f, dmin=%.1f", i, numpy.degrees(tth[i]), keep, size2, upper_limit, dist_min) - res = self.massif.peaks_from_area(mask2, Imin=Imin, keep=keep, dmin=dist_min, seed=seeds, ring=i) + res = self.method.peaks_from_area(mask2, Imin=Imin, keep=keep, dmin=dist_min, seed=seeds, ring=i) cp.append(res, i) self.control_points = cp self.geometry_refinement.data = numpy.asarray(cp.getList(), dtype=numpy.float64) From 41d666f5479b7990614bc3a355b7a9d2f6b021ef Mon Sep 17 00:00:00 2001 From: Edgar Date: Sat, 6 Jun 2026 13:48:59 +0200 Subject: [PATCH 3/6] compatibility --- src/pyFAI/goniometer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyFAI/goniometer.py b/src/pyFAI/goniometer.py index df0fa32a1..4f3320c9a 100644 --- a/src/pyFAI/goniometer.py +++ b/src/pyFAI/goniometer.py @@ -690,13 +690,13 @@ def get_method(self, method): self.massif = Massif(self.image, mask) return self.massif - def extract_cp(self, method="massif", max_rings=None, pts_per_deg=1.0, Imin=0): + def extract_cp(self, max_rings=None, pts_per_deg=1.0, Imin=0, method="massif"): """Performs an automatic keypoint extraction and update the geometry refinement part - :param method: method to use for keypoint extraction, default is "massif" :param max_ring: extract at most N rings from the image :param pts_per_deg: number of control points per azimuthal degree (increase for better precision) :param Imin: minimum intensity for a pixel to be considered control point + :param method: method to use for keypoint extraction, default is "massif" """ if self.image is None: raise RuntimeError("To perform control point extraction, a data image must be provided: pyFAI.goniometer.SingleGeometry(image=...)") From 8e481e9519671d404081cdb129f51535dc6e3ff6 Mon Sep 17 00:00:00 2001 From: Edgar Date: Sat, 6 Jun 2026 13:50:38 +0200 Subject: [PATCH 4/6] not implemented --- src/pyFAI/goniometer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pyFAI/goniometer.py b/src/pyFAI/goniometer.py index 4f3320c9a..8ad6d9e93 100644 --- a/src/pyFAI/goniometer.py +++ b/src/pyFAI/goniometer.py @@ -689,6 +689,10 @@ def get_method(self, method): mask = None self.massif = Massif(self.image, mask) return self.massif + elif method == "blob": + raise NotImplementedError("Blob method is not implemented yet") + elif method == "watershed": + raise NotImplementedError("Watershed method is not implemented yet") def extract_cp(self, max_rings=None, pts_per_deg=1.0, Imin=0, method="massif"): """Performs an automatic keypoint extraction and update the geometry refinement part From eabbb2ff7a0b5128ec82794d83cf80a10363f136 Mon Sep 17 00:00:00 2001 From: Edgar Date: Sat, 6 Jun 2026 13:52:23 +0200 Subject: [PATCH 5/6] typo docs --- src/pyFAI/goniometer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyFAI/goniometer.py b/src/pyFAI/goniometer.py index 8ad6d9e93..9b922d716 100644 --- a/src/pyFAI/goniometer.py +++ b/src/pyFAI/goniometer.py @@ -697,7 +697,7 @@ def get_method(self, method): def extract_cp(self, max_rings=None, pts_per_deg=1.0, Imin=0, method="massif"): """Performs an automatic keypoint extraction and update the geometry refinement part - :param max_ring: extract at most N rings from the image + :param max_rings: extract at most N rings from the image :param pts_per_deg: number of control points per azimuthal degree (increase for better precision) :param Imin: minimum intensity for a pixel to be considered control point :param method: method to use for keypoint extraction, default is "massif" From bfc916744367ad524b482eb415b9e0e13c01dd70 Mon Sep 17 00:00:00 2001 From: Edgar Date: Sat, 6 Jun 2026 17:15:20 +0200 Subject: [PATCH 6/6] add blob --- src/pyFAI/goniometer.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pyFAI/goniometer.py b/src/pyFAI/goniometer.py index 9b922d716..95e23be11 100644 --- a/src/pyFAI/goniometer.py +++ b/src/pyFAI/goniometer.py @@ -46,6 +46,7 @@ from scipy.optimize import minimize from silx.image import marchingsquares from .massif import Massif +from .blob_detection import BlobDetection from .control_points import ControlPoints from .detectors import detector_factory, Detector from .geometry import Geometry @@ -669,6 +670,7 @@ def __init__(self, label, image=None, metadata=None, pos_function=None, self.detector = self.geometry_refinement.detector self.pos_function = pos_function self.massif = None + self.blob = None def get_position(self): """This method is in charge of calculating the motor position from metadata/label/...""" @@ -690,7 +692,14 @@ def get_method(self, method): self.massif = Massif(self.image, mask) return self.massif elif method == "blob": - raise NotImplementedError("Blob method is not implemented yet") + if self.blob is None: + if self.detector: + mask = self.detector.dynamic_mask(self.image) + else: + mask = None + self.blob = BlobDetection(self.image, mask) + self.blob.process() + return self.blob elif method == "watershed": raise NotImplementedError("Watershed method is not implemented yet")