From bac95b76361ee01304543bb43a719c08f1e4d216 Mon Sep 17 00:00:00 2001 From: yoshikisd Date: Thu, 20 Nov 2025 21:42:19 +0000 Subject: [PATCH 1/3] Make it easier to change surface normal in Bragg2DPtycho.from_dataset --- src/cdtools/models/bragg_2d_ptycho.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/cdtools/models/bragg_2d_ptycho.py b/src/cdtools/models/bragg_2d_ptycho.py index bc10ab4d..8560c041 100644 --- a/src/cdtools/models/bragg_2d_ptycho.py +++ b/src/cdtools/models/bragg_2d_ptycho.py @@ -258,6 +258,7 @@ def from_dataset( obj_padding=200, obj_view_crop=None, units='um', + surface_normal=np.array([0., 0., 1.]) ): wavelength = dataset.wavelength det_basis = dataset.detector_geometry['basis'] @@ -278,16 +279,7 @@ def from_dataset( distance, oversampling=oversampling) - # now we grab the sample surface normal - if hasattr(dataset, 'sample_info') and \ - dataset.sample_info is not None and \ - 'orientation' in dataset.sample_info: - surface_normal = dataset.sample_info['orientation'][2] - else: - surface_normal = np.array([0.,0.,1.]) - - # If this information is supplied when the function is called, - # then we override the information in the .cxi file + # Now we define the surface normal if scattering_mode in {'t', 'transmission'}: surface_normal = np.array([0.,0.,1.]) elif scattering_mode in {'r', 'reflection'}: @@ -295,6 +287,13 @@ def from_dataset( outgoing_dir /= np.linalg.norm(outgoing_dir) surface_normal = outgoing_dir + np.array([0.,0.,1.]) surface_normal /= np.linalg.norm(outgoing_dir) + else: + # If the scattering_mode has not been defined, we grab + # this from the cxi file if its present. + if hasattr(dataset, 'sample_info') and \ + dataset.sample_info is not None and \ + 'orientation' in dataset.sample_info: + surface_normal = dataset.sample_info['orientation'][2] # and we use that to generate the probe basis From 7286bad2640fda8d83ed29fedc77937d3c72571c Mon Sep 17 00:00:00 2001 From: yoshikisd Date: Mon, 1 Dec 2025 22:20:28 +0000 Subject: [PATCH 2/3] Added heirarchy of surface_normal definition in Bragg2DPtycho from_dataset --- src/cdtools/models/bragg_2d_ptycho.py | 39 ++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/cdtools/models/bragg_2d_ptycho.py b/src/cdtools/models/bragg_2d_ptycho.py index 8560c041..3a981ca7 100644 --- a/src/cdtools/models/bragg_2d_ptycho.py +++ b/src/cdtools/models/bragg_2d_ptycho.py @@ -258,7 +258,7 @@ def from_dataset( obj_padding=200, obj_view_crop=None, units='um', - surface_normal=np.array([0., 0., 1.]) + surface_normal=None ): wavelength = dataset.wavelength det_basis = dataset.detector_geometry['basis'] @@ -280,20 +280,35 @@ def from_dataset( oversampling=oversampling) # Now we define the surface normal - if scattering_mode in {'t', 'transmission'}: - surface_normal = np.array([0.,0.,1.]) - elif scattering_mode in {'r', 'reflection'}: - outgoing_dir = np.cross(det_basis[:,0], det_basis[:,1]) - outgoing_dir /= np.linalg.norm(outgoing_dir) - surface_normal = outgoing_dir + np.array([0.,0.,1.]) - surface_normal /= np.linalg.norm(outgoing_dir) - else: - # If the scattering_mode has not been defined, we grab - # this from the cxi file if its present. - if hasattr(dataset, 'sample_info') and \ + # The surface normal definition is based on the following heirarchy: + # manual surface_normal definition > scattering_mode + # > dataset.sample_info['orientation'] > transmission geometry + + # Guard against any surface_normal entries that are not numpy vectors + if surface_normal is not None and \ + not isinstance(surface_normal, np.ndarray) or \ + (isinstance(surface_normal, np.ndarray) + and not surface_normal.shape == (3,)): + raise RuntimeError( + 'surface_normal needs to be a numpy vector with 3 elements.' + ) + + if surface_normal is None: + if scattering_mode in {'t', 'transmission'}: + surface_normal = np.array([0.,0.,1.]) + elif scattering_mode in {'r', 'reflection'}: + outgoing_dir = np.cross(det_basis[:,0], det_basis[:,1]) + outgoing_dir /= np.linalg.norm(outgoing_dir) + surface_normal = outgoing_dir + np.array([0.,0.,1.]) + surface_normal /= np.linalg.norm(outgoing_dir) + elif hasattr(dataset, 'sample_info') and \ dataset.sample_info is not None and \ 'orientation' in dataset.sample_info: + # If the scattering_mode has not been defined, we grab + # this from the cxi file if its present. surface_normal = dataset.sample_info['orientation'][2] + else: + surface_normal = np.array([0., 0., 1.]) # and we use that to generate the probe basis From 8f3ad59c6dca1edf5f70ed0e181f54e7636babea Mon Sep 17 00:00:00 2001 From: Abe Levitan Date: Tue, 2 Dec 2025 15:27:20 +0100 Subject: [PATCH 3/3] Suggested changes: Make surface_normal work with anything castable to an array with np.asarray, change the check for length-3 to cover all cases, and add a check to explicitly fail if the scattering_mode argument is improperly set, instead of silently falling back to default --- src/cdtools/models/bragg_2d_ptycho.py | 51 ++++++++++++++------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/cdtools/models/bragg_2d_ptycho.py b/src/cdtools/models/bragg_2d_ptycho.py index 3a981ca7..b3869a9c 100644 --- a/src/cdtools/models/bragg_2d_ptycho.py +++ b/src/cdtools/models/bragg_2d_ptycho.py @@ -283,33 +283,34 @@ def from_dataset( # The surface normal definition is based on the following heirarchy: # manual surface_normal definition > scattering_mode # > dataset.sample_info['orientation'] > transmission geometry - - # Guard against any surface_normal entries that are not numpy vectors - if surface_normal is not None and \ - not isinstance(surface_normal, np.ndarray) or \ - (isinstance(surface_normal, np.ndarray) - and not surface_normal.shape == (3,)): - raise RuntimeError( - 'surface_normal needs to be a numpy vector with 3 elements.' + if surface_normal is not None: + surface_normal = np.asarray(surface_normal) + elif scattering_mode.strip().lower() in {'t', 'transmission'}: + surface_normal = np.array([0.,0.,1.]) + elif scattering_mode.strip().lower() in {'r', 'reflection'}: + outgoing_dir = np.cross(det_basis[:,0], det_basis[:,1]) + outgoing_dir /= np.linalg.norm(outgoing_dir) + surface_normal = outgoing_dir + np.array([0.,0.,1.]) + surface_normal /= np.linalg.norm(outgoing_dir) + elif scattering_mode is not None: + raise ValueError( + 'Scattering mode must be either "transmission" ("t"), "reflection" ("r"), or the default of None.' ) + elif hasattr(dataset, 'sample_info') and \ + dataset.sample_info is not None and \ + 'orientation' in dataset.sample_info: + # If the scattering_mode has not been defined, we grab + # this from the cxi file if its present. + surface_normal = dataset.sample_info['orientation'][2] + else: + surface_normal = np.array([0., 0., 1.]) - if surface_normal is None: - if scattering_mode in {'t', 'transmission'}: - surface_normal = np.array([0.,0.,1.]) - elif scattering_mode in {'r', 'reflection'}: - outgoing_dir = np.cross(det_basis[:,0], det_basis[:,1]) - outgoing_dir /= np.linalg.norm(outgoing_dir) - surface_normal = outgoing_dir + np.array([0.,0.,1.]) - surface_normal /= np.linalg.norm(outgoing_dir) - elif hasattr(dataset, 'sample_info') and \ - dataset.sample_info is not None and \ - 'orientation' in dataset.sample_info: - # If the scattering_mode has not been defined, we grab - # this from the cxi file if its present. - surface_normal = dataset.sample_info['orientation'][2] - else: - surface_normal = np.array([0., 0., 1.]) - + # Guard against any surface_normal entries that are not castable + # to a length-3 numpy vector, with a sensible error message + if not surface_normal.shape == (3,): + raise ValueError( + '`surface_normal` needs to be a numpy vector with 3 elements. If it was set incorrectly from dataset.sample_info, consider explicitly setting it via the `surface_normal` keyword argument.' + ) # and we use that to generate the probe basis ew_normal = np.cross(np.array(ew_basis)[:,1],