Skip to content

Commit

Permalink
Merge pull request #2009 from kif/2003_orientation
Browse files Browse the repository at this point in the history
2003 orientation
  • Loading branch information
kif authored Dec 11, 2023
2 parents e6778ff + 58db30b commit ab6b31b
Show file tree
Hide file tree
Showing 16 changed files with 396 additions and 115 deletions.
1 change: 1 addition & 0 deletions bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def find_executable(target):
else:
logger.error("Script %s not found", script)
else:
logging.info("Running IPython by default")
logger.info("Patch the sys.argv: %s", sys.argv)
sys.path.insert(2, "")
try:
Expand Down
2 changes: 1 addition & 1 deletion build-deb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ then
if [ -z $debian_version ]
then
#we are probably on a ubuntu platform
debian_version=$(cat /etc/debian_version | cut -d/ -f1)
debian_version=$(< /etc/debian_version cut -d/ -f1)
case $debian_version in
squeeze)
debian_version=6
Expand Down
52 changes: 52 additions & 0 deletions doc/source/conventions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,55 @@ Note some specificities:
* The origin is at the bottom and differs from the *camera* convention where the origin is at the top. As a consequence, the sign of the ᵪ-angle gets inverted.
* The detector is seen from the sample and differs from the *camera* convention where the observer is behind the camera. As a consequence, the sign of the ᵪ-angle gets inverted.
* The former 2 inversions cancel each other.

Detector orientation
--------------------

Since 2023.12, it is possible to take into account the detector orientation in pyFAI.
There is a norme in photography for storing this kind of information in an EXIF tag as an integer with a value from 1 to 8::

1 2 3 4 5 6 7 8
o o o o
888888 888888 88 88 8888888888 88 88 8888888888
88 88 88 88 88 88 88 88 88 88 88 88
8888 8888 8888 8888 88 8888888888 8888888888 88
88 88 88 88 o o
88 88 888888 888888
o o
In photography the observer is behind the camera, thus the image's first pixel of the image (origin, noted (o)) is by default on the top-left.
This differes in two point with pyFAI's definition:

* pyFAI considers the frame seen from the sample, so in front of the camera, this change of point of view is equivalent to a flip-right-left of the image
* pyFAI considers the origin is a the bottom (left) of the image, so this corresponds to a flip-up-down of the image
* Those two image flip result in a default orientation identified as *BottomRight (3)* by default on all detectors.

The *orientation* is a property of the *detector*, it is **intentionnally unmuttable** to prevent errors.
For now, only the geometries 1 to 4 are supported, the rotated geometries can easily be accomodated using the *rot3* parameter during the calibration of the experimental setup


Converting the calibration from *Dioptas* to process data with *pyFAI*
......................................................................

Despite *Diotas* uses the *pyFAI* library to perform the geometry calculation, refinement and PONI-file management, it offers the ability to actualy flip/rotate the image
and not only the representation of the image on the screen.
As a consequence, the user has to remind the sequence of operations performed on the image and the position of the origin.
Moreover, the image is flipped up-down when loaded in *Dioptas*, making thinks even more complicated (origin: TopRight, orientation 2).
The support for the *orientation* of the image allows to treat with *pyFAI* a dataset calibrated with *Dioptas*.
For this, edit the PONI-file and find the line entitled *detector_config* and declare or modify the value for the orientation like this::

Detector_config: {"orientation": 2}

This will allow to treat the data.
Since the images are flipped, the direction of the azimuthal angle cannot match and the **chi** will not only differ in numeriacal values but most importantly in direction.
This has to be taken into account when performing texture analysis.

Conventions from the detector manufacturer
..........................................

Different detector manufacturer follow different convention, in addition to which acquisition software allow to save the image with some transformation applied.
Here are some information collected for the manufacture:

* Dectris: orientation 2: TopRight
* ... to be completed.

For now, all detectors are intialized with the orientation (3), i.e. it does not change with the convention used in pyFAI since 2010.
1 change: 1 addition & 0 deletions run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ def report_rst(cov, package, version="0.0.0", base="", inject_xml=None):
else:
fn = inject_xml
from lxml import etree

xml = etree.parse(fn)
classes = xml.xpath("//class")

Expand Down
7 changes: 4 additions & 3 deletions src/pyFAI/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
__authors__ = ["Jérôme Kieffer", "V. Valls"]
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "21/11/2023"
__date__ = "01/12/2023"
__status__ = "production"
__docformat__ = 'restructuredtext'
__all__ = ["date", "version_info", "strictversion", "hexversion", "debianversion",
Expand All @@ -61,15 +61,14 @@
"final": 15}

MAJOR = 2023
MINOR = 11
MINOR = 12
MICRO = 0
RELEV = "dev" # <16
SERIAL = 0 # <16

date = __date__

from collections import namedtuple
from argparse import ArgumentParser

_version_info = namedtuple("version_info", ["major", "minor", "micro", "releaselevel", "serial"])

Expand Down Expand Up @@ -130,6 +129,8 @@ def calc_hexversion(major=0, minor=0, micro=0, releaselevel="dev", serial=0, str
citation = "doi:10.1107/S1600576715004306"

if __name__ == "__main__":
from argparse import ArgumentParser

parser = ArgumentParser(usage="print the version of the software")
parser.add_argument("--wheel", action="store_true", dest="wheel", default=None,
help="print version formated for wheel")
Expand Down
68 changes: 44 additions & 24 deletions src/pyFAI/detectors/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
__contact__ = "[email protected]"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "24/11/2023"
__date__ = "07/12/2023"
__status__ = "stable"

import logging
Expand Down Expand Up @@ -572,14 +572,14 @@ def setFit2D(self, **kwarg):
elif kw == "splineFile":
self.set_splineFile(kwarg[kw])

def _calc_pixel_index_from_orientation(self, plus_one=False):
def _calc_pixel_index_from_orientation(self, center=True):
"""Calculate the pixel index when considering the different orientations"""
if plus_one: # used with corners
m1 = self.shape[0] + 1
m2 = self.shape[1] + 1
else: #used with centers
if center:
m1 = self.shape[0]
m2 = self.shape[1]
else: #corner
m1 = self.shape[0] + 1
m2 = self.shape[1] + 1

if self.orientation in (0,3):
r1 = numpy.arange(m1, dtype="float32")
Expand All @@ -597,6 +597,30 @@ def _calc_pixel_index_from_orientation(self, plus_one=False):
raise RuntimeError(f"Unsuported orientation: {self.orientation.name} ({self.orientation.value})")
return r1, r2

def _reorder_indexes_from_orientation(self, d1, d2, center=True):
"""Helper function to recalculate the index of pixels considering orientation
# Not +=: do not mangle in place arrays"""
if self.orientation in (0,3):
return d1, d2
if center:
shape1 = self.shape[0] - 1
shape2 = self.shape[1] - 1
else: #corner
shape1 = self.shape[0]
shape2 = self.shape[1]

if self.orientation==1:
d1 = shape1 - d1
d2 = shape2 - d2
elif self.orientation==2:
d1 = shape1 - d1
elif self.orientation == 4:
d2 = shape2 - d2
else:
raise RuntimeError(f"Unsuported orientation: {self.orientation.name} ({self.orientation.value})")
return d1, d2


def calc_cartesian_positions(self, d1=None, d2=None, center=True, use_cython=True):
"""
Calculate the position of each pixel center in cartesian coordinate
Expand All @@ -620,28 +644,24 @@ def calc_cartesian_positions(self, d1=None, d2=None, center=True, use_cython=Tru
"""
if self.shape:
if (d1 is None) or (d2 is None):
r1, r2 = self._calc_pixel_index_from_orientation(False)
d1 = expand2d(r1, self.shape[1], False)
d2 = expand2d(r2, self.shape[0], True)
r1, r2 = self._calc_pixel_index_from_orientation(center)
delta = 0 if center else 1
d1 = expand2d(r1, self.shape[1] + delta, False)
d2 = expand2d(r2, self.shape[0] + delta, True)
else:
if self.orientation in (0,3):
pass
elif self.orientation==1:
d1 = self.shape[0] - 1 - d1
d2 = self.shape[1] - 1 - d2
elif self.orientation==2:
d1 = self.shape[0] - 1 - d1
elif self.orientation == 4:
d2 = self.shape[1] - 1 - d2
else:
raise RuntimeError(f"Unsuported orientation: {self.orientation.name} ({self.orientation.value})")

d1, d2 = self._reorder_indexes_from_orientation(d1, d2, center)
elif "ndim" in dir(d1):
if d1.ndim == 2:
self.shape = d1.shape
if center:
self.shape = d1.shape
else: #corner
self.shape = tuple(i-1 for i in d1.shape)
elif "ndim" in dir(d2):
if d2.ndim == 2:
self.shape = d2.shape
if center:
self.shape = d2.shape
else: #corner
self.shape = tuple(i-1 for i in d2.shape)

if center:
# avoid += It modifies in place then segfaults
Expand Down Expand Up @@ -736,7 +756,7 @@ def get_pixel_corners(self, correct_binning=False):
if self._pixel_corners is None:
with self._sem:
if self._pixel_corners is None:
r1, r2 = self._calc_pixel_index_from_orientation(True)
r1, r2 = self._calc_pixel_index_from_orientation(False)
# like numpy.ogrid
d1 = expand2d(r1, self.shape[1] + 1, False)
d2 = expand2d(r2, self.shape[0] + 1, True)
Expand Down
40 changes: 11 additions & 29 deletions src/pyFAI/detectors/_dectris.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
__contact__ = "[email protected]"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "23/11/2023"
__date__ = "07/12/2023"
__status__ = "production"

import os
Expand Down Expand Up @@ -130,21 +130,12 @@ def calc_cartesian_positions(self, d1=None, d2=None, center=True, use_cython=Tru
"""
if self.shape:
if (d1 is None) or (d2 is None):
r1, r2 = self._calc_pixel_index_from_orientation(False)
d1 = expand2d(r1, self.shape[1], False)
d2 = expand2d(r2, self.shape[0], True)
r1, r2 = self._calc_pixel_index_from_orientation(center)
delta = 0 if center else 1
d1 = expand2d(r1, self.shape[1] + delta, False)
d2 = expand2d(r2, self.shape[0] + delta, True)
else:
if self.orientation in (0,3):
pass
elif self.orientation == 1:
d1 = self.shape[0] - 1 - d1
d2 = self.shape[1] - 1 - d2
elif self.orientation == 2:
d1 = self.shape[0] - 1 - d1
elif self.orientation == 4:
d2 = self.shape[1] - 1 - d2
else:
raise RuntimeError(f"Unsuported orientation: {self.orientation.name} ({self.orientation.value})")
d1, d2 = self._reorder_indexes_from_orientation(d1, d2, center)

if self.offset1 is None or self.offset2 is None:
delta1 = delta2 = 0.
Expand Down Expand Up @@ -535,21 +526,12 @@ def calc_cartesian_positions(self, d1=None, d2=None, center=True, use_cython=Tru
"""
if self.shape:
if ((d1 is None) or (d2 is None)):
r1, r2 = self._calc_pixel_index_from_orientation(False)
d1 = expand2d(r1, self.shape[1], False)
d2 = expand2d(r2, self.shape[0], True)
r1, r2 = self._calc_pixel_index_from_orientation(center)
delta = 0 if center else 1
d1 = expand2d(r1, self.shape[1] + delta, False)
d2 = expand2d(r2, self.shape[0] + delta, True)
else:
if self.orientation in (0,3):
pass
elif self.orientation==1:
d1 = self.shape[0] - 1 - d1
d2 = self.shape[1] - 1 - d2
elif self.orientation==2:
d1 = self.shape[0] - 1 - d1
elif self.orientation == 4:
d2 = self.shape[1] - 1 - d2
else:
raise RuntimeError(f"Unsuported orientation: {self.orientation.name} ({self.orientation.value})")
d1, d2 = self._reorder_indexes_from_orientation(d1, d2, center)

if (self.offset1 is None) or (self.offset2 is None):
delta1 = delta2 = 0.
Expand Down
17 changes: 10 additions & 7 deletions src/pyFAI/detectors/_imxpad.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
__contact__ = "[email protected]"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "23/11/2023"
__date__ = "07/12/2023"
__status__ = "production"

import functools
Expand Down Expand Up @@ -213,6 +213,7 @@ def calc_cartesian_positions(self, d1=None, d2=None, center=True, use_cython=Tru
p1 = numpy.outer(d1, numpy.ones(self.shape[1]))
p2 = numpy.outer(numpy.ones(self.shape[0]), d2)
else:
d1, d2 = self._reorder_indexes_from_orientation(d1, d2, center)
if center:
# Not +=: do not mangle in place arrays
d1 = d1 + 0.5
Expand Down Expand Up @@ -389,9 +390,10 @@ def calc_cartesian_positions(self, d1=None, d2=None, center=True, use_cython=Tru
"""
if self.shape:
if (d1 is None) or (d2 is None):
r1, r2 = self._calc_pixel_index_from_orientation(False)
d1 = mathutil.expand2d(r1, self.shape[1], False)
d2 = mathutil.expand2d(r2, self.shape[0], True)
r1, r2 = self._calc_pixel_index_from_orientation(center)
delta = 0 if center else 1
d1 = mathutil.expand2d(r1, self.shape[1] + delta, False)
d2 = mathutil.expand2d(r2, self.shape[0] + delta, True)
corners = self.get_pixel_corners()
if center:
# note += would make an increment in place which is bad (segfault !)
Expand Down Expand Up @@ -617,9 +619,10 @@ def get_pixel_corners(self, correct_binning=False):
# TODO !!!
def calc_cartesian_positions(self, d1=None, d2=None, center=True, use_cython=True):
if (d1 is None) or d2 is None:
r1, r2 = self._calc_pixel_index_from_orientation(False)
d1 = mathutil.expand2d(r1, self.MAX_SHAPE[1], False)
d2 = mathutil.expand2d(r2, self.MAX_SHAPE[0], True)
r1, r2 = self._calc_pixel_index_from_orientation(center)
delta = 0 if center else 1
d1 = mathutil.expand2d(r1, self.MAX_SHAPE[1] + delta, False)
d2 = mathutil.expand2d(r2, self.MAX_SHAPE[0] + delta, True)
corners = self.get_pixel_corners()
if center:
# avoid += It modifies in place and segfaults
Expand Down
13 changes: 8 additions & 5 deletions src/pyFAI/detectors/_non_flat.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
__contact__ = "[email protected]"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "23/11/2023"
__date__ = "07/12/2023"
__status__ = "production"


Expand Down Expand Up @@ -178,10 +178,13 @@ def calc_cartesian_positions(self, d1=None, d2=None, center=True, use_cython=Tru
d1 and d2 must have the same shape, returned array will have
the same shape.
"""
if (d1 is None) or d2 is None:
r1, r2 = self._calc_pixel_index_from_orientation(False)
d1 = mathutil.expand2d(r1, self.shape[1], False)
d2 = mathutil.expand2d(r2, self.shape[0], True)
if (d1 is None) or (d2 is None):
r1, r2 = self._calc_pixel_index_from_orientation(center)
delta = 0 if center else 1
d1 = mathutil.expand2d(r1, self.shape[1] + delta, False)
d2 = mathutil.expand2d(r2, self.shape[0] + delta, True)
else:
d1, d2 = self._reorder_indexes_from_orientation(d1, d2, center)
corners = self.get_pixel_corners()
if center:
# avoid += It modifies in place and segfaults
Expand Down
3 changes: 2 additions & 1 deletion src/pyFAI/detectors/_psi.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
__contact__ = "[email protected]"
__license__ = "MIT"
__copyright__ = "2021 European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "21/11/2023"
__date__ = "07/12/2023"
__status__ = "production"

import numpy
Expand Down Expand Up @@ -188,6 +188,7 @@ def calc_cartesian_positions(self, d1=None, d2=None, center=True, use_cython=Tru
p1 = numpy.outer(d1, numpy.ones(self.shape[1]))
p2 = numpy.outer(numpy.ones(self.shape[0]), d2)
else:
d1, d2 = self._reorder_indexes_from_orientation(d1, d2, center)
if center:
# Not +=: do not mangle in place arrays
d1 = d1 + 0.5
Expand Down
Loading

0 comments on commit ab6b31b

Please sign in to comment.