From f0f44e3c836ce4f7f9067cd706321e924a75a337 Mon Sep 17 00:00:00 2001 From: gnzng Date: Wed, 12 Nov 2025 11:54:17 -0600 Subject: [PATCH 1/8] fix: documentation build step to use Sphinx directly --- .github/workflows/main.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a24af2b..18dc696 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -94,9 +94,8 @@ jobs: uv pip install ."[docs]" - name: Build docs - working-directory: docs run: | - make html + uv run python -m sphinx -b html docs/source docs/build/html - name: List files in docs/build/html run: | From 4d18171e5930d80c6fa374675f41a4680b1410b5 Mon Sep 17 00:00:00 2001 From: gnzng Date: Sun, 16 Nov 2025 11:00:33 -0600 Subject: [PATCH 2/8] docs: add instructions for building custom documentation --- README.md | 2 ++ docs/build_custom_docs.md | 52 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 docs/build_custom_docs.md diff --git a/README.md b/README.md index 742b472..8381ff1 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ plt.show() Further documentation is found [here](https://cdtools-developers.github.io/cdtools/). +Instructions for building custom documentation based on a specific version or commit can be found [here](https://github.com/cdtools-developers/cdtools/blob/master/docs/build_custom_docs.md). + # Installation CDTools can be installed in several ways depending on your needs. For most users, installation from pypi is recommended. For developers or those who want the latest features, installation from source is available. diff --git a/docs/build_custom_docs.md b/docs/build_custom_docs.md new file mode 100644 index 0000000..51d39e2 --- /dev/null +++ b/docs/build_custom_docs.md @@ -0,0 +1,52 @@ +# Build custom docs + +This is a short description how to build custom documentation with `sphinx` based on the exact version (or even commit) you are using. + +This requires a cloned version of `cdtools` from GitHub. See the installation guide for more information: + +https://cdtools-developers.github.io/cdtools/installation.html#option-2-installation-from-source + + +## Installation of Dependencies + +First, ensure you have all necessary dependencies installed. You can do this using [`uv`](https://github.com/astral-sh/uv): + +```sh +uv pip install ."[docs]" +``` + +This will install your project along with the extra dependencies required for building the documentation. + + +## Checkout the version or commit + +To ensure your documentation matches a specific version or commit of your codebase, use `git` to checkout the desired state. For example, to checkout a specific tag or commit: + +```sh +git checkout +``` + +Replace `` with the version tag (e.g., `v1.2.3`) or the commit hash you want to use. This ensures the documentation is built for the exact code you are working with. + +## Building the Documentation + +To build the HTML documentation, run the following command from the root of your project: + +```sh +uv run python -m sphinx -b html docs/source docs/_build/html/ +``` + +This command tells Sphinx to build the documentation located in the `docs/source` directory and output the HTML files to `docs/_build/html/`. + +You can then open the generated HTML files in your browser to view the documentation. + + +## Get back to the latest version of cdtools + +To return to the latest version of your code, use: + +```sh +git checkout master +``` + +This will switch your working directory back to the latest development branch. \ No newline at end of file From 444b56b829cae8226d994c6c0303969d85a3766a Mon Sep 17 00:00:00 2001 From: gnzng Date: Sun, 16 Nov 2025 11:21:36 -0600 Subject: [PATCH 3/8] docs: update installation instructions for dependencies to include pip --- docs/build_custom_docs.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/build_custom_docs.md b/docs/build_custom_docs.md index 51d39e2..f9f6f6f 100644 --- a/docs/build_custom_docs.md +++ b/docs/build_custom_docs.md @@ -9,14 +9,19 @@ https://cdtools-developers.github.io/cdtools/installation.html#option-2-installa ## Installation of Dependencies -First, ensure you have all necessary dependencies installed. You can do this using [`uv`](https://github.com/astral-sh/uv): +First, ensure you have all necessary dependencies installed. You can do this using [`uv`](https://github.com/astral-sh/uv) or `pip`: + ```sh uv pip install ."[docs]" +# or, if you prefer pip: +pip install ."[docs]" ``` This will install your project along with the extra dependencies required for building the documentation. +**Note:** `uv` is a fast Python package installer and resolver, serving as a drop-in replacement for `pip` with improved performance. You can use either `pip` or `uv` as shown above. + ## Checkout the version or commit From c78b52893030ee03394fbff98b76921d2e62dab5 Mon Sep 17 00:00:00 2001 From: Abe Levitan Date: Wed, 19 Nov 2025 10:46:01 +0100 Subject: [PATCH 4/8] Change how the dataset mask is calculated, so that it only raises a divide by zero error if unmasked pixels have an issue. Add an option to ptycho_2d_dataset to plot the mask --- src/cdtools/datasets/ptycho_2d_dataset.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/cdtools/datasets/ptycho_2d_dataset.py b/src/cdtools/datasets/ptycho_2d_dataset.py index 3825d6d..adfe866 100644 --- a/src/cdtools/datasets/ptycho_2d_dataset.py +++ b/src/cdtools/datasets/ptycho_2d_dataset.py @@ -220,7 +220,8 @@ class Ptycho2DDataset(CDataset): logarithmic=True, units='um', log_offset=1, - plot_mean_pattern=True + plot_mean_pattern=True, + plot_mask=False, ): """Launches an interactive plot for perusing the data @@ -241,7 +242,7 @@ class Ptycho2DDataset(CDataset): mask = 1 if logarithmic: - return np.log(meas_data + log_offset) / np.log(10) * mask + return np.log10((meas_data * mask) + log_offset) else: return meas_data * mask @@ -268,6 +269,9 @@ class Ptycho2DDataset(CDataset): if plot_mean_pattern: self.plot_mean_pattern(log_offset=log_offset) + + if plot_mask: + plotting.plot_real(self.mask, title='Dataset Mask') return plotting.plot_nanomap_with_images(self.translations.detach().cpu(), get_images, values=nanomap_values, nanomap_units=units, image_title='Diffraction Pattern', image_colorbar_title=cbar_title) From bac95b76361ee01304543bb43a719c08f1e4d216 Mon Sep 17 00:00:00 2001 From: yoshikisd Date: Thu, 20 Nov 2025 21:42:19 +0000 Subject: [PATCH 5/8] 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 bc10ab4..8560c04 100644 --- a/src/cdtools/models/bragg_2d_ptycho.py +++ b/src/cdtools/models/bragg_2d_ptycho.py @@ -258,6 +258,7 @@ class Bragg2DPtycho(CDIModel): 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 @@ class Bragg2DPtycho(CDIModel): 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 @@ class Bragg2DPtycho(CDIModel): 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 6/8] 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 8560c04..3a981ca 100644 --- a/src/cdtools/models/bragg_2d_ptycho.py +++ b/src/cdtools/models/bragg_2d_ptycho.py @@ -258,7 +258,7 @@ class Bragg2DPtycho(CDIModel): 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 @@ class Bragg2DPtycho(CDIModel): 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 7/8] 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 3a981ca..b3869a9 100644 --- a/src/cdtools/models/bragg_2d_ptycho.py +++ b/src/cdtools/models/bragg_2d_ptycho.py @@ -283,33 +283,34 @@ class Bragg2DPtycho(CDIModel): # 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], From 69a94f0fe8b6951648e6b909a22733dfd9cac631 Mon Sep 17 00:00:00 2001 From: yoshikisd Date: Thu, 18 Dec 2025 18:15:19 +0000 Subject: [PATCH 8/8] Bragg2DPtycho scattering_mode string values only inspected if scattering_mode is a string --- src/cdtools/models/bragg_2d_ptycho.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/cdtools/models/bragg_2d_ptycho.py b/src/cdtools/models/bragg_2d_ptycho.py index b3869a9..507323d 100644 --- a/src/cdtools/models/bragg_2d_ptycho.py +++ b/src/cdtools/models/bragg_2d_ptycho.py @@ -285,13 +285,14 @@ class Bragg2DPtycho(CDIModel): # > dataset.sample_info['orientation'] > transmission geometry 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 isinstance(scattering_mode, str): + if 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.'