diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 00000000..16645e0c --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,101 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: lint, style, and tests + +on: + pull_request: + branches: + - main + +jobs: + style: + name: Style Check + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ["3.10"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install black==25.1.0 + - name: Check code styling with Black + run: | + black --diff -S -t py310 waveorder + black --check -S -t py310 waveorder + + # lint: + # name: Lint Check + # runs-on: ubuntu-latest + + # strategy: + # matrix: + # python-version: ["3.10"] + + # steps: + # - uses: actions/checkout@v3 + # - name: Set up Python + # uses: actions/setup-python@v4 + # with: + # python-version: ${{ matrix.python-version }} + # - name: Install dependencies + # run: | + # python -m pip install --upgrade pip + # pip install flake8 + # - name: Check code with Flake8 + # # E203 conflicts with black + # run: | + # flake8 waveorder --extend-ignore=E203 + + isort: + name: isort Check + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ["3.10"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install isort + - name: Check code with isort + run: | + isort --check waveorder + + tests: + needs: [style, isort] # lint + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ".[dev]" + + - name: Test with pytest + run: | + pytest -v --cov=./ --cov-report=xml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..29907895 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ + +repos: +# basic pre-commit + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-added-large-files + - id: check-yaml + - id: check-toml + - id: detect-private-key +# sorting imports + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort +# syntax linting and formatting + - repo: https://github.com/myint/autoflake + rev: v2.1.1 + hooks: + - id: autoflake + args: [--in-place, --remove-all-unused-imports, + --ignore-init-module-imports] + # - repo: https://github.com/PyCQA/flake8 + # rev: 6.0.0 + # hooks: + # - id: flake8 + # args: [--ignore, "E203,W503", --min-python-version, '3.10'] + # additional_dependencies: [flake8-typing-imports==1.12.0] + - repo: https://github.com/psf/black + rev: 25.1.0 + hooks: + - id: black diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..44b51673 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,62 @@ +# Contributing guide + +Thanks for your interest in contributing to `waveorder`! + +Please see the following steps for our workflow. + +## Getting started + +Please read the [README](./README.md) for an overview of the project, +and how you can install and use the package. + +## Issues + +We use [issues](https://github.com/mehta-lab/waveorder/issues) to track +bug reports, feature requests, and provide user support. + +Before opening a new issue, please first search existing issues (including closed ones), +to see if there is an existing discussion about it. + +### Setting up development environment + +For local development, first install [Git](https://git-scm.com/) +and Python with an environment management tool +(e.g. [miniforge](https://github.com/conda-forge/miniforge), a minimal community distribution of Conda). + +If you use Conda, set up an environment with: + +```sh +conda create -n waveorder-dev python=3.10 +conda activate waveorder-dev +``` + +If you have push permission to the repository, +clone the repository (the code blocks below are shell commands): + +```sh +cd # to the directory you want to work in +git clone https://github.com/mehta-lab/waveorder.git +``` + +Otherwise, you can follow [these instructions](https://docs.github.com/en/get-started/quickstart/fork-a-repo) +to [fork](https://github.com/mehta-lab/waveorder/fork) the repository. + +Then install the package in editable mode with the development dependencies: + +```sh +cd waveorder/ # or the renamed project root directory +pip install -e ".[dev]" +``` + +Then make the changes and [track them with Git](https://docs.github.com/en/get-started/using-git/about-git#example-contribute-to-an-existing-repository). + + +### Code style + +We use [pre-commit](https://pre-commit.com/) to sort imports with [isort](https://github.com/PyCQA/isort) and format code with [black](https://black.readthedocs.io/en/stable/) automatically prior to each commit. To minimize test errors when submitting pull requests, please install pre-commit in your environment as follows: + +```bash +pre-commit install +``` + +When these packages are executed within the project root directory, they should automatically use the [project settings](./pyproject.toml). diff --git a/examples/documentation/PTI_experiment/PTI_Experiment_Recon3D_anisotropic_target_small.py b/examples/documentation/PTI_experiment/PTI_Experiment_Recon3D_anisotropic_target_small.py index e259dc96..fa3b76dd 100644 --- a/examples/documentation/PTI_experiment/PTI_Experiment_Recon3D_anisotropic_target_small.py +++ b/examples/documentation/PTI_experiment/PTI_Experiment_Recon3D_anisotropic_target_small.py @@ -1,16 +1,16 @@ # %% # This notebook-style script requires a ~500 MB download from https://www.ebi.ac.uk/biostudies/files/S-BIAD1063/PTI-BIA/Anisotropic_target_small.zip -import numpy as np +from pathlib import Path + import matplotlib.pyplot as plt +import numpy as np +import zarr +from iohub import open_ome_zarr from numpy.fft import fftshift import waveorder as wo -from waveorder import optics, waveorder_reconstructor, util - -import zarr -from pathlib import Path -from iohub import open_ome_zarr +from waveorder import optics, util, waveorder_reconstructor from waveorder.visuals import jupyter_visuals # %% @@ -62,10 +62,12 @@ I_cali_mean = np.array(PTI_file.I_cali_mean) # source polarization, instrument matrix calibration -E_in, A_matrix, I_cali_mean = ( - wo.waveorder_reconstructor.instrument_matrix_and_source_calibration( - I_cali_mean, handedness="RCP" - ) +( + E_in, + A_matrix, + I_cali_mean, +) = wo.waveorder_reconstructor.instrument_matrix_and_source_calibration( + I_cali_mean, handedness="RCP" ) # %% @@ -238,15 +240,18 @@ # "negative" -> only solution of negatively uniaxial material # "unknown" -> both solutions of positively and negatively uniaxial material + optic sign estimation -differential_permittivity, azimuth, theta, mat_map = ( - setup.scattering_potential_tensor_to_3D_orientation( - f_tensor, - S_image_tm, - material_type="unknown", - reg_ret_pr=reg_differential_permittivity, - itr=10, - fast_gpu_mode=True, - ) +( + differential_permittivity, + azimuth, + theta, + mat_map, +) = setup.scattering_potential_tensor_to_3D_orientation( + f_tensor, + S_image_tm, + material_type="unknown", + reg_ret_pr=reg_differential_permittivity, + itr=10, + fast_gpu_mode=True, ) # %% @@ -264,7 +269,10 @@ [ ((-1) ** i) * util.wavelet_softThreshold( - ((-1) ** i) * differential_permittivity_PT[i], "db8", 0.00303, level=1 + ((-1) ** i) * differential_permittivity_PT[i], + "db8", + 0.00303, + level=1, ) for i in range(2) ] @@ -374,17 +382,27 @@ # compute the physical properties from the scattering potential tensor -differential_permittivity_p, azimuth_p, theta_p = ( - optics.scattering_potential_tensor_to_3D_orientation_PN( - f_tensor, material_type="positive", reg_ret_pr=reg_differential_permittivity - ) -) -differential_permittivity_n, azimuth_n, theta_n = ( - optics.scattering_potential_tensor_to_3D_orientation_PN( - f_tensor, material_type="negative", reg_ret_pr=reg_differential_permittivity - ) +( + differential_permittivity_p, + azimuth_p, + theta_p, +) = optics.scattering_potential_tensor_to_3D_orientation_PN( + f_tensor, + material_type="positive", + reg_ret_pr=reg_differential_permittivity, +) +( + differential_permittivity_n, + azimuth_n, + theta_n, +) = optics.scattering_potential_tensor_to_3D_orientation_PN( + f_tensor, + material_type="negative", + reg_ret_pr=reg_differential_permittivity, +) +differential_permittivity = np.array( + [differential_permittivity_p, differential_permittivity_n] ) -differential_permittivity = np.array([differential_permittivity_p, differential_permittivity_n]) azimuth = np.array([azimuth_p, azimuth_n]) theta = np.array([theta_p, theta_n]) @@ -402,7 +420,10 @@ [ ((-1) ** i) * util.wavelet_softThreshold( - ((-1) ** i) * differential_permittivity_PT[i], "db8", 0.00303, level=1 + ((-1) ** i) * differential_permittivity_PT[i], + "db8", + 0.00303, + level=1, ) for i in range(2) ] diff --git a/examples/maintenance/PTI_simulation/PTI_Simulation_Forward_2D3D.py b/examples/maintenance/PTI_simulation/PTI_Simulation_Forward_2D3D.py index 14309272..85d05f89 100644 --- a/examples/maintenance/PTI_simulation/PTI_Simulation_Forward_2D3D.py +++ b/examples/maintenance/PTI_simulation/PTI_Simulation_Forward_2D3D.py @@ -9,14 +9,11 @@ # density and anisotropy," bioRxiv 2020.12.15.422951 (2020).``` # #################################################################### -import numpy as np import matplotlib.pyplot as plt +import numpy as np from numpy.fft import fftshift -from waveorder import ( - optics, - waveorder_simulator, - util, -) + +from waveorder import optics, util, waveorder_simulator from waveorder.visuals import jupyter_visuals ##################################################################### diff --git a/examples/maintenance/PTI_simulation/PTI_Simulation_Recon2D.py b/examples/maintenance/PTI_simulation/PTI_Simulation_Recon2D.py index 0042a1a5..646cbb2b 100644 --- a/examples/maintenance/PTI_simulation/PTI_Simulation_Recon2D.py +++ b/examples/maintenance/PTI_simulation/PTI_Simulation_Recon2D.py @@ -9,14 +9,11 @@ # density and anisotropy," bioRxiv 2020.12.15.422951 (2020).``` # #################################################################### -import numpy as np import matplotlib.pyplot as plt +import numpy as np from numpy.fft import fftshift -from waveorder import ( - optics, - waveorder_reconstructor, -) +from waveorder import optics, waveorder_reconstructor from waveorder.visuals import jupyter_visuals ## Initialization @@ -271,7 +268,6 @@ # in-plane orientation from matplotlib.colors import hsv_to_rgb - ret_min_color = 0 ret_max_color = 1.5 diff --git a/examples/maintenance/PTI_simulation/PTI_Simulation_Recon3D.py b/examples/maintenance/PTI_simulation/PTI_Simulation_Recon3D.py index 78f0c54a..115458e0 100644 --- a/examples/maintenance/PTI_simulation/PTI_Simulation_Recon3D.py +++ b/examples/maintenance/PTI_simulation/PTI_Simulation_Recon3D.py @@ -8,13 +8,11 @@ # "uPTI: uniaxial permittivity tensor imaging of intrinsic # # density and anisotropy," bioRxiv 2020.12.15.422951 (2020).``` # #################################################################### -import numpy as np import matplotlib.pyplot as plt +import numpy as np from numpy.fft import fftshift -from waveorder import ( - optics, - waveorder_reconstructor, -) + +from waveorder import optics, waveorder_reconstructor from waveorder.visuals import jupyter_visuals ## Initialization diff --git a/examples/maintenance/QLIPP_simulation/2D_QLIPP_forward.py b/examples/maintenance/QLIPP_simulation/2D_QLIPP_forward.py index 3a7a5d39..fd7b2c41 100644 --- a/examples/maintenance/QLIPP_simulation/2D_QLIPP_forward.py +++ b/examples/maintenance/QLIPP_simulation/2D_QLIPP_forward.py @@ -10,14 +10,11 @@ ##################################################################################################### -import numpy as np import matplotlib.pyplot as plt +import numpy as np from numpy.fft import fftshift -from waveorder import ( - optics, - waveorder_simulator, - util, -) + +from waveorder import optics, util, waveorder_simulator from waveorder.visuals import jupyter_visuals # Key parameters diff --git a/examples/maintenance/QLIPP_simulation/2D_QLIPP_recon.py b/examples/maintenance/QLIPP_simulation/2D_QLIPP_recon.py index 742fb498..f8a8583a 100644 --- a/examples/maintenance/QLIPP_simulation/2D_QLIPP_recon.py +++ b/examples/maintenance/QLIPP_simulation/2D_QLIPP_recon.py @@ -10,13 +10,11 @@ # eLife 9:e55502 (2020).``` # ##################################################################################################### -import numpy as np import matplotlib.pyplot as plt -from waveorder import ( - waveorder_reconstructor, -) -from waveorder.visuals import jupyter_visuals +import numpy as np +from waveorder import waveorder_reconstructor +from waveorder.visuals import jupyter_visuals # ### Load simulated data # Load simulations diff --git a/examples/models/inplane_oriented_thick_pol3d_vector.py b/examples/models/inplane_oriented_thick_pol3d_vector.py index b082a983..a85f5e4f 100644 --- a/examples/models/inplane_oriented_thick_pol3d_vector.py +++ b/examples/models/inplane_oriented_thick_pol3d_vector.py @@ -1,9 +1,7 @@ -import torch import napari +import torch -from waveorder.models import ( - inplane_oriented_thick_pol3d_vector, -) +from waveorder.models import inplane_oriented_thick_pol3d_vector # Parameters # all lengths must use consistent units e.g. um @@ -25,20 +23,22 @@ ) # Calculate transfer function -sfZYX_transfer_function, intensity_to_stokes_matrix, singular_system = ( - inplane_oriented_thick_pol3d_vector.calculate_transfer_function( - swing, - scheme, - zyx_shape, - yx_pixel_size, - z_pixel_size, - wavelength_illumination, - z_padding, - index_of_refraction_media, - numerical_aperture_illumination, - numerical_aperture_detection, - fourier_oversample_factor=fourier_oversample_factor, - ) +( + sfZYX_transfer_function, + intensity_to_stokes_matrix, + singular_system, +) = inplane_oriented_thick_pol3d_vector.calculate_transfer_function( + swing, + scheme, + zyx_shape, + yx_pixel_size, + z_pixel_size, + wavelength_illumination, + z_padding, + index_of_refraction_media, + numerical_aperture_illumination, + numerical_aperture_detection, + fourier_oversample_factor=fourier_oversample_factor, ) # Display transfer function diff --git a/examples/models/isotropic_thin_3d.py b/examples/models/isotropic_thin_3d.py index 05b9c0f2..4d9701cb 100644 --- a/examples/models/isotropic_thin_3d.py +++ b/examples/models/isotropic_thin_3d.py @@ -5,7 +5,7 @@ import napari import numpy as np -from waveorder import util + from waveorder.models import isotropic_thin_3d # Parameters diff --git a/examples/models/phase_thick_3d.py b/examples/models/phase_thick_3d.py index 54d7e43d..bfbe6d30 100644 --- a/examples/models/phase_thick_3d.py +++ b/examples/models/phase_thick_3d.py @@ -5,7 +5,7 @@ import napari import numpy as np -from waveorder import util + from waveorder.models import phase_thick_3d # Parameters diff --git a/examples/visuals/plot_greens_tensor.py b/examples/visuals/plot_greens_tensor.py index 138d30f1..20175eca 100644 --- a/examples/visuals/plot_greens_tensor.py +++ b/examples/visuals/plot_greens_tensor.py @@ -1,17 +1,19 @@ -from skimage import measure +import os + import napari -from napari.experimental import link_layers import numpy as np import torch -import os -from waveorder import util, optics +from napari.experimental import link_layers from scipy.ndimage import gaussian_filter +from skimage import measure + +from waveorder import optics, util # Parameters # all lengths must use consistent units e.g. um output_dirpath = "./greens_plots" os.makedirs(output_dirpath, exist_ok=True) -grid_size = 100 # 300 for publication +grid_size = 100 # 300 for publication blur_width = grid_size // 35 # blurring to smooth sharp corners zyx_shape = 3 * (grid_size,) yx_pixel_size = 6.5 / 63 diff --git a/examples/visuals/plot_vector_transfer_function_support.py b/examples/visuals/plot_vector_transfer_function_support.py index 6bf9094a..7442cc2f 100644 --- a/examples/visuals/plot_vector_transfer_function_support.py +++ b/examples/visuals/plot_vector_transfer_function_support.py @@ -1,7 +1,7 @@ +import os + import napari import numpy as np -import os -import matplotlib.pyplot as plt def plot_otf_support( @@ -28,8 +28,14 @@ def plot_otf_support( points = np.array( [ [0, 0], - [det_na - ill_na, (1 - ill_na**2) ** 0.5 - (1 - det_na**2) ** 0.5], - [det_na + ill_na, (1 - ill_na**2) ** 0.5 - (1 - det_na**2) ** 0.5], + [ + det_na - ill_na, + (1 - ill_na**2) ** 0.5 - (1 - det_na**2) ** 0.5, + ], + [ + det_na + ill_na, + (1 - ill_na**2) ** 0.5 - (1 - det_na**2) ** 0.5, + ], [2 * ill_na, 0], ] ) @@ -203,7 +209,6 @@ def plot_otf_support( ] for my_color in my_colors: - plot_otf_support( ill_na, det_na, diff --git a/pyproject.toml b/pyproject.toml index c9fb2466..a9f2c685 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ dependencies = [ dynamic = ["version"] [project.optional-dependencies] -dev = ["pytest", "pytest-cov"] +dev = ["pytest", "pytest-cov", "black==25.1.0"] examples = ["napari[all]", "jupyter"] [project.urls] diff --git a/tests/models/test_isotropic_fluorescent_thick_3d.py b/tests/models/test_isotropic_fluorescent_thick_3d.py index a6c5a11c..15e1fe97 100644 --- a/tests/models/test_isotropic_fluorescent_thick_3d.py +++ b/tests/models/test_isotropic_fluorescent_thick_3d.py @@ -1,4 +1,3 @@ -import pytest import torch from waveorder.models import isotropic_fluorescent_thick_3d diff --git a/tests/models/test_phase_thick_3d.py b/tests/models/test_phase_thick_3d.py index d60c7fa6..ffed7211 100644 --- a/tests/models/test_phase_thick_3d.py +++ b/tests/models/test_phase_thick_3d.py @@ -1,5 +1,6 @@ -import pytest import numpy as np +import pytest + from waveorder.models import phase_thick_3d @@ -27,7 +28,6 @@ def simulate_phase_recon( z_pixel_size_um=0.1, yx_pixel_size_um=6.5 / 63, ): - z_fov_um = 50 yx_fov_um = 50 @@ -98,4 +98,4 @@ def test_phase_invariance(): # test yx pixel size invariance recon2 = simulate_phase_recon(yx_pixel_size_um=0.7 * 6.5 / 63) - assert np.abs((recon2 - recon) / recon) < 0.02 \ No newline at end of file + assert np.abs((recon2 - recon) / recon) < 0.02 diff --git a/tests/test_examples.py b/tests/test_examples.py index f7d19ae8..3aeb868a 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -1,8 +1,9 @@ -import subprocess import os -import pytest +import subprocess import sys +import pytest + def _run_scripts(scripts): for script in scripts: diff --git a/tests/test_focus_estimator.py b/tests/test_focus_estimator.py index c3cca07b..5e4442df 100644 --- a/tests/test_focus_estimator.py +++ b/tests/test_focus_estimator.py @@ -1,5 +1,6 @@ -import pytest import numpy as np +import pytest + from waveorder import focus diff --git a/tests/test_optics.py b/tests/test_optics.py index 4e821776..d51b5bef 100644 --- a/tests/test_optics.py +++ b/tests/test_optics.py @@ -1,6 +1,7 @@ -from waveorder import optics, util import torch +from waveorder import optics, util + def test_generate_pupil(): radial_frequencies = util.generate_radial_frequencies((10, 10), 0.5) diff --git a/tests/test_sampling.py b/tests/test_sampling.py index 4dc65969..13a26ff5 100644 --- a/tests/test_sampling.py +++ b/tests/test_sampling.py @@ -1,9 +1,6 @@ import torch -import numpy as np -from waveorder.sampling import ( - nd_fourier_central_cuboid, -) +from waveorder.sampling import nd_fourier_central_cuboid def test_nd_fourier_central_cuboid(): diff --git a/tests/test_util.py b/tests/test_util.py index c9db2470..e1f6b840 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,6 +1,7 @@ -from waveorder import util -import torch import pytest +import torch + +from waveorder import util def test_gen_coordinate(): diff --git a/waveorder/background_estimator.py b/waveorder/background_estimator.py index 56595d6e..fe00cbc0 100644 --- a/waveorder/background_estimator.py +++ b/waveorder/background_estimator.py @@ -1,12 +1,12 @@ """Estimate flat field images""" -import numpy as np import itertools +import numpy as np """ -This script is adopted from +This script is adopted from https://github.com/mehta-lab/reconstruct-order diff --git a/waveorder/correction.py b/waveorder/correction.py index 09a74a88..329800d7 100644 --- a/waveorder/correction.py +++ b/waveorder/correction.py @@ -2,7 +2,7 @@ import torch import torch.nn.functional as F -from torch import Tensor, Size +from torch import Size, Tensor def _sample_block_medians(image: Tensor, block_size) -> Tensor: diff --git a/waveorder/focus.py b/waveorder/focus.py index 709be235..5d470b57 100644 --- a/waveorder/focus.py +++ b/waveorder/focus.py @@ -1,9 +1,11 @@ -from scipy.signal import peak_widths +import warnings from typing import Literal, Optional -from waveorder import util + import matplotlib.pyplot as plt import numpy as np -import warnings +from scipy.signal import peak_widths + +from waveorder import util def focus_from_transverse_band( diff --git a/waveorder/models/inplane_oriented_thick_pol3d.py b/waveorder/models/inplane_oriented_thick_pol3d.py index fd0ac4df..52ccb9dd 100644 --- a/waveorder/models/inplane_oriented_thick_pol3d.py +++ b/waveorder/models/inplane_oriented_thick_pol3d.py @@ -7,7 +7,9 @@ from waveorder import correction, stokes, util -def generate_test_phantom(yx_shape: Tuple[int, int]) -> Tuple[Tensor, Tensor, Tensor, Tensor]: +def generate_test_phantom( + yx_shape: Tuple[int, int], +) -> Tuple[Tensor, Tensor, Tensor, Tensor]: star, theta, _ = util.generate_star_target(yx_shape, blur_px=0.1) retardance = 0.25 * star orientation = (theta % np.pi) * (star > 1e-3) @@ -23,7 +25,9 @@ def calculate_transfer_function( return stokes.calculate_intensity_to_stokes_matrix(swing, scheme=scheme) -def visualize_transfer_function(viewer, intensity_to_stokes_matrix: Tensor) -> None: +def visualize_transfer_function( + viewer, intensity_to_stokes_matrix: Tensor +) -> None: viewer.add_image( intensity_to_stokes_matrix.cpu().numpy(), name="Intensity to stokes matrix", diff --git a/waveorder/models/inplane_oriented_thick_pol3d_vector.py b/waveorder/models/inplane_oriented_thick_pol3d_vector.py index 1d8e5c60..68e193cc 100644 --- a/waveorder/models/inplane_oriented_thick_pol3d_vector.py +++ b/waveorder/models/inplane_oriented_thick_pol3d_vector.py @@ -1,12 +1,13 @@ -import torch -import numpy as np +from typing import Literal +import numpy as np +import torch from torch import Tensor -from typing import Literal -from torch.nn.functional import avg_pool3d, interpolate +from torch.nn.functional import avg_pool3d + from waveorder import optics, sampling, stokes, util -from waveorder.visuals.napari_visuals import add_transfer_function_to_viewer from waveorder.filter import apply_filter_bank +from waveorder.visuals.napari_visuals import add_transfer_function_to_viewer def generate_test_phantom(zyx_shape: tuple[int, int, int]) -> torch.Tensor: @@ -69,20 +70,21 @@ def calculate_transfer_function( int(np.ceil(zyx_shape[2] * yx_factor * fourier_oversample_factor)), ) - sfZYX_transfer_function, intensity_to_stokes_matrix = ( - _calculate_wrap_unsafe_transfer_function( - swing, - scheme, - tf_calculation_shape, - yx_pixel_size / yx_factor, - z_pixel_size / z_factor, - wavelength_illumination, - z_padding, - index_of_refraction_media, - numerical_aperture_illumination, - numerical_aperture_detection, - invert_phase_contrast=invert_phase_contrast, - ) + ( + sfZYX_transfer_function, + intensity_to_stokes_matrix, + ) = _calculate_wrap_unsafe_transfer_function( + swing, + scheme, + tf_calculation_shape, + yx_pixel_size / yx_factor, + z_pixel_size / z_factor, + wavelength_illumination, + z_padding, + index_of_refraction_media, + numerical_aperture_illumination, + numerical_aperture_detection, + invert_phase_contrast=invert_phase_contrast, ) # avg_pool3d does not support complex numbers diff --git a/waveorder/models/isotropic_fluorescent_thick_3d.py b/waveorder/models/isotropic_fluorescent_thick_3d.py index 75048dba..cf67c862 100644 --- a/waveorder/models/isotropic_fluorescent_thick_3d.py +++ b/waveorder/models/isotropic_fluorescent_thick_3d.py @@ -5,9 +5,9 @@ from torch import Tensor from waveorder import optics, sampling, util -from waveorder.visuals.napari_visuals import add_transfer_function_to_viewer from waveorder.filter import apply_filter_bank from waveorder.reconstruct import tikhonov_regularized_inverse_filter +from waveorder.visuals.napari_visuals import add_transfer_function_to_viewer def generate_test_phantom( @@ -32,7 +32,6 @@ def calculate_transfer_function( index_of_refraction_media: float, numerical_aperture_detection: float, ) -> Tensor: - transverse_nyquist = sampling.transverse_nyquist( wavelength_emission, numerical_aperture_detection, # ill = det for fluorescence @@ -217,10 +216,12 @@ def apply_inverse_transfer_function( inverse_filter = tikhonov_regularized_inverse_filter( optical_transfer_function, regularization_strength ) - + # [None]s and [0] are for applying a 1x1 "bank" of filters. # For further uniformity, consider returning (1, Z, Y, X) - f_real = apply_filter_bank(inverse_filter[None, None], zyx_padded[None])[0] + f_real = apply_filter_bank( + inverse_filter[None, None], zyx_padded[None] + )[0] elif reconstruction_algorithm == "TV": raise NotImplementedError f_real = util.single_variable_admm_tv_deconvolution_3D( diff --git a/waveorder/models/isotropic_thin_3d.py b/waveorder/models/isotropic_thin_3d.py index 3d0d5737..75e748a0 100644 --- a/waveorder/models/isotropic_thin_3d.py +++ b/waveorder/models/isotropic_thin_3d.py @@ -6,6 +6,7 @@ from waveorder import optics, sampling, util + def generate_test_phantom( yx_shape: Tuple[int, int], yx_pixel_size: float, @@ -50,20 +51,21 @@ def calculate_transfer_function( ) yx_factor = int(np.ceil(yx_pixel_size / transverse_nyquist)) - absorption_2d_to_3d_transfer_function, phase_2d_to_3d_transfer_function = ( - _calculate_wrap_unsafe_transfer_function( - ( - yx_shape[0] * yx_factor, - yx_shape[1] * yx_factor, - ), - yx_pixel_size / yx_factor, - z_position_list, - wavelength_illumination, - index_of_refraction_media, - numerical_aperture_illumination, - numerical_aperture_detection, - invert_phase_contrast=invert_phase_contrast, - ) + ( + absorption_2d_to_3d_transfer_function, + phase_2d_to_3d_transfer_function, + ) = _calculate_wrap_unsafe_transfer_function( + ( + yx_shape[0] * yx_factor, + yx_shape[1] * yx_factor, + ), + yx_pixel_size / yx_factor, + z_position_list, + wavelength_illumination, + index_of_refraction_media, + numerical_aperture_illumination, + numerical_aperture_detection, + invert_phase_contrast=invert_phase_contrast, ) absorption_2d_to_3d_transfer_function_out = torch.zeros( @@ -151,9 +153,9 @@ def visualize_transfer_function( absorption_2d_to_3d_transfer_function: Tensor, phase_2d_to_3d_transfer_function: Tensor, ) -> None: - """Note: unlike other `visualize_transfer_function` calls, this transfer + """Note: unlike other `visualize_transfer_function` calls, this transfer function is a mixed 3D-to-2D transfer function, so it cannot reuse - util.add_transfer_function_to_viewer. If more 3D-to-2D transfer functions + util.add_transfer_function_to_viewer. If more 3D-to-2D transfer functions are added, consider refactoring. """ arrays = [ diff --git a/waveorder/models/phase_thick_3d.py b/waveorder/models/phase_thick_3d.py index a5aed92b..5a2e2547 100644 --- a/waveorder/models/phase_thick_3d.py +++ b/waveorder/models/phase_thick_3d.py @@ -5,10 +5,10 @@ from torch import Tensor from waveorder import optics, sampling, util -from waveorder.models import isotropic_fluorescent_thick_3d -from waveorder.visuals.napari_visuals import add_transfer_function_to_viewer from waveorder.filter import apply_filter_bank +from waveorder.models import isotropic_fluorescent_thick_3d from waveorder.reconstruct import tikhonov_regularized_inverse_filter +from waveorder.visuals.napari_visuals import add_transfer_function_to_viewer def generate_test_phantom( @@ -58,22 +58,23 @@ def calculate_transfer_function( yx_factor = int(np.ceil(yx_pixel_size / transverse_nyquist)) z_factor = int(np.ceil(z_pixel_size / axial_nyquist)) - real_potential_transfer_function, imag_potential_transfer_function = ( - _calculate_wrap_unsafe_transfer_function( - ( - zyx_shape[0] * z_factor, - zyx_shape[1] * yx_factor, - zyx_shape[2] * yx_factor, - ), - yx_pixel_size / yx_factor, - z_pixel_size / z_factor, - wavelength_illumination, - z_padding, - index_of_refraction_media, - numerical_aperture_illumination, - numerical_aperture_detection, - invert_phase_contrast=invert_phase_contrast, - ) + ( + real_potential_transfer_function, + imag_potential_transfer_function, + ) = _calculate_wrap_unsafe_transfer_function( + ( + zyx_shape[0] * z_factor, + zyx_shape[1] * yx_factor, + zyx_shape[2] * yx_factor, + ), + yx_pixel_size / yx_factor, + z_pixel_size / z_factor, + wavelength_illumination, + z_padding, + index_of_refraction_media, + numerical_aperture_illumination, + numerical_aperture_detection, + invert_phase_contrast=invert_phase_contrast, ) zyx_out_shape = (zyx_shape[0] + 2 * z_padding,) + zyx_shape[1:] diff --git a/waveorder/optics.py b/waveorder/optics.py index fa7bd37b..46204517 100644 --- a/waveorder/optics.py +++ b/waveorder/optics.py @@ -1,9 +1,8 @@ +import itertools + import numpy as np import torch -import matplotlib.pyplot as plt -import gc -import itertools -from numpy.fft import fft, fft2, ifft2, fftn, ifftn, fftshift, ifftshift +from numpy.fft import fft2, fftn, fftshift, ifft2, ifftn, ifftshift def Jones_sample(Ein, t, sa): @@ -272,7 +271,7 @@ def generate_vector_source_defocus_pupil( # TEMPORARY SIMPLIFY ROTATIONS "TURN OFF ROTATIONS" # 3x2 IDENTITY MATRIX rotations = torch.zeros_like(rotations) - rotations[1, 0, ...] = 1 + rotations[1, 0, ...] = 1 rotations[2, 1, ...] = 1 # Main calculation in the frequency domain @@ -718,10 +717,10 @@ def gen_dyadic_Greens_tensor(G_real, ps, psz, lambda_in, space="real"): def generate_greens_tensor_spectrum( - zyx_shape, - zyx_pixel_size, - wavelength, - ): + zyx_shape, + zyx_pixel_size, + wavelength, +): """ Parameters ---------- @@ -733,14 +732,12 @@ def generate_greens_tensor_spectrum( Returns ------- torch.tensor - Green's tensor spectrum + Green's tensor spectrum """ Z, Y, X = zyx_shape dZ, dY, dX = zyx_pixel_size - z_step = torch.fft.ifftshift( - (torch.arange(Z) - Z // 2) * dZ - ) + z_step = torch.fft.ifftshift((torch.arange(Z) - Z // 2) * dZ) y_step = torch.fft.ifftshift((torch.arange(Y) - Y // 2) * dY) x_step = torch.fft.ifftshift((torch.arange(X) - X // 2) * dX) @@ -769,7 +766,7 @@ def generate_greens_tensor_spectrum( G_3D /= torch.amax(torch.abs(G_3D)) return G_3D - + def compute_weak_object_transfer_function_2d( illumination_pupil, detection_pupil diff --git a/waveorder/stokes.py b/waveorder/stokes.py index b7bf7ce6..136eb2e0 100644 --- a/waveorder/stokes.py +++ b/waveorder/stokes.py @@ -2,7 +2,7 @@ Overview -------- -This module collects Stokes- and Mueller-related calculations. +This module collects Stokes- and Mueller-related calculations. The functions are roughly organized into groups: @@ -29,8 +29,8 @@ Usage ----- -All functions are intended to be used with torch.Tensors with Stokes- or -Mueller-indices as the first axes. +All functions are intended to be used with torch.Tensors with Stokes- or +Mueller-indices as the first axes. For example, the following usage modes of stokes_after_adr are valid: @@ -46,6 +46,7 @@ >>> stokes_after_adr(*adr_params) # * expands along the first axis """ + import numpy as np import torch diff --git a/waveorder/util.py b/waveorder/util.py index bb8a60b7..f5e13602 100644 --- a/waveorder/util.py +++ b/waveorder/util.py @@ -1,15 +1,14 @@ +import re +import time +from collections import namedtuple + import numpy as np -import matplotlib.pyplot as plt import pywt -import time import torch - -from numpy.fft import fft, ifft, fft2, ifft2, fftn, ifftn, fftshift, ifftshift +from numpy.fft import fft, fft2, fftn, fftshift, ifft, ifftn, ifftshift from scipy.ndimage import uniform_filter -from collections import namedtuple -from .optics import scattering_potential_tensor_to_3D_orientation_PN -import re +from .optics import scattering_potential_tensor_to_3D_orientation_PN numbers = re.compile(r"(\d+)") @@ -2289,5 +2288,6 @@ def gellmann(): [[e, 0, 0], [0, d, 0], [0, 0, d]], [[0, 0, c], [0, 0, 0], [c, 0, 0]], [[0, 0, 0], [0, -c, 0], [0, 0, c]], # - ], dtype=torch.complex64 - ) \ No newline at end of file + ], + dtype=torch.complex64, + ) diff --git a/waveorder/visuals/jupyter_visuals.py b/waveorder/visuals/jupyter_visuals.py index f3d9db20..0e42fdd4 100644 --- a/waveorder/visuals/jupyter_visuals.py +++ b/waveorder/visuals/jupyter_visuals.py @@ -1,22 +1,16 @@ -import numpy as np -import matplotlib.pyplot as plt -import ipywidgets as widgets -import os import io +import os + +import ipywidgets as widgets +import matplotlib.pyplot as plt +import numpy as np +from ipywidgets import HBox, Image, Layout, interact +from matplotlib.colors import Normalize, hsv_to_rgb +from numpy.typing import NDArray from PIL import Image as PImage -from ipywidgets import ( - Image, - Layout, - interact, - HBox, -) -from matplotlib.colors import hsv_to_rgb -from matplotlib.colors import Normalize from scipy.ndimage import uniform_filter from scipy.stats import binned_statistic_2d -from numpy.typing import NDArray - def im_bit_convert(im, bit=16, norm=False, limit=[]): im = im.astype( @@ -172,7 +166,7 @@ def image_stack_viewer_fast( else: raise ValueError('origin can only be either "upper" or "lower"') - im_wgt = Image( + im_wgt = Image( value=im_dict[0], layout=Layout(height=str(size[0]) + "px", width=str(size[1]) + "px"), ) @@ -1046,15 +1040,17 @@ def plotVectorField( # plot vector field representaiton of the orientation map # Compute U, V such that they are as long as line-length when anisotropy = 1. - U, V = anisotropy * linelength * np.cos( - 2 * orientation - ), anisotropy * linelength * np.sin(2 * orientation) + U, V = ( + anisotropy * linelength * np.cos(2 * orientation), + anisotropy * linelength * np.sin(2 * orientation), + ) USmooth = uniform_filter(U, (window, window)) # plot smoothed vector field VSmooth = uniform_filter(V, (window, window)) # plot smoothed vector field azimuthSmooth = 0.5 * np.arctan2(VSmooth, USmooth) RSmooth = np.sqrt(USmooth**2 + VSmooth**2) - USmooth, VSmooth = RSmooth * np.cos(azimuthSmooth), RSmooth * np.sin( - azimuthSmooth + USmooth, VSmooth = ( + RSmooth * np.cos(azimuthSmooth), + RSmooth * np.sin(azimuthSmooth), ) nY, nX = img.shape @@ -1639,8 +1635,9 @@ def plot3DVectorField( VSmooth = uniform_filter(V, (window, window)) # plot smoothed vector field azimuthSmooth = 0.5 * np.arctan2(VSmooth, USmooth) RSmooth = np.sqrt(USmooth**2 + VSmooth**2) - USmooth, VSmooth = RSmooth * np.cos(azimuthSmooth), RSmooth * np.sin( - azimuthSmooth + USmooth, VSmooth = ( + RSmooth * np.cos(azimuthSmooth), + RSmooth * np.sin(azimuthSmooth), ) nY, nX = img.shape diff --git a/waveorder/visuals/matplotlib_visuals.py b/waveorder/visuals/matplotlib_visuals.py index 3f415370..ec4ba6b9 100644 --- a/waveorder/visuals/matplotlib_visuals.py +++ b/waveorder/visuals/matplotlib_visuals.py @@ -23,7 +23,7 @@ def plot_5d_ortho( Plot 5D multi-channel data in a grid or ortho-slice views. Input data is a 6D array with (row, column, channels, Z, Y, X) dimensions. - + `color_funcs` permits different RGB color maps for each row and column. Parameters @@ -32,7 +32,7 @@ def plot_5d_ortho( 5D array with shape (R, C, Ch, Z, Y, X) containing the data to plot. [r]ows and [c]olumns form a grid [C]hannels contain multiple color channels - [ZYX] contain 3D volumes. + [ZYX] contain 3D volumes. filename : str Path to save the output plot. voxel_size : tuple[float, float, float] @@ -40,8 +40,8 @@ def plot_5d_ortho( zyx_slice : tuple[int, int, int] Indices of the ortho-slices to plot in (Z, Y, X) indices. color_funcs : list[list[callable]] - A list of lists of callables, one for each element of the plot grid, - with len(color_funcs) == R and len(colors_funcs[0] == C). + A list of lists of callables, one for each element of the plot grid, + with len(color_funcs) == R and len(colors_funcs[0] == C). Each callable accepts [C]hannel arguments and returns RGB color values, enabling different RGB color maps for each member of the grid. row_labels : list[str], optional diff --git a/waveorder/visuals/napari_visuals.py b/waveorder/visuals/napari_visuals.py index b1887740..03f873c2 100644 --- a/waveorder/visuals/napari_visuals.py +++ b/waveorder/visuals/napari_visuals.py @@ -1,9 +1,8 @@ -from waveorder.visuals.utils import complex_tensor_to_rgb -from typing import TYPE_CHECKING - import numpy as np import torch +from waveorder.visuals.utils import complex_tensor_to_rgb + def add_transfer_function_to_viewer( viewer: "napari.Viewer", diff --git a/waveorder/visuals/utils.py b/waveorder/visuals/utils.py index dbde006c..8fbfe492 100644 --- a/waveorder/visuals/utils.py +++ b/waveorder/visuals/utils.py @@ -1,11 +1,10 @@ -import numpy as np import matplotlib.colors as mcolors +import numpy as np # Main function to convert a complex-valued torch tensor to RGB numpy array # with red at +1, green at +i, blue at -1, and purple at -i def complex_tensor_to_rgb(array, saturate_clim_fraction=1.0): - # Calculate magnitude and phase for the entire array magnitude = np.abs(array) phase = np.angle(array) diff --git a/waveorder/waveorder_reconstructor.py b/waveorder/waveorder_reconstructor.py index 54da4526..e6d95146 100644 --- a/waveorder/waveorder_reconstructor.py +++ b/waveorder/waveorder_reconstructor.py @@ -1,15 +1,15 @@ -import numpy as np -import matplotlib.pyplot as plt import itertools import time -import os import warnings -from numpy.fft import fft, ifft, fft2, ifft2, fftn, ifftn, fftshift, ifftshift + +import matplotlib.pyplot as plt +import numpy as np from IPython import display -from scipy.ndimage import uniform_filter -from .util import * -from .optics import * +from numpy.fft import fft2, fftn, fftshift, ifft, ifft2, ifftn, ifftshift + from .background_estimator import * +from .optics import * +from .util import * def intensity_mapping(img_stack): @@ -1474,8 +1474,9 @@ def OTF_compute(x, y, z): torch.tensor(z.astype("complex64").transpose((2, 1, 0))), torch.tensor(self.psz), ) - return H_re.numpy().transpose((1, 2, 0)), H_im.numpy().transpose( - (1, 2, 0) + return ( + H_re.numpy().transpose((1, 2, 0)), + H_im.numpy().transpose((1, 2, 0)), ) for i in range(self.N_pattern): diff --git a/waveorder/waveorder_simulator.py b/waveorder/waveorder_simulator.py index 6de0d5b3..aceb1b79 100644 --- a/waveorder/waveorder_simulator.py +++ b/waveorder/waveorder_simulator.py @@ -1,13 +1,13 @@ -import numpy as np -import matplotlib.pyplot as plt import itertools import time -import os -import torch -from numpy.fft import fft, ifft, fft2, ifft2, fftn, ifftn, fftshift, ifftshift from concurrent.futures import ProcessPoolExecutor -from .util import * + +import numpy as np +import torch +from numpy.fft import fft2, fftn, fftshift, ifft2, ifftn, ifftshift + from .optics import * +from .util import * def Jones_PC_forward_model(