diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 06d879c..0000000 --- a/.flake8 +++ /dev/null @@ -1,5 +0,0 @@ -[flake8] -exclude = - api.py, - __init__.py - scratch/scratch.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4f9c848..e2535ae 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,11 +5,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: pip install flake8 - - run: pip install pydocstyle - - run: flake8 --exclude=api.py --ignore=E501 etspy/ - - if: always() - run: pydocstyle etspy/ + - run: pip install ruff isort + - run: isort . --check + - run: ruff check pytest: runs-on: ubuntu-latest @@ -17,7 +15,10 @@ jobs: image: aaherzing/etspy:latest steps: - uses: actions/checkout@v4 - - run: pip install -e ./ - - run: pytest --doctest-modules --ignore=etspy/tests/test_datasets.py etspy/ - - if: always() - run: pytest --ignore=etspy/tests/test_datasets.py etspy/tests/ + - name: Install ETSpy + run: pip install -e ./ + - name: Run docstring example tests + run: pytest --doctest-modules --ignore=etspy/tests etspy/ + - name: Run full test suite + if: always() + run: pytest etspy/tests/ diff --git a/etspy/__init__.py b/etspy/__init__.py index 2139bb0..141639e 100644 --- a/etspy/__init__.py +++ b/etspy/__init__.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -# -# This file is part of ETSpy -"""__init__ function for ETSpy.""" +"""ETSpy.""" -__version__ = '0.8' +__version__ = "0.8" diff --git a/etspy/align.py b/etspy/align.py index b49e209..5169596 100644 --- a/etspy/align.py +++ b/etspy/align.py @@ -1,35 +1,36 @@ -# -*- coding: utf-8 -*- -# -# This file is part of ETSpy - """ Alignment module for ETSpy package. @author: Andrew Herzing """ -import numpy as np +# pyright: reportPossiblyUnboundVariable=false + import copy -from scipy import optimize, ndimage +import logging + +import astra +import matplotlib.pylab as plt +import numpy as np import tqdm from pystackreg import StackReg -import logging -from skimage.registration import phase_cross_correlation as pcc -from skimage.transform import hough_line, hough_line_peaks +from scipy import ndimage, optimize from skimage.feature import canny from skimage.filters import sobel -import matplotlib.pylab as plt -import astra +from skimage.registration import phase_cross_correlation as pcc +from skimage.transform import hough_line, hough_line_peaks has_cupy = True try: - import cupy as cp + import cupy as cp # type: ignore except ImportError: has_cupy = False logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) +CL_RES_THRESHOLD = 0.5 # threshold for common line registration method + def get_best_slices(stack, nslices): """ @@ -38,7 +39,7 @@ def get_best_slices(stack, nslices): Slices which have the highest ratio of total mass to mass variance and their location are returned. - Args + Parameters ---------- stack : TomoStack object Tilt series from which to select the best slices. @@ -46,7 +47,7 @@ def get_best_slices(stack, nslices): Number of slices to return. Returns - ---------- + ------- locs : NumPy array Location along the x-axis of the best slices @@ -63,7 +64,7 @@ def get_coms(stack, slices): """ Calculate the center of mass for indicated slices. - Args + Parameters ---------- stack : TomoStack object Tilt series from which to calculate the centers of mass. @@ -71,16 +72,14 @@ def get_coms(stack, slices): Location of slices to use for center of mass calculation. Returns - ---------- + ------- coms : NumPy array Center of mass as a function of tilt for each slice [ntilts, nslices]. """ sinos = stack.data[:, :, slices] com_range = int(sinos.shape[1] / 2) - y_coordinates = np.linspace(-com_range, - com_range, - sinos.shape[1], dtype="int") + y_coordinates = np.linspace(-com_range, com_range, sinos.shape[1], dtype="int") total_mass = sinos.sum(1) coms = np.sum(np.transpose(sinos, [0, 2, 1]) * y_coordinates, 2) / total_mass return coms @@ -91,7 +90,7 @@ def apply_shifts(stack, shifts): Apply a series of shifts to a TomoStack. - Args + Parameters ---------- stack : TomoStack object The image series to be aligned @@ -99,20 +98,22 @@ def apply_shifts(stack, shifts): The X- and Y-shifts to be applied to each image Returns - ---------- + ------- shifted : TomoStack object Copy of input stack after shifts are applied """ shifted = stack.deepcopy() if len(shifts) != stack.data.shape[0]: - raise ValueError( - "Number of shifts (%s) is not consistent with number" - "of images in the stack (%s)" % (len(shifts), stack.data.shape[0]) + msg = ( + f"Number of shifts ({len(shifts)}) is not consistent " + f"with number of images in the stack ({stack.data.shape[0]})" ) + raise ValueError(msg) for i in range(shifted.data.shape[0]): shifted.data[i, :, :] = ndimage.shift( - shifted.data[i, :, :], shift=[shifts[i, 0], shifts[i, 1]] + shifted.data[i, :, :], + shift=[shifts[i, 0], shifts[i, 1]], ) shifted.metadata.Tomography.shifts = shifted.metadata.Tomography.shifts + shifts return shifted @@ -120,10 +121,9 @@ def apply_shifts(stack, shifts): def pad_line(line, paddedsize): """ - Pad a 1D array for FFT treatment without altering center location. - Args + Parameters ---------- line : 1D NumPy array The data to be padded @@ -131,7 +131,7 @@ def pad_line(line, paddedsize): The size of the desired padded data. Returns - ---------- + ------- padded : 1D NumPy array Padded version of input data @@ -151,7 +151,7 @@ def calc_shifts_cl(stack, cl_ref_index, cl_resolution, cl_div_factor): Used to align stack in dimension parallel to the tilt axis - Args + Parameters ---------- stack : TomoStack object The stack on which to calculate shifts @@ -164,7 +164,7 @@ def calc_shifts_cl(stack, cl_ref_index, cl_resolution, cl_div_factor): Factor used to determine number of iterations of alignment. Returns - ---------- + ------- yshifts : NumPy array Shifts parallel to tilt axis for each projection @@ -180,21 +180,23 @@ def align_line(ref_line, line, cl_resolution, cl_div_factor): niters = int(np.abs(np.floor(np.log(cl_resolution) / np.log(cl_div_factor)))) start, end = -0.5, 0.5 - ref_line_pad_FT = np.fft.fftshift(np.fft.fft(np.fft.ifftshift(ref_line_pad))) - line_pad_FT = np.fft.fftshift(np.fft.fft(np.fft.ifftshift(line_pad))) + ref_line_pad_ft = np.fft.fftshift(np.fft.fft(np.fft.ifftshift(ref_line_pad))) + line_pad_ft = np.fft.fftshift(np.fft.fft(np.fft.ifftshift(line_pad))) midpoint = (npad - 1) / 2 kx = np.arange(-midpoint, midpoint + 1) - for i in range(niters): - boundary = np.linspace(start, end, cl_div_factor, False) + for _ in range(niters): + boundary = np.linspace(start, end, cl_div_factor, endpoint=False) index = (boundary[:-1] + boundary[1:]) / 2 max_vals = np.zeros(len(index)) for j, idx in enumerate(index): pfactor = np.exp(2 * np.pi * 1j * (idx * kx / npad)) - conjugate = np.conj(ref_line_pad_FT) * line_pad_FT * pfactor - xcorr = np.abs(np.fft.fftshift(np.fft.ifft(np.fft.ifftshift(conjugate)))) + conjugate = np.conj(ref_line_pad_ft) * line_pad_ft * pfactor + xcorr = np.abs( + np.fft.fftshift(np.fft.ifft(np.fft.ifftshift(conjugate))), + ) max_vals[j] = np.max(xcorr) max_loc = np.argmax(max_vals) @@ -204,7 +206,7 @@ def align_line(ref_line, line, cl_resolution, cl_div_factor): max_pfactor = np.exp(2 * np.pi * 1j * (subpixel_shift * kx / npad)) # Determine integer shift via cross correlation - conjugate = np.conj(ref_line_pad_FT) * line_pad_FT * max_pfactor + conjugate = np.conj(ref_line_pad_ft) * line_pad_ft * max_pfactor xcorr = np.abs(np.fft.fftshift(np.fft.ifft(np.fft.ifftshift(conjugate)))) max_loc = np.argmax(xcorr) @@ -236,7 +238,7 @@ def calculate_shifts_conservation_of_mass(stack, xrange=None, p=20): Slices which have the highest ratio of total mass to mass variance and their location are returned. - Args + Parameters ---------- stack : TomoStack object Tilt series to be aligned. @@ -246,7 +248,7 @@ def calculate_shifts_conservation_of_mass(stack, xrange=None, p=20): Padding element Returns - ---------- + ------- xshifts : NumPy array Calculated shifts parallel to tilt axis. @@ -262,15 +264,18 @@ def calculate_shifts_conservation_of_mass(stack, xrange=None, p=20): xshifts = np.zeros([ntilts, 1]) total_mass = np.zeros([ntilts, xrange[1] - xrange[0] + 2 * p + 1]) - for i in range(0, ntilts): - total_mass[i, :] = np.sum(stack.data[i, :, xrange[0] - p - 1: xrange[1] + p], 0) + for i in range(ntilts): + total_mass[i, :] = np.sum( + stack.data[i, :, xrange[0] - p - 1 : xrange[1] + p], + 0, + ) mean_mass = np.mean(total_mass[:, p:-p], 0) - for i in range(0, ntilts): + for i in range(ntilts): s = 0 for j in range(-p, p): - resid = np.linalg.norm(mean_mass - total_mass[i, p + j: -p + j]) + resid = np.linalg.norm(mean_mass - total_mass[i, p + j : -p + j]) if resid < s or j == -p: s = resid xshifts[i] = -j @@ -285,7 +290,7 @@ def calculate_shifts_com(stack, nslices): perpendicular to the tilt axis are refined by a center of mass analysis. - Args + Parameters ---------- stack : TomoStack object The image series to be aligned @@ -297,7 +302,7 @@ def calculate_shifts_com(stack, nslices): center of mass analysis. Must be less than or equal to 1.0. Returns - ---------- + ------- shifts : NumPy array The X- and Y-shifts to be applied to each image @@ -310,109 +315,162 @@ def calculate_shifts_com(stack, nslices): thetas = np.pi * angles / 180 coms = get_coms(stack, slices) - I_tilts = np.eye(ntilts) - Gam = np.array([np.cos(thetas), np.sin(thetas)]).T - Gam = np.dot(Gam, np.linalg.pinv(Gam)) - I_tilts - b = np.dot(Gam, coms) + i_tilts = np.eye(ntilts) + gam = np.array([np.cos(thetas), np.sin(thetas)]).T + gam = np.dot(gam, np.linalg.pinv(gam)) - i_tilts + b = np.dot(gam, coms) - cx = np.linalg.lstsq(Gam, b, rcond=-1)[0] + cx = np.linalg.lstsq(gam, b, rcond=-1)[0] yshifts = -cx[:, 0] return yshifts +def _upsampled_dft( + data, + upsampled_region_size, + upsample_factor, + axis_offsets, +): + upsampled_region_size = [ + upsampled_region_size, + ] * data.ndim + + im2pi = 1j * 2 * cp.pi + + dim_properties = list(zip(data.shape, upsampled_region_size, axis_offsets)) + + for n_items, ups_size, ax_offset in dim_properties[::-1]: + kernel = (cp.arange(ups_size) - ax_offset)[:, None] * cp.fft.fftfreq( + n_items, + upsample_factor, + ) + kernel = cp.exp(-im2pi * kernel) + # use kernel with same precision as the data + kernel = kernel.astype(data.dtype, copy=False) + data = cp.tensordot(kernel, data, axes=(1, -1)) + return data + + +def _cupy_phase_correlate(ref_cp, mov_cp, upsample_factor, shape): + ref_fft = cp.fft.fftn(ref_cp) + mov_fft = cp.fft.fftn(mov_cp) + + cross_power_spectrum = ref_fft * mov_fft.conj() + eps = cp.finfo(cross_power_spectrum.real.dtype).eps + cross_power_spectrum /= cp.maximum(cp.abs(cross_power_spectrum), 100 * eps) + phase_correlation = cp.fft.ifft2(cross_power_spectrum) + + maxima = cp.unravel_index( + cp.argmax(cp.abs(phase_correlation)), + phase_correlation.shape, + ) + midpoint = cp.array([cp.fix(axis_size / 2) for axis_size in shape]) + + float_dtype = cross_power_spectrum.real.dtype + + shift = cp.stack(maxima).astype(float_dtype, copy=False) + shift[shift > midpoint] -= cp.array(shape)[shift > midpoint] + + if upsample_factor > 1: + upsample_factor = cp.array(upsample_factor, dtype=float_dtype) + upsampled_region_size = cp.ceil(upsample_factor * 1.5) + dftshift = cp.fix(upsampled_region_size / 2.0) + + shift = cp.round(shift * upsample_factor) / upsample_factor + + sample_region_offset = dftshift - shift * upsample_factor + phase_correlation = _upsampled_dft( + cross_power_spectrum.conj(), + upsampled_region_size, + upsample_factor, + sample_region_offset, + ).conj() + maxima = np.unravel_index( + cp.argmax(np.abs(phase_correlation)), + phase_correlation.shape, + ) + + maxima = cp.stack(maxima).astype(float_dtype, copy=False) + maxima -= dftshift + + shift += maxima / upsample_factor + return shift + + +def _cupy_calculate_shifts(stack, start, show_progressbar, upsample_factor): + stack_cp = cp.array(stack.data) + shifts = cp.zeros([stack_cp.shape[0], 2]) + ref_cp = stack_cp[0] + ref_fft = cp.fft.fftn(ref_cp) + shape = ref_fft.shape + with tqdm.tqdm( + total=stack.data.shape[0] - 1, + desc="Calculating shifts", + disable=not show_progressbar, + ) as pbar: + for i in range(start, 0, -1): + shift = _cupy_phase_correlate( + stack_cp[i], + stack_cp[i - 1], + upsample_factor=upsample_factor, + shape=shape, + ) + shifts[i - 1] = shifts[i] + shift + pbar.update(1) + for i in range(start, stack.data.shape[0] - 1): + shift = _cupy_phase_correlate( + stack_cp[i], + stack_cp[i + 1], + upsample_factor=upsample_factor, + shape=shape, + ) + shifts[i + 1] = shifts[i] + shift + pbar.update(1) + shifts = shifts.get() + return shifts + + def calculate_shifts_pc(stack, start, show_progressbar, upsample_factor, cuda): """ - Calculate shifts using the phase correlation algorithm. - Args + Parameters ---------- stack : TomoStack object The image series to be aligned Returns - ---------- + ------- shifts : NumPy array The X- and Y-shifts to be applied to each image """ - def _upsampled_dft(data, upsampled_region_size, upsample_factor=1, axis_offsets=None): - upsampled_region_size = [upsampled_region_size,] * data.ndim - - im2pi = 1j * 2 * cp.pi - - dim_properties = list(zip(data.shape, upsampled_region_size, axis_offsets)) - - for n_items, ups_size, ax_offset in dim_properties[::-1]: - kernel = (cp.arange(ups_size) - ax_offset)[:, None] * cp.fft.fftfreq(n_items, upsample_factor) - kernel = cp.exp(-im2pi * kernel) - # use kernel with same precision as the data - kernel = kernel.astype(data.dtype, copy=False) - data = cp.tensordot(kernel, data, axes=(1, -1)) - return data - - def _cupy_phase_correlate(ref_cp, mov_cp, upsample_factor): - ref_fft = cp.fft.fftn(ref_cp) - mov_fft = cp.fft.fftn(mov_cp) - - cross_power_spectrum = ref_fft * mov_fft.conj() - eps = cp.finfo(cross_power_spectrum.real.dtype).eps - cross_power_spectrum /= cp.maximum(cp.abs(cross_power_spectrum), 100 * eps) - phase_correlation = cp.fft.ifft2(cross_power_spectrum) - - maxima = cp.unravel_index(cp.argmax(cp.abs(phase_correlation)), phase_correlation.shape) - midpoint = cp.array([cp.fix(axis_size / 2) for axis_size in shape]) - - float_dtype = cross_power_spectrum.real.dtype - - shift = cp.stack(maxima).astype(float_dtype, copy=False) - shift[shift > midpoint] -= cp.array(shape)[shift > midpoint] - - if upsample_factor > 1: - upsample_factor = cp.array(upsample_factor, dtype=float_dtype) - upsampled_region_size = cp.ceil(upsample_factor * 1.5) - dftshift = cp.fix(upsampled_region_size / 2.0) - - shift = cp.round(shift * upsample_factor) / upsample_factor - - sample_region_offset = dftshift - shift * upsample_factor - phase_correlation = _upsampled_dft(cross_power_spectrum.conj(), upsampled_region_size, upsample_factor, sample_region_offset).conj() - maxima = np.unravel_index(cp.argmax(np.abs(phase_correlation)), phase_correlation.shape) - - maxima = cp.stack(maxima).astype(float_dtype, copy=False) - maxima -= dftshift - - shift += maxima / upsample_factor - return shift - if has_cupy and astra.use_cuda() and cuda: - stack_cp = cp.array(stack.data) - shifts = cp.zeros([stack_cp.shape[0], 2]) - ref_cp = stack_cp[0] - ref_fft = cp.fft.fftn(ref_cp) - shape = ref_fft.shape - with tqdm.tqdm(total=stack.data.shape[0] - 1, desc="Calculating shifts", disable=not show_progressbar) as pbar: - for i in range(start, 0, -1): - shift = _cupy_phase_correlate(stack_cp[i], stack_cp[i - 1], upsample_factor=upsample_factor) - shifts[i - 1] = shifts[i] + shift - pbar.update(1) - for i in range(start, stack.data.shape[0] - 1): - shift = _cupy_phase_correlate(stack_cp[i], stack_cp[i + 1], upsample_factor=upsample_factor) - shifts[i + 1] = shifts[i] + shift - pbar.update(1) - shifts = shifts.get() + shifts = _cupy_calculate_shifts(stack, start, show_progressbar, upsample_factor) else: shifts = np.zeros((stack.data.shape[0], 2)) - with tqdm.tqdm(total=stack.data.shape[0] - 1, desc="Calculating shifts", disable=not show_progressbar) as pbar: + with tqdm.tqdm( + total=stack.data.shape[0] - 1, + desc="Calculating shifts", + disable=not show_progressbar, + ) as pbar: for i in range(start, 0, -1): - shift = pcc(stack.data[i], stack.data[i - 1], upsample_factor=upsample_factor)[0] + shift = pcc( + stack.data[i], + stack.data[i - 1], + upsample_factor=upsample_factor, + )[0] shifts[i - 1] = shifts[i] + shift pbar.update(1) for i in range(start, stack.data.shape[0] - 1): - shift = pcc(stack.data[i], stack.data[i + 1], upsample_factor=upsample_factor)[0] + shift = pcc( + stack.data[i], + stack.data[i + 1], + upsample_factor=upsample_factor, + )[0] shifts[i + 1] = shifts[i] + shift pbar.update(1) @@ -423,13 +481,13 @@ def calculate_shifts_stackreg(stack, start, show_progressbar): """ Calculate shifts using PyStackReg. - Args + Parameters ---------- stack : TomoStack object The image series to be aligned Returns - ---------- + ------- shifts : NumPy array The X- and Y-shifts to be applied to each image @@ -442,7 +500,11 @@ def calculate_shifts_stackreg(stack, start, show_progressbar): # Initialize pystackreg object with TranslationTransform2D reg = StackReg(StackReg.TRANSLATION) - with tqdm.tqdm(total=stack.data.shape[0] - 1, desc="Calculating shifts", disable=not show_progressbar) as pbar: + with tqdm.tqdm( + total=stack.data.shape[0] - 1, + desc="Calculating shifts", + disable=not show_progressbar, + ) as pbar: # Calculate shifts relative to the image at the 'start' index for i in range(start, 0, -1): transformation = reg.register(stack.data[i], stack.data[i - 1]) @@ -458,14 +520,20 @@ def calculate_shifts_stackreg(stack, start, show_progressbar): return shifts -def calc_shifts_com_cl(stack, com_ref_index, cl_ref_index, cl_resolution, cl_div_factor): +def calc_shifts_com_cl( + stack, + com_ref_index, + cl_ref_index, + cl_resolution, + cl_div_factor, +): """ Align stack using combined center of mass and common line methods. Center of mass aligns stack perpendicular to the tilt axis and common line is used to align the stack parallel to the tilt axis. - Args + Parameters ---------- stack : TomoStack object Tilt series to be aligned @@ -485,7 +553,7 @@ def calc_shifts_com_cl(stack, com_ref_index, cl_ref_index, cl_resolution, cl_div alignment to perform. Default is 8. Returns - ---------- + ------- reg : TomoStack object Copy of stack after spatial registration. Shift values are stored in reg.metadata.Tomography.shifts for later use. @@ -494,21 +562,22 @@ def calc_shifts_com_cl(stack, com_ref_index, cl_ref_index, cl_resolution, cl_div def calc_yshifts(stack, com_ref): ntilts = stack.data.shape[0] - aliX = stack.deepcopy() + ali_x = stack.deepcopy() coms = np.zeros(ntilts) yshifts = np.zeros_like(coms) - for i in tqdm.tqdm(range(0, ntilts)): - im = aliX.data[i, :, :] + for i in tqdm.tqdm(range(ntilts)): + im = ali_x.data[i, :, :] coms[i], _ = ndimage.center_of_mass(im) yshifts[i] = com_ref - coms[i] return yshifts - if cl_resolution >= 0.5: - raise ValueError("Resolution should be less than 0.5") + if cl_resolution >= CL_RES_THRESHOLD: + msg = f"Resolution should be less than {CL_RES_THRESHOLD}" + raise ValueError(msg) - logger.info("Center of mass reference slice: %s" % com_ref_index) - logger.info("Common line reference slice: %s" % cl_ref_index) + logger.info("Center of mass reference slice: %s", com_ref_index) + logger.info("Common line reference slice: %s", cl_ref_index) xshifts = np.zeros(stack.data.shape[0]) yshifts = np.zeros(stack.data.shape[0]) yshifts = calc_yshifts(stack, com_ref_index) @@ -546,13 +615,13 @@ def align_stack(stack, method, start, show_progressbar, **kwargs): alignment for parallel to the tilt axis. This is a Python implementation of Matlab code described in: M. C. Scott, et al. Electron tomography at 2.4-ångström resolution, - Nature 483, 444–447 (2012). + Nature 483, 444-447 (2012). https://doi.org/10.1038/nature10934 Shifts are then applied and the aligned stack is returned. The tilts are stored in stack.metadata.Tomography.shifts for later use. - Args + Parameters ---------- stack : Numpy array 3-D numpy array containing the tilt series data @@ -566,42 +635,63 @@ def align_stack(stack, method, start, show_progressbar, **kwargs): Enable/disable progress bar Returns - ---------- + ------- out : TomoStack object Spatially registered copy of the input stack """ if start is None: - start = stack.data.shape[0] // 2 # Use the slice closest to the midpoint if start is not provided + start = ( + stack.data.shape[0] // 2 + ) # Use the slice closest to the midpoint if start is not provided if method.lower() == "com": logger.info("Performing stack registration using center of mass method") - xrange = kwargs.get('xrange', None) - p = kwargs.get('p', 20) - nslices = kwargs.get('nslices', 20) + xrange = kwargs.get("xrange", None) + p = kwargs.get("p", 20) + nslices = kwargs.get("nslices", 20) shifts = np.zeros([stack.data.shape[0], 2]) shifts[:, 1] = calculate_shifts_conservation_of_mass(stack, xrange, p) shifts[:, 0] = calculate_shifts_com(stack, nslices) - elif method.lower() == 'pc': - cuda = kwargs.get('cuda', False) - upsample_factor = kwargs.get('upsample_factor', 3) + elif method.lower() == "pc": + cuda = kwargs.get("cuda", False) + upsample_factor = kwargs.get("upsample_factor", 3) if cuda: - logger.info("Performing stack registration using CUDA-accelerated phase correlation") + logger.info( + "Performing stack registration using " + "CUDA-accelerated phase correlation", + ) else: logger.info("Performing stack registration using phase correlation") - shifts = calculate_shifts_pc(stack, start, show_progressbar, upsample_factor, cuda) - elif method.lower() in ["stackreg", 'sr']: + shifts = calculate_shifts_pc( + stack, + start, + show_progressbar, + upsample_factor, + cuda, + ) + elif method.lower() in ["stackreg", "sr"]: logger.info("Performing stack registration using PyStackReg") shifts = calculate_shifts_stackreg(stack, start, show_progressbar) elif method.lower() == "com-cl": - logger.info("Performing stack registration using combined center of mass and common line methods") - com_ref_index = kwargs.get('com_ref_index', stack.data.shape[1] // 2) - cl_ref_index = kwargs.get('cl_ref_index', stack.data.shape[0] // 2) - cl_resolution = kwargs.get('cl_resolution', 0.05) - cl_div_factor = kwargs.get('cl_div_factor', 8) - shifts = calc_shifts_com_cl(stack, com_ref_index, cl_ref_index, cl_resolution, cl_div_factor) + logger.info( + "Performing stack registration using combined " + "center of mass and common line methods", + ) + com_ref_index = kwargs.get("com_ref_index", stack.data.shape[1] // 2) + cl_ref_index = kwargs.get("cl_ref_index", stack.data.shape[0] // 2) + cl_resolution = kwargs.get("cl_resolution", 0.05) + cl_div_factor = kwargs.get("cl_div_factor", 8) + shifts = calc_shifts_com_cl( + stack, + com_ref_index, + cl_ref_index, + cl_resolution, + cl_div_factor, + ) else: - raise ValueError('Invalid alignment method %s' % method) + msg = f"Invalid alignment method {method}" + raise ValueError(msg) aligned = apply_shifts(stack, shifts) logger.info("Stack registration complete") return aligned @@ -613,7 +703,7 @@ def tilt_com(stack, slices=None, nslices=None): Compares path of specimen to the path expected for an ideal cylinder - Args + Parameters ---------- stack : TomoStack object 3-D numpy array containing the tilt series data @@ -623,7 +713,7 @@ def tilt_com(stack, slices=None, nslices=None): Nubmer of slices to suer for the analysis Returns - ---------- + ------- out : TomoStack object Copy of the input stack after rotation and translation to center and make the tilt axis vertical @@ -637,30 +727,38 @@ def fit_line(x, m, b): return m * x + b _, ny, nx = stack.data.shape + nx_threshold = 3 if stack.metadata.Tomography.tilts is None: - raise ValueError("Tilts are not defined in stack.metadata.Tomography.") + msg = "Tilts are not defined in stack.metadata.Tomography" + raise ValueError(msg) - if nx < 3: - raise ValueError("Dataset is only %s pixels in x dimension. This method cannot be used." % stack.data.shape[2]) + if nx < nx_threshold: + msg = ( + f"Dataset is only {stack.data.shape[2]} pixels in x dimension. " + "This method cannot be used." + ) + raise ValueError(msg) # Determine the best slice locations for the analysis if slices is None: if nslices is None: nslices = int(0.1 * nx) - if nslices < 3: - nslices = 3 - elif nslices > 50: - nslices = 50 + nslices = max(min(nslices, 50), 3) # clamp nslices to [3, 50] else: if nslices > nx: - raise ValueError("nslices is greater than the X-dimension of the data.") + msg = "nslices is greater than the X-dimension of the data." + raise ValueError(msg) if nslices > 0.3 * nx: nslices = int(0.3 * nx) - logger.warning("nslices is greater than 30%% of number of x pixels. Using %s slices instead." % nslices) + msg = ( + "nslices is greater than 30% of number of x pixels. " + f"Using {nslices} slices instead." + ) + logger.warning(msg) slices = get_best_slices(stack, nslices) - logger.info("Performing alignments using best %s slices" % nslices) + logger.info("Performing alignments using best %s slices", nslices) slices = np.sort(slices) @@ -669,28 +767,40 @@ def fit_line(x, m, b): r, x0, z0 = np.zeros(len(slices)), np.zeros(len(slices)), np.zeros(len(slices)) - for idx, i in enumerate(slices): - r[idx], x0[idx], z0[idx] = optimize.curve_fit(com_motion, xdata=thetas, ydata=coms[:, idx], p0=[0, 0, 0])[0] + for idx, _ in enumerate(slices): + r[idx], x0[idx], z0[idx] = optimize.curve_fit( + com_motion, + xdata=thetas, + ydata=coms[:, idx], + p0=[0, 0, 0], + )[0] slope, intercept = optimize.curve_fit(fit_line, xdata=r, ydata=slices, p0=[0, 0])[0] tilt_shift = (ny / 2 - intercept) / slope tilt_rotation = -(180 * np.arctan(1 / slope) / np.pi) final = stack.trans_stack(yshift=tilt_shift, angle=tilt_rotation) - logger.info("Calculated tilt-axis shift %.2f" % tilt_shift) - logger.info("Calculated tilt-axis rotation %.2f" % tilt_rotation) + logger.info("Calculated tilt-axis shift %.2f", tilt_shift) + logger.info("Calculated tilt-axis rotation %.2f", tilt_rotation) return final -def tilt_maximage(stack, limit=10, delta=0.1, plot_results=False, also_shift=False, shift_limit=20): +def tilt_maximage( + stack, + limit=10, + delta=0.1, + plot_results=False, + also_shift=False, + shift_limit=20, +): """ Perform automated determination of the tilt axis of a TomoStack. The projected maximum image used to determine the tilt axis by a combination of Sobel filtering and Hough transform analysis. - Args + Parameters ---------- stack : TomoStack object 3-D numpy array containing the tilt series data @@ -706,7 +816,7 @@ def tilt_maximage(stack, limit=10, delta=0.1, plot_results=False, also_shift=Fal by minimizing the sum of the reconstruction Returns - ---------- + ------- rotated : TomoStack object Rotated version of the input stack @@ -719,7 +829,7 @@ def tilt_maximage(stack, limit=10, delta=0.1, plot_results=False, also_shift=Fal edges = canny(edges) # Perform Hough transform to detect lines - angles = np.pi * np.arange(-limit, limit, delta) / 180. + angles = np.pi * np.arange(-limit, limit, delta) / 180.0 h, theta, d = hough_line(edges, angles) # Find peaks in Hough space @@ -730,7 +840,7 @@ def tilt_maximage(stack, limit=10, delta=0.1, plot_results=False, also_shift=Fal if plot_results: fig, ax = plt.subplots(1) - ax.imshow(image, cmap='gray') + ax.imshow(image, cmap="gray") for i in range(len(angles)): (x0, y0) = dists[i] * np.array([np.cos(angles[i]), np.sin(angles[i])]) @@ -746,9 +856,9 @@ def tilt_maximage(stack, limit=10, delta=0.1, plot_results=False, also_shift=Fal shifts = np.arange(-shift_limit, shift_limit, 1) nshifts = shifts.shape[0] shifted = ali.isig[0:nshifts, :].deepcopy() - for i in range(0, nshifts): + for i in range(nshifts): shifted.data[:, :, i] = np.roll(ali.isig[idx, :].data, int(shifts[i])) - shifted_rec = shifted.reconstruct('SIRT', 100, constrain=True) + shifted_rec = shifted.reconstruct("SIRT", 100, constrain=True) tilt_shift = shifts[shifted_rec.sum((1, 2)).data.argmin()] ali = ali.trans_stack(yshift=-tilt_shift) ali.metadata.Tomography.yshift = -tilt_shift @@ -759,7 +869,7 @@ def align_to_other(stack, other): """ Spatially register a TomoStack using previously calculated shifts. - Args + Parameters ---------- stack : TomoStack object TomoStack which was previously aligned @@ -767,7 +877,7 @@ def align_to_other(stack, other): TomoStack to be aligned. Must be the same size as the primary stack Returns - ---------- + ------- out : TomoStack object Aligned copy of other TomoStack @@ -794,9 +904,9 @@ def align_to_other(stack, other): out = out.trans_stack(xshift, yshift, tiltaxis) logger.info("TomoStack alignment applied") - logger.info("X-shift: %.1f" % xshift) - logger.info("Y-shift: %.1f" % yshift) - logger.info("Rotation: %.1f" % tiltaxis) + logger.info("X-shift: %.1f", xshift) + logger.info("Y-shift: %.1f", yshift) + logger.info("Rotation: %.1f", tiltaxis) return out @@ -804,13 +914,13 @@ def shift_crop(stack): """ Crop shifted stack to common area. - Args + Parameters ---------- stack : TomoStack object TomoStack which was previously aligned Returns - ---------- + ------- out : TomoStack object Aligned copy of other TomoStack diff --git a/etspy/api.py b/etspy/api.py index 8b57fc3..c2fb71b 100644 --- a/etspy/api.py +++ b/etspy/api.py @@ -1,16 +1,16 @@ -# -*- coding: utf-8 -*- -# -# This file is part of ETSpy +# ruff: noqa: F401 """API for ETSpy.""" import logging -from etspy.io import load, create_stack +from pathlib import Path + +from etspy import align, io, utils from etspy.base import TomoStack -from etspy import io -from etspy import utils -from etspy import align +from etspy.io import create_stack, load from . import __version__ logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) + +etspy_path = Path(__file__).parent diff --git a/etspy/base.py b/etspy/base.py index 8ff9caa..dd258ad 100644 --- a/etspy/base.py +++ b/etspy/base.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -# -# This file is part of ETSpy - """ Primary module for ETSpy package. @@ -10,17 +6,21 @@ @author: Andrew Herzing """ +import logging +from pathlib import Path +from typing import Iterable, Optional, Union + +import astra +import matplotlib as mpl import numpy as np -from etspy import recon, align -import os -from skimage import transform import pylab as plt -import matplotlib.animation as animation -from hyperspy.signals import Signal2D, Signal1D +from hyperspy.signals import Signal1D, Signal2D +from matplotlib import animation +from matplotlib.artist import Artist from scipy import ndimage -import matplotlib as mpl -import logging -import astra +from skimage import transform + +from etspy import align, recon logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -53,7 +53,7 @@ def invert(self): Invert the contrast levels of an entire Stack. Returns - ---------- + ------- inverted : CommonStack object Copy of the input stack with contrast inverted @@ -71,7 +71,8 @@ def invert(self): inverted = self.deepcopy() inverted.data = inverted.data - np.reshape( - inverted.data.mean(2).mean(1), [self.data.shape[0], 1, 1] + inverted.data.mean(2).mean(1), + [self.data.shape[0], 1, 1], ) inverted.data = (inverted.data - minvals) / ranges @@ -86,14 +87,14 @@ def normalize(self, width=3): """ Normalize the contrast levels of an entire Stack. - Args + Parameters ---------- width : integer Number of standard deviations from the mean to set as maximum intensity level. Returns - ---------- + ------- normalized : CommonStack object Copy of the input stack with intensities normalized @@ -106,24 +107,37 @@ def normalize(self, width=3): """ normalized = self.deepcopy() minvals = np.reshape( - (normalized.data.min(2).min(1)), [self.data.shape[0], 1, 1] + (normalized.data.min(2).min(1)), + [self.data.shape[0], 1, 1], ) normalized.data = normalized.data - minvals meanvals = np.reshape( - (normalized.data.mean(2).mean(1)), [self.data.shape[0], 1, 1] + (normalized.data.mean(2).mean(1)), + [self.data.shape[0], 1, 1], ) stdvals = np.reshape( - (normalized.data.std(2).std(1)), [self.data.shape[0], 1, 1] + (normalized.data.std(2).std(1)), + [self.data.shape[0], 1, 1], ) normalized.data = normalized.data / (meanvals + width * stdvals) return normalized - # noinspection PyTypeChecker - def savemovie(self, start, stop, axis="XY", fps=15, dpi=100, outfile=None, title="output.avi", clim=None, cmap="afmhot"): + def save_movie( + self, + start, + stop, + axis="XY", + fps=15, + dpi=100, + outfile="output.avi", + title="output.avi", + clim=None, + cmap="afmhot", + ): """ Save the Stack as an AVI movie file. - Args + Parameters ---------- start : integer Filename for output. If None, a UI will prompt for a filename. @@ -157,51 +171,67 @@ def savemovie(self, start, stop, axis="XY", fps=15, dpi=100, outfile=None, title ax.set_title(title) if axis == "XY": - im = ax.imshow(self.data[:, start, :], interpolation="none", cmap=cmap, clim=clim) + im = ax.imshow( + self.data[:, start, :], + interpolation="none", + cmap=cmap, + clim=clim, + ) elif axis == "XZ": - im = ax.imshow(self.data[start, :, :], interpolation="none", cmap=cmap, clim=clim) + im = ax.imshow( + self.data[start, :, :], + interpolation="none", + cmap=cmap, + clim=clim, + ) elif axis == "YZ": - im = ax.imshow(self.data[:, :, start], interpolation="none", cmap=cmap, clim=clim) + im = ax.imshow( + self.data[:, :, start], + interpolation="none", + cmap=cmap, + clim=clim, + ) else: - raise ValueError("Unknown axis!") + msg = "Unknown axis!" + raise ValueError(msg) fig.tight_layout() - def updatexy(n): + def updatexy(n) -> Iterable[Artist]: tmp = self.data[:, n, :] im.set_data(tmp) - return im + return [im] - def updatexz(n): + def updatexz(n) -> Iterable[Artist]: tmp = self.data[n, :, :] im.set_data(tmp) - return im + return [im] - def updateyz(n): + def updateyz(n) -> Iterable[Artist]: tmp = self.data[:, :, n] im.set_data(tmp) - return im + return [im] frames = np.arange(start, stop, 1) if axis == "XY": - ani = animation.FuncAnimation(fig, updatexy, frames) + ani = animation.FuncAnimation(fig=fig, func=updatexy, frames=frames) elif axis == "XZ": ani = animation.FuncAnimation(fig, updatexz, frames) elif axis == "YZ": ani = animation.FuncAnimation(fig, updateyz, frames) else: - raise ValueError("Axis not understood!") + msg = "Axis not understood!" + raise ValueError(msg) writer = animation.writers["ffmpeg"](fps=fps) ani.save(outfile, writer=writer, dpi=dpi) plt.close() - return - def save_raw(self, filename=None): + def save_raw(self, filename: Optional[Union[str, Path]] = None): """ Save Stack data as a .raw/.rpl file pair. - Args + Parameters ---------- filname : string (optional) Name of file to receive data. If not specified, the metadata will @@ -211,32 +241,30 @@ def save_raw(self, filename=None): datashape = self.data.shape if filename is None: - filename = self.metadata.General.title - else: - filename, ext = os.path.splitext(filename) - - filename = filename + "_%sx%sx%s_%s.rpl" % ( - str(datashape[0]), - str(datashape[1]), - str(datashape[2]), - self.data.dtype.name, + filename = Path(self.metadata.General.title) + elif isinstance(filename, str): + filename = Path(filename) + + filename = filename.parent / ( + filename.stem + f"_{datashape[0]}x" + f"{datashape[1]}x" + f"{datashape[2]}_" + f"{self.data.dtype.name}.rpl" ) self.save(filename) - return def stats(self): """Print basic stats about Stack data to terminal.""" - print("Mean: %.1f" % self.data.mean()) - print("Std: %.2f" % self.data.std()) - print("Max: %.1f" % self.data.max()) - print("Min: %.1f\n" % self.data.min()) - return + print(f"Mean: {self.data.mean():.1f}") # noqa: T201 + print(f"Std: {self.data.std():.2f}") # noqa: T201 + print(f"Max: {self.data.max():.1f}") # noqa: T201 + print(f"Min: {self.data.min():.1f}\n") # noqa: T201 def trans_stack(self, xshift=0.0, yshift=0.0, angle=0.0, interpolation="linear"): """ Transform the stack using the skimage Affine transform. - Args + Parameters ---------- xshift : float Number of pixels by which to shift in the X dimension @@ -250,12 +278,12 @@ def trans_stack(self, xshift=0.0, yshift=0.0, angle=0.0, interpolation="linear") are equivalent. Default is 'linear'. Returns - ---------- + ------- out : CommonStack object Transformed copy of the input stack Examples - ---------- + -------- >>> import etspy.datasets as ds >>> stack = ds.get_needle_data() >>> xshift = 10.0 @@ -268,14 +296,17 @@ def trans_stack(self, xshift=0.0, yshift=0.0, angle=0.0, interpolation="linear") """ transformed = self.deepcopy() theta = np.pi * angle / 180.0 - center_y, center_x = np.float32(np.array(transformed.data.shape[1:]) / 2) + center_y, center_x = np.array( + np.array(transformed.data.shape[1:]) / 2, + dtype=np.float32, + ) rot_mat = np.array( [ [np.cos(theta), -np.sin(theta), 0], [np.sin(theta), np.cos(theta), 0], [0, 0, 1], - ] + ], ) trans_mat = np.array([[1, 0, center_x], [0, 1, center_y], [0, 0, 1]]) @@ -284,7 +315,9 @@ def trans_stack(self, xshift=0.0, yshift=0.0, angle=0.0, interpolation="linear") rotation_mat = np.dot(np.dot(trans_mat, rot_mat), rev_mat) - shift = np.array([[1, 0, np.float32(xshift)], [0, 1, np.float32(-yshift)], [0, 0, 1]]) + shift = np.array( + [[1, 0, np.float32(xshift)], [0, 1, np.float32(-yshift)], [0, 0, 1]], + ) full_transform = np.dot(shift, rotation_mat) tform = transform.AffineTransform(full_transform) @@ -296,23 +329,30 @@ def trans_stack(self, xshift=0.0, yshift=0.0, angle=0.0, interpolation="linear") elif interpolation.lower() == "cubic": interpolation_order = 3 else: - raise ValueError( - "Interpolation method %s unknown. " - "Must be 'nearest', 'linear', or 'cubic'" % interpolation + msg = ( + f"Interpolation method '{interpolation}' unknown. " + "Must be 'nearest', 'linear', or 'cubic'" ) + raise ValueError(msg) - for i in range(0, self.data.shape[0]): + for i in range(self.data.shape[0]): transformed.data[i, :, :] = transform.warp( transformed.data[i, :, :], inverse_map=tform.inverse, order=interpolation_order, ) - transformed.metadata.Tomography.xshift = self.metadata.Tomography.xshift + xshift + transformed.metadata.Tomography.xshift = ( + self.metadata.Tomography.xshift + xshift + ) - transformed.metadata.Tomography.yshift = self.metadata.Tomography.yshift + yshift + transformed.metadata.Tomography.yshift = ( + self.metadata.Tomography.yshift + yshift + ) - transformed.metadata.Tomography.tiltaxis = self.metadata.Tomography.tiltaxis + angle + transformed.metadata.Tomography.tiltaxis = ( + self.metadata.Tomography.tiltaxis + angle + ) return transformed @@ -328,26 +368,30 @@ class TomoStack(CommonStack): def plot_sinos(self, *args, **kwargs): """Plot the TomoStack in sinogram orientation.""" - self.swap_axes(1, 0).swap_axes(1, 2).plot(navigator='slider', *args, **kwargs) - return + self.swap_axes(1, 0).swap_axes(1, 2).plot( + navigator="slider", + *args, # noqa: B026 + **kwargs, + ) def remove_projections(self, projections=None): """ Remove projections from tilt series. - Args + Parameters ---------- projections : list List of projection indices in integers to remove Returns - ---------- + ------- s_new : TomoStack Copy of self with indicated projections removed """ if projections is None: - raise ValueError('No projections provided') + msg = "No projections provided" + raise ValueError(msg) nprojs = len(projections) s_new = self.deepcopy() s_new.axes_manager[0].size -= nprojs @@ -362,13 +406,13 @@ def test_correlation(self, images=None): """ Test output of cross-correlation prior to alignment. - Args + Parameters ---------- images : list List of two numbers indicating which projections to cross-correlate Returns - ---------- + ------- fig : Matplotlib Figure Figure showing the results @@ -407,7 +451,7 @@ def align_other(self, other): This will include the spatial registration, tilt axis, and tilt axis shift if they have been previously calculated. - Args + Parameters ---------- other : TomoStack object The tilt series which is to be aligned using the previously @@ -415,19 +459,35 @@ def align_other(self, other): the same size as that in self.data Returns - ---------- + ------- out : TomoStack object The result of applying the alignment to other """ # Check if any transformations have been applied to the current stack no_shifts = np.all(self.metadata.Tomography.shifts == 0) - no_xshift = any([self.metadata.Tomography.xshift is None, self.metadata.Tomography.xshift == 0.0,]) - no_yshift = any([self.metadata.Tomography.xshift is None, self.metadata.Tomography.xshift == 0.0,]) - no_rotation = any([self.metadata.Tomography.tiltaxis is None, self.metadata.Tomography.tiltaxis == 0.0,]) + no_xshift = any( + [ + self.metadata.Tomography.xshift is None, + self.metadata.Tomography.xshift == 0.0, + ], + ) + no_yshift = any( + [ + self.metadata.Tomography.xshift is None, + self.metadata.Tomography.xshift == 0.0, + ], + ) + no_rotation = any( + [ + self.metadata.Tomography.tiltaxis is None, + self.metadata.Tomography.tiltaxis == 0.0, + ], + ) if all([no_shifts, no_xshift, no_yshift, no_rotation]): - raise ValueError("No transformations have been applied to this stack") + msg = "No transformations have been applied to this stack" + raise ValueError(msg) out = align.align_to_other(self, other) @@ -437,7 +497,7 @@ def filter(self, method="median", size=5, taper=0.1): """ Apply one of several image filters to an entire TomoStack. - Args + Parameters ---------- method : string Type of filter to apply. Must be 'median', 'bpf', 'both', or 'sobel'. @@ -447,7 +507,7 @@ def filter(self, method="median", size=5, taper=0.1): Fraction of image size to pad to the mean. Returns - ---------- + ------- filtered : TomoStack object Filtered copy of the input stack @@ -462,13 +522,13 @@ def filter(self, method="median", size=5, taper=0.1): if method == "median": filtered.data = ndimage.median_filter(filtered.data, size=(1, size, size)) elif method == "sobel": - for i in range(0, filtered.data.shape[0]): + for i in range(filtered.data.shape[0]): dx = ndimage.sobel(filtered.data[i, :, :], 0) dy = ndimage.sobel(filtered.data[i, :, :], 1) filtered.data[i, :, :] = np.hypot(dx, dy) elif method == "both": filtered.data = ndimage.median_filter(filtered.data, size=(1, size, size)) - for i in range(0, filtered.data.shape[0]): + for i in range(filtered.data.shape[0]): dx = ndimage.sobel(filtered.data[i, :, :], 0) dy = ndimage.sobel(filtered.data[i, :, :], 1) filtered.data[i, :, :] = np.hypot(dx, dy) @@ -479,7 +539,7 @@ def filter(self, method="median", size=5, taper=0.1): hp_sigma = 1.5 [nprojs, rows, cols] = self.data.shape - F = np.fft.fftshift(np.fft.fft2(self.data)) + fft = np.fft.fftshift(np.fft.fft2(self.data)) x = (np.arange(0, cols) - np.fix(cols / 2)) / cols y = (np.arange(0, rows) - np.fix(rows / 2)) / rows @@ -489,29 +549,40 @@ def filter(self, method="median", size=5, taper=0.1): hpf = 1 - (1 / (1.0 + (r / hp_freq) ** (2 * hp_sigma))) bpf = lpf * hpf - F_filtered = F * bpf + fft_filtered = fft * bpf - filtered.data = np.fft.ifft2(np.fft.ifftshift(F_filtered)).real + filtered.data = np.fft.ifft2(np.fft.ifftshift(fft_filtered)).real h = np.hamming(rows) ham2d = np.sqrt(np.outer(h, h)) filtered.data = filtered.data * ham2d else: - raise ValueError("Unknown filter method. Must be 'median', 'sobel', 'both', or 'bpf'" - ) + msg = ( + f"Unknown filter method '{method}'. " + "Must be 'median', 'sobel', 'both', or 'bpf'" + ) + raise ValueError(msg) if taper: - taper_size = np.int32(np.array(taper) * self.data.shape[1:]) + taper_size = np.array(np.array(taper) * self.data.shape[1:], dtype=np.int32) filtered.data = np.pad( filtered.data, - [(0, 0), - (taper_size[0], taper_size[0]), - (taper_size[1], taper_size[1]), - ], + [ + (0, 0), + (taper_size[0], taper_size[0]), + (taper_size[1], taper_size[1]), + ], mode="constant", ) return filtered - def stack_register(self, method="PC", start=None, show_progressbar=False, crop=False, **kwargs): + def stack_register( + self, + method="PC", + start=None, + show_progressbar=False, + crop=False, + **kwargs, + ): """ Register stack spatially. @@ -519,7 +590,7 @@ def stack_register(self, method="PC", start=None, show_progressbar=False, crop=F mass ('COM'), or combined center of mass and common line methods. See docstring for etspy.align.align_stack for details. - Args + Parameters ---------- method : string Algorithm to use for registration calculation. Must be either @@ -553,7 +624,7 @@ def stack_register(self, method="PC", start=None, show_progressbar=False, crop=F alignment to perform. Default is 8. Returns - ---------- + ------- out : TomoStack object Spatially registered copy of the input stack @@ -584,10 +655,11 @@ def stack_register(self, method="PC", start=None, show_progressbar=False, crop=F if method in ["pc", "com", "stackreg", "com-cl"]: out = align.align_stack(self, method, start, show_progressbar, **kwargs) else: - raise ValueError( - "Unknown registration method: " - "%s. Must be PC, StackReg, or COM" % method + msg = ( + f"Unknown registration method: '{method}'. " + "Must be 'PC', 'StackReg', or 'COM'" ) + raise ValueError(msg) if crop: out = align.shift_crop(out) @@ -613,28 +685,28 @@ def tilt_align(self, method, **kwargs): rotation of the stack. Optionally, the global shift of the tilt axis can also be calculated by minimization of the sum of the reconstruction. - Args + Parameters ---------- method : string Algorithm to use for registration alignment. Must be either 'CoM' or 'MaxImage'. **kwargs: Additional keyword arguments. Possible keys include: - - nslices (int): Number of slices to use for the center of mass tilt alignment. - - locs (list): Location along tilt axis to use for center of mass tilt alignment. - - limit (integer or float): Maximum rotation angle to use for MaxImage calculation - - delta (float): Angular increment in degrees for MaxImage calculation - - plot_results (bool): if True, plot results of Hough line analysis - - also_shift (bool): if True, also calculate global shift of tilt axis - - shift_limit (int): Search range for global shift of tilt axis + - nslices (int): Number of slices to use for center of mass tilt alignment. + - locs (list): Location along tilt axis for center of mass tilt alignment. + - limit (integer or float): Maximum rotation angle for MaxImage calculation + - delta (float): Angular increment in degrees for MaxImage calculation + - plot_results (bool): if True, plot results of Hough line analysis + - also_shift (bool): if True, also calculate global shift of tilt axis + - shift_limit (int): Search range for global shift of tilt axis Returns - ---------- + ------- out : TomoStack object Copy of the input stack rotated by calculated angle Examples - ---------- + -------- Align tilt axis using the center of mass (CoM) method >>> import etspy.datasets as ds >>> stack = ds.get_needle_data() @@ -653,44 +725,51 @@ def tilt_align(self, method, **kwargs): method = method.lower() if method == "com": - nslices = kwargs.get('nslices', 20) - locs = kwargs.get('locs', None) + nslices = kwargs.get("nslices", 20) + locs = kwargs.get("locs", None) out = align.tilt_com(self, locs, nslices) elif method == "maximage": - limit = kwargs.get('limit', 10) - delta = kwargs.get('delta', 0.3) - plot_results = kwargs.get('plot_results', False) - also_shift = kwargs.get('also_shift', False) - shift_limit = kwargs.get('shift_limit', 20) - out = align.tilt_maximage(self, limit, delta, plot_results, also_shift, shift_limit) - else: - raise ValueError( - "Invalid alignment method: %s." - "Must be 'CoM' or 'MaxImage'" % method + limit = kwargs.get("limit", 10) + delta = kwargs.get("delta", 0.3) + plot_results = kwargs.get("plot_results", False) + also_shift = kwargs.get("also_shift", False) + shift_limit = kwargs.get("shift_limit", 20) + out = align.tilt_maximage( + self, + limit, + delta, + plot_results, + also_shift, + shift_limit, ) + else: + msg = f"Invalid alignment method: '{method}'. Must be 'CoM' or 'MaxImage'" + raise ValueError(msg) return out def reconstruct( self, method="FBP", - iterations=None, + iterations=5, constrain=False, thresh=0, cuda=None, thickness=None, show_progressbar=True, - **kwargs + p=0.99, + **kwargs, ): """ Reconstruct a TomoStack series using one of the available methods. - Args + Parameters ---------- method : string - Reconstruction algorithm to use. Must be'FBP' (default), 'SIRT', 'SART', or 'DART' + Reconstruction algorithm to use. Must be'FBP' (default), + 'SIRT', 'SART', or 'DART' iterations : integer Number of iterations for the SIRT reconstruction (for astraSIRT - and astraSIRT_GPU, methods only) + and astraSIRT_GPU, methods only) (default: 5) constrain : boolean If True, output reconstruction is constrained above value given by 'thresh' @@ -702,46 +781,53 @@ def reconstruct( Size of the output volume (in pixels) in the projection direction. show_progressbar : bool If True, show a progress bar for the reconstruction. Default is True. + p : float + Probability for setting free pixels in DART reconstruction (only used + if the reconstruction method is DART, default: 0.99) **kwargs: Additional keyword arguments. Possible keys include: - ncores (int): Number of cores to use for multithreaded reconstructions. - - sino_filter (str): Filter to apply for filtered backprojection. Default is shepp-logan. + - sino_filter (str): Filter for filtered backprojection. Default is shepp-logan. - dart_iterations (int): Number of iterations to employ for DART reconstruction. Returns - ---------- + ------- out : TomoStack object TomoStack containing the reconstructed volume Examples - ---------- + -------- Filtered backprojection (FBP) reconstruction >>> import etspy.datasets as ds - >>> stack = ds.get_needle_data(True) + >>> stack = ds.get_needle_data(aligned=True) >>> slices = stack.isig[:, 120:121].deepcopy() >>> rec = slices.reconstruct('FBP', cuda=False, show_progressbar=False) Simultaneous iterative reconstruction technique (SIRT) reconstruction >>> import etspy.datasets as ds - >>> stack = ds.get_needle_data(True) + >>> stack = ds.get_needle_data(aligned=True) >>> slices = stack.isig[:, 120:121].deepcopy() - >>> rec = slices.reconstruct('SIRT',iterations=5, cuda=False, show_progressbar=False) + >>> rec = slices.reconstruct('SIRT',iterations=5, + ... cuda=False, show_progressbar=False) Simultaneous iterative reconstruction technique (SIRT) reconstruction with positivity constraint >>> import etspy.datasets as ds - >>> stack = ds.get_needle_data(True) + >>> stack = ds.get_needle_data(aligned=True) >>> slices = stack.isig[:, 120:121].deepcopy() >>> iterations = 5 >>> constrain = True >>> thresh = 0 - >>> rec = slices.reconstruct('SIRT',iterations, constrain, thresh, cuda=False, show_progressbar=False) + >>> rec = slices.reconstruct('SIRT', iterations, constrain, thresh, + ... cuda=False, show_progressbar=False) Discreate algebraice reconstruction technique (DART) reconstruction >>> import etspy.datasets as ds - >>> stack = ds.get_needle_data(True) + >>> stack = ds.get_needle_data(aligned=True) >>> slices = stack.isig[:, 120:121].deepcopy() >>> gray_levels = [0., slices.data.max()/2, slices.data.max()] - >>> rec = slices.reconstruct('DART',iterations=5, cuda=False, gray_levels=gray_levels, p=0.99, dart_iterations=5, show_progressbar=False) + >>> rec = slices.reconstruct('DART', iterations=5, cuda=False, + ... gray_levels=gray_levels, p=0.99, + ... dart_iterations=5, show_progressbar=False) """ if method.lower() not in [ @@ -750,7 +836,8 @@ def reconstruct( "sart", "dart", ]: - raise ValueError("Unknown reconstruction algorithm: %s" % method) + msg = f"Unknown reconstruction algorithm: '{method}'" + raise ValueError(msg) if cuda is None: if astra.use_cuda(): logger.info("CUDA detected with Astra") @@ -759,60 +846,65 @@ def reconstruct( cuda = False logger.info("CUDA not detected with Astra") - ncores = kwargs.get('ncores', None) - sino_filter = kwargs.get('sino_filter', 'shepp-logan') - if method.lower() == 'dart': - dart_iterations = kwargs.get('dart_iterations', 5) - p = kwargs.get('p', 0.99) - gray_levels = kwargs.get('gray_levels', None) + ncores = kwargs.get("ncores", None) + sino_filter = kwargs.get("sino_filter", "shepp-logan") + if method.lower() == "dart": + dart_iterations = kwargs.get("dart_iterations", 5) + gray_levels = kwargs.get("gray_levels", None) if not isinstance(gray_levels, (np.ndarray, list)): - raise ValueError("Unknown type (%s) for gray_levels" % type(gray_levels)) - elif gray_levels is None: - raise ValueError("gray_levels must be provided for DART") + msg = f"Unknown type ({type(gray_levels)}) for gray_levels" + raise ValueError(msg) + if gray_levels is None: + msg = "gray_levels must be provided for DART" + raise ValueError(msg) else: dart_iterations = None - p = None gray_levels = None rec = recon.run( - self, - method, - iterations, - constrain, - thresh, - cuda, - thickness, - ncores, - sino_filter, - gray_levels, - dart_iterations, - p, - show_progressbar + stack=self, + method=method, + niterations=iterations, + constrain=constrain, + thresh=thresh, + cuda=cuda, + thickness=thickness, + ncores=ncores, + bp_filter=sino_filter, + gray_levels=gray_levels, + dart_iterations=dart_iterations, + p=p, + show_progressbar=show_progressbar, ) axes_dict = self.axes_manager.as_dictionary() - rec_axes_dict = [axes_dict['axis-2'], dict(axes_dict['axis-1']), axes_dict['axis-1']] - rec_axes_dict[1]['name'] = 'z' - rec_axes_dict[1]['size'] = rec.shape[1] + rec_axes_dict = [ + axes_dict["axis-2"], + dict(axes_dict["axis-1"]), + axes_dict["axis-1"], + ] + rec_axes_dict[1]["name"] = "z" + rec_axes_dict[1]["size"] = rec.shape[1] rec = RecStack(rec, axes=rec_axes_dict) return rec - def test_align(self, - tilt_shift=0.0, - tilt_rotation=0.0, - slices=None, - thickness=None, - method="FBP", - iterations=50, - constrain=True, - cuda=None, - thresh=0, - vmin_std=0.1, - vmax_std=10, - ): + def test_align( # noqa: PLR0913 + self, + tilt_shift=0.0, + tilt_rotation=0.0, + slices=None, + thickness=None, + method="FBP", + iterations=50, + constrain=True, + cuda=None, + thresh=0, + vmin_std=0.1, + vmax_std=10, + ): """ Reconstruct three slices from the input data for visual inspection. - Args + Parameters ---------- xshift : float Number of pixels by which to shift the input data. @@ -832,11 +924,12 @@ def test_align(self, thresh : float Minimum value for reconstruction vmin_std, vmax_std : float - Number of standard deviations from mean to use for scaling the displayed slices + Number of standard deviations from mean to use for + scaling the displayed slices """ if slices is None: - mid = np.int32(self.data.shape[2] / 2) - slices = np.int32([mid / 2, mid, mid + mid / 2]) + mid = np.array(self.data.shape[2] / 2, dtype=np.int32) + slices = np.array([mid / 2, mid, mid + mid / 2], dtype=np.int32) if (tilt_shift != 0.0) or (tilt_rotation != 0.0): shifted = self.trans_stack(xshift=0, yshift=tilt_shift, angle=tilt_rotation) @@ -859,7 +952,7 @@ def test_align(self, thickness=thickness, cuda=cuda, thresh=thresh, - show_progressbar=False + show_progressbar=False, ) if "ipympl" in mpl.get_backend().lower(): @@ -874,24 +967,23 @@ def test_align(self, maxvals = rec.data.mean((1, 2)) + vmax_std * rec.data.std((1, 2)) ax1.imshow(rec.data[0, :, :], cmap="afmhot", vmin=minvals[0], vmax=maxvals[0]) - ax1.set_title("Slice %s" % str(slices[0])) + ax1.set_title(f"Slice {slices[0]}") ax1.set_axis_off() ax2.imshow(rec.data[1, :, :], cmap="afmhot", vmin=minvals[1], vmax=maxvals[1]) - ax2.set_title("Slice %s" % str(slices[1])) + ax2.set_title(f"Slice {slices[1]}") ax2.set_axis_off() ax3.imshow(rec.data[2, :, :], cmap="afmhot", vmin=minvals[2], vmax=maxvals[2]) - ax3.set_title("Slice %s" % str(slices[2])) + ax3.set_title(f"Slice {slices[2]}") ax3.set_axis_off() fig.tight_layout() - return def set_tilts(self, start, increment): """ Calibrate the tilt axis of the image stack. - Args + Parameters ---------- start : float or integer Tilt angle of first image in stack @@ -914,16 +1006,15 @@ def set_tilts(self, start, increment): self.metadata.Tomography.set_item("xshift", 0) self.metadata.Tomography.set_item("yshift", 0) self.metadata.Tomography.set_item("shifts", None) - self.metadata.Tomography.set_item("cropped", False) + self.metadata.Tomography.set_item("cropped", value=False) else: self.metadata.Tomography.set_item("tilts", tilts) - return - def manual_align(self, nslice, xshift=0, yshift=0, display=False): + def manual_align(self, nslice, xshift=0, yshift=0, display=False): # noqa: PLR0915 """ Manually shift one portion of a stack with respect to the other. - Args + Parameters ---------- nslice : integer Slice position at which to implement shift @@ -957,25 +1048,24 @@ def manual_align(self, nslice, xshift=0, yshift=0, display=False): output.data = output.data[:, :yshift, :] output.data[0:nslice, :, :] = self.data[0:nslice, :yshift, :] output.data[nslice:, :, :] = self.data[nslice:, -yshift:, :] + elif (xshift > 0) and (yshift > 0): + output.data = output.data[:, :-yshift, :-xshift] + output.data[0:nslice, :, :] = self.data[0:nslice, yshift:, xshift:] + output.data[nslice:, :, :] = self.data[nslice:, :-yshift, :-xshift] + elif (xshift > 0) and (yshift < 0): + output.data = output.data[:, :yshift, :-xshift] + output.data[0:nslice, :, :] = self.data[0:nslice, :yshift, xshift:] + output.data[nslice:, :, :] = self.data[nslice:, -yshift:, :-xshift] + elif (xshift < 0) and (yshift > 0): + output.data = output.data[:, :-yshift, :xshift] + output.data[0:nslice, :, :] = self.data[0:nslice, yshift:, :xshift] + output.data[nslice:, :, :] = self.data[nslice:, :-yshift, -xshift:] + elif (xshift < 0) and (yshift < 0): + output.data = output.data[:, :yshift, :xshift] + output.data[0:nslice, :, :] = self.data[0:nslice, :yshift, :xshift] + output.data[nslice:, :, :] = self.data[nslice:, -yshift:, -xshift:] else: - if (xshift > 0) and (yshift > 0): - output.data = output.data[:, :-yshift, :-xshift] - output.data[0:nslice, :, :] = self.data[0:nslice, yshift:, xshift:] - output.data[nslice:, :, :] = self.data[nslice:, :-yshift, :-xshift] - elif (xshift > 0) and (yshift < 0): - output.data = output.data[:, :yshift, :-xshift] - output.data[0:nslice, :, :] = self.data[0:nslice, :yshift, xshift:] - output.data[nslice:, :, :] = self.data[nslice:, -yshift:, :-xshift] - elif (xshift < 0) and (yshift > 0): - output.data = output.data[:, :-yshift, :xshift] - output.data[0:nslice, :, :] = self.data[0:nslice, yshift:, :xshift] - output.data[nslice:, :, :] = self.data[nslice:, :-yshift, -xshift:] - elif (xshift < 0) and (yshift < 0): - output.data = output.data[:, :yshift, :xshift] - output.data[0:nslice, :, :] = self.data[0:nslice, :yshift, :xshift] - output.data[nslice:, :, :] = self.data[nslice:, -yshift:, -xshift:] - else: - pass + pass if display: old_im1 = self.data[nslice - 1, :, :] old_im2 = self.data[nslice, :, :] @@ -1001,7 +1091,15 @@ def manual_align(self, nslice, xshift=0, yshift=0, display=False): return output - def recon_error(self, nslice=None, algorithm='SIRT', iterations=50, constrain=True, cuda=None, thresh=0): + def recon_error( + self, + nslice=None, + algorithm="SIRT", + iterations=50, + constrain=True, + cuda=None, + thresh=0, + ): """ Determine the optimum number of iterations for reconstruction. @@ -1009,7 +1107,7 @@ def recon_error(self, nslice=None, algorithm='SIRT', iterations=50, constrain=Tr at each iteration and terminates when the change between iterations is below tolerance. - Args + Parameters ---------- algorithm : str Reconstruction algorithm use. Must be 'SIRT' (default) or 'SART'. @@ -1025,7 +1123,7 @@ def recon_error(self, nslice=None, algorithm='SIRT', iterations=50, constrain=Tr Value above which to constrain the reconstructed data Returns - ---------- + ------- rec_stack : Hyperspy Signal2D Signal containing the SIRT reconstruction at each iteration for visual inspection. @@ -1034,14 +1132,15 @@ def recon_error(self, nslice=None, algorithm='SIRT', iterations=50, constrain=Tr reconstruction and the input sinogram at each iteration Examples - ---------- + -------- >>> import etspy.datasets as ds - >>> stack = ds.get_needle_data(True) + >>> stack = ds.get_needle_data(aligned=True) >>> rec_stack, error = stack.recon_error(iterations=5) """ if self.metadata.Tomography.tilts is None: - raise ValueError("Tilt angles not defined") + msg = "Tilt angles not defined" + raise ValueError(msg) if not nslice: nslice = int(self.data.shape[2] / 2) @@ -1091,20 +1190,28 @@ class RecStack(CommonStack): CommonStack class """ - def plot_slices(self, xslice=None, yslice=None, zslice=None, vmin_std=0.1, vmax_std=5): + def plot_slices( + self, + xslice=None, + yslice=None, + zslice=None, + vmin_std=0.1, + vmax_std=5, + ): """ Plot slices along all three axes of a reconstruction stack. - Args + Parameters ---------- yslice, zslice, xslice : int Indices of slices to plot vmin_std, vmax_std : float - Number of standard deviations from mean to use for scaling the displayed slices + Number of standard deviations from mean to use for + scaling the displayed slices Returns - ---------- + ------- fig : Matplotlib Figure """ @@ -1122,23 +1229,27 @@ def plot_slices(self, xslice=None, yslice=None, zslice=None, vmin_std=0.1, vmax_ else: fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(12, 4)) - slices = [self.data[xslice, :, :], self.data[:, zslice, :], self.data[:, :, yslice]] + slices = [ + self.data[xslice, :, :], + self.data[:, zslice, :], + self.data[:, :, yslice], + ] minvals = [slices[i].mean() - vmin_std * slices[i].std() for i in range(3)] minvals = [x if x >= 0 else 0 for x in minvals] maxvals = [slices[i].mean() + vmax_std * slices[i].std() for i in range(3)] ax1.imshow(slices[0], cmap="afmhot", vmin=minvals[0], vmax=maxvals[0]) - ax1.set_title("Z-Y Slice %s" % str(xslice)) + ax1.set_title(f"Z-Y Slice {xslice}") ax1.set_ylabel("Z") ax1.set_xlabel("Y") ax2.imshow(slices[1], cmap="afmhot", vmin=minvals[1], vmax=maxvals[1]) - ax2.set_title("Y-X Slice %s" % str(zslice)) + ax2.set_title(f"Y-X Slice {zslice}") ax2.set_ylabel("Y") ax2.set_xlabel("X") ax3.imshow(slices[2].T, cmap="afmhot", vmin=minvals[2], vmax=maxvals[2]) - ax3.set_title("Z-X Slice %s" % str(yslice)) + ax3.set_title(f"Z-X Slice {yslice}") ax3.set_ylabel("Z") ax3.set_xlabel("X") fig.tight_layout() diff --git a/etspy/datasets.py b/etspy/datasets.py index cadeb3e..ba199cf 100644 --- a/etspy/datasets.py +++ b/etspy/datasets.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -# -# This file is part of ETSpy - """ Test dataset handling module for ETSpy package. @@ -9,76 +5,70 @@ """ import etspy.api as etspy -from etspy.simulation import misalign_stack, add_noise -import os - -etspy_path = os.path.dirname(etspy.__file__) +from etspy.api import etspy_path +from etspy.simulation import add_noise, misalign_stack -def get_needle_data(aligned=False): +def get_needle_data(aligned: bool = False): """ - Retrieve experimental tilt series of needle-shaped specimen. + Load an experimental tilt series of needle-shaped specimen. Returns - ---------- + ------- needle : TomoStack object TomoStack containing the simulated catalyst tilt series """ if aligned: - filename = os.path.join( - etspy_path, "tests", "test_data", "HAADF_Aligned.hdf5" - ) + filename = etspy_path / "tests" / "test_data" / "HAADF_Aligned.hdf5" needle = etspy.load(filename) else: - filename = os.path.join(etspy_path, "tests", "test_data", "HAADF.mrc") + filename = etspy_path / "tests" / "test_data" / "HAADF.mrc" needle = etspy.load(filename) return needle def get_catalyst_data( - misalign=False, - minshift=-5, - maxshift=5, - tiltshift=0, - tiltrotate=0, - yonly=False, - noise=False, - noise_factor=0.2, -): + misalign: bool = False, + minshift: int = -5, + maxshift: int = 5, + tiltshift: int = 0, + tiltrotate: int = 0, + yonly: bool = False, + noise: bool = False, + noise_factor: float = 0.2, +) -> etspy.TomoStack: """ - Retrieve model catalyst tilt series. + Load a model-simulated catalyst tilt series. Arguments ---------- - misalign : bool + misalign If True, apply random shifts to each projection to simulated drift - minshift : float + minshift Lower bound for random shifts - maxshift : float + maxshift Upper bound for random shifts - tiltshift : float + tiltshift Number of pixels by which to shift entire tilt series. Simulates offset tilt axis. - rotate : float + rotate Angle by which to rotate entire tilt series. Simulates non-vertical tilt axis. - xonly : bool + xonly If True, shifts are only applied along the X-axis - noise : bool + noise If True, add Gaussian noise to the stack - noise_factor : float + noise_factor Percentage noise to be added. Must be between 0 and 1. Returns - ---------- - catalyst : TomoStack object + ------- + catalyst : :py:class:`~etspy.TomoStack` TomoStack containing the simulated catalyst tilt series """ - filename = os.path.join( - etspy_path, "tests", "test_data", "Catalyst3DModel_TiltSeries180.hdf5" - ) + filename = etspy_path / "tests" / "test_data" / "Catalyst3DModel_TiltSeries180.hdf5" catalyst = etspy.load(filename) if misalign: catalyst = misalign_stack( diff --git a/etspy/io.py b/etspy/io.py index 9a76cc4..d7a1f68 100644 --- a/etspy/io.py +++ b/etspy/io.py @@ -1,42 +1,74 @@ -# -*- coding: utf-8 -*- -# -# This file is part of ETSpy - """ Data input/output module for ETSpy package. @author: Andrew Herzing """ +import logging +from pathlib import Path +from typing import Any, List, Optional, Tuple, Union, cast + import numpy as np -import os -import hyperspy.api as hspy +from hyperspy._signals.signal2d import ( + Signal2D, # import from _signals for type-checking +) +from hyperspy.io import ( + load as hs_load, # import load function directly for better type-checking +) +from hyperspy.misc.utils import DictionaryTreeBrowser +from hyperspy.misc.utils import ( + stack as hs_stack, # import stack function directly for better type-checking +) + from etspy.base import TomoStack -import logging -from hyperspy.signals import Signal2D logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) +PathLike = Union[str, Path] + +# declare known file types for later use +hspy_file_types = [".hdf5", ".h5", ".hspy"] +mrc_file_types = [".mrc", ".ali", ".rec"] +dm_file_types = [".dm3", ".dm4"] +known_file_types = hspy_file_types + mrc_file_types + dm_file_types + + +class MismatchedTiltError(ValueError): + """Error for when number of tilts in signal does not match tilt dimension.""" + + def __init__(self, num_tilts, tilt_dimension): + """Create a MismatchedTiltError.""" + super().__init__( + f"Number of tilts ({num_tilts}) does not match " + f"the tilt dimension of the data array ({tilt_dimension})", + ) -def create_stack(stack, tilts=None): - """Create a TomoStack. +def create_stack( + stack: Union[Signal2D, np.ndarray], + tilts: Optional[np.ndarray] = None, +) -> TomoStack: + """ + Create a TomoStack from existing in-memory tilt series data. - Args: - stack : Hyperspy Signal2D or NumPy array - Tilt series data (ntilts, ny, nx) - tilts : NumPy array - Array defining the tilt angles + Parameters + ---------- + stack + Tilt series data (ntilts, ny, nx) + tilts + An (optional) array defining the tilt angles - Returns: - stack : TomoStack + Returns + ------- + stack: TomoStack + A TomoStack instance containing the provided data """ - if type(stack) is Signal2D: + if isinstance(stack, Signal2D): ntilts = stack.data.shape[0] if tilts is None: tilts = np.zeros(ntilts) if ntilts != tilts.shape[0]: - raise ValueError("Number of tilts (%s) does not match the tilt dimension of the data array (%s)" % (tilts.shape[0], ntilts)) + raise MismatchedTiltError(tilts.shape[0], ntilts) if stack.metadata.has_item("Tomography"): pass @@ -44,26 +76,46 @@ def create_stack(stack, tilts=None): ntilts = stack.data.shape[0] if tilts is None: tilts = np.zeros(ntilts) - tomo_metadata = {"cropped": False, "shifts": np.zeros([ntilts, 2]), "tiltaxis": 0, - "tilts": tilts, "xshift": 0, "yshift": 0} + tomo_metadata = { + "cropped": False, + "shifts": np.zeros([ntilts, 2]), + "tiltaxis": 0, + "tilts": tilts, + "xshift": 0, + "yshift": 0, + } stack.metadata.add_node("Tomography") - stack.metadata.Tomography.add_dictionary(tomo_metadata) + # cast for type-checking: + tomo_meta_node = cast(DictionaryTreeBrowser, stack.metadata.Tomography) + tomo_meta_node.add_dictionary(tomo_metadata) axes_list = [x for _, x in sorted(stack.axes_manager.as_dictionary().items())] metadata_dict = stack.metadata.as_dictionary() original_metadata_dict = stack.original_metadata.as_dictionary() - stack = TomoStack(stack, axes=axes_list, metadata=metadata_dict, original_metadata=original_metadata_dict) - elif type(stack) is np.ndarray: + stack = TomoStack( + stack, + axes=axes_list, + metadata=metadata_dict, + original_metadata=original_metadata_dict, + ) + elif isinstance(stack, np.ndarray): ntilts = stack.shape[0] if tilts is None: tilts = np.zeros(ntilts) if ntilts != tilts.shape[0]: - raise ValueError("Number of tilts (%s) does not match the tilt dimension of the data array (%s)" % (tilts.shape[0], ntilts)) - - tomo_metadata = {"cropped": False, "shifts": np.zeros([ntilts, 2]), "tiltaxis": 0, - "tilts": tilts, "xshift": 0, "yshift": 0} + raise MismatchedTiltError(tilts.shape[0], ntilts) + + tomo_metadata = { + "cropped": False, + "shifts": np.zeros([ntilts, 2]), + "tiltaxis": 0, + "tilts": tilts, + "xshift": 0, + "yshift": 0, + } stack = TomoStack(stack) stack.metadata.add_node("Tomography") stack.metadata.Tomography.add_dictionary(tomo_metadata) + stack = cast(TomoStack, stack) # type-checking cast stack.axes_manager[0].name = "Tilt" stack.axes_manager[0].units = "degrees" stack.axes_manager[1].name = "x" @@ -73,94 +125,120 @@ def create_stack(stack, tilts=None): return stack -def get_mrc_tilts(stack, filename): +def get_mrc_tilts( + stack: Union[Signal2D, TomoStack], + filename: PathLike, +) -> Optional[np.ndarray]: """Extract tilts from an MRC file. Parameters ---------- - stack : Signal2D or TomoStack - - filename : string - Name of MRC file + stack + A HyperSpy or TomoStack signal + filename + Name of MRC file from which to extract tilts Returns ------- - tilts : NumPy array or None - Tilt angles extracted from MRC file + tilts: Optional[np_types.ArrayLike] + Tilt angles extracted from MRC file (or ``None`` if not present) """ - tiltfile = os.path.splitext(filename)[0] + ".rawtlt" + if isinstance(filename, str): + filename = Path(filename) + tiltfile = filename.with_suffix(".rawtlt") + tilts = None if stack.original_metadata.has_item("fei_header"): - if stack.original_metadata.fei_header.has_item("a_tilt"): - tilts = stack.original_metadata.fei_header["a_tilt"][ - 0: stack.data.shape[0] - ] + fei_header = cast(DictionaryTreeBrowser, stack.original_metadata.fei_header) + if fei_header.has_item("a_tilt"): + tilts = fei_header["a_tilt"][0 : stack.data.shape[0]] logger.info("Tilts found in MRC file header") elif stack.original_metadata.has_item("std_header"): logger.info("SerialEM generated MRC file detected") ext_header = parse_mrc_header(filename)["ext_header"] tilts = ext_header[np.arange(0, int(ext_header.shape[0]), 7)][ - 0: stack.data.shape[0] + 0 : stack.data.shape[0] ] tilts = tilts / 100 - elif os.path.isfile(tiltfile): + elif tiltfile.is_file(): tilts = np.loadtxt(tiltfile) logger.info(".rawtlt file detected.") if len(tilts) == stack.data.shape[0]: logger.info("Tilts loaded from .rawtlt file") else: - raise ValueError("Number of tilts in .rawtlt file inconsistent with data shape") - else: - tilts = None + msg = "Number of tilts in .rawtlt file inconsistent with data shape" + raise ValueError(msg) return tilts -def get_dm_tilts(s): +def get_dm_tilts(s: Union[Signal2D, TomoStack]) -> np.ndarray: """Extract tilts from DM tags. Parameters ---------- - s : Singal2D or TomoStack + s + A HyperSpy or ETSpy signal containing DigitalMigrograph + metadata tags Returns ------- - tilts : NumPy array + tilts: np.ndarray Tilt angles extracted from the DM tags """ maxtilt = s.original_metadata["ImageList"]["TagGroup0"]["ImageTags"]["Tomography"][ - "Tomography_setup"]["Tilt_angles"]["Maximum_tilt_angle_deg"] + "Tomography_setup" + ]["Tilt_angles"]["Maximum_tilt_angle_deg"] mintilt = s.original_metadata["ImageList"]["TagGroup0"]["ImageTags"]["Tomography"][ - "Tomography_setup"]["Tilt_angles"]["Minimum_tilt_angle_deg"] + "Tomography_setup" + ]["Tilt_angles"]["Minimum_tilt_angle_deg"] tiltstep = s.original_metadata["ImageList"]["TagGroup0"]["ImageTags"]["Tomography"][ - "Tomography_setup"]["Tilt_angles"]["Tilt_angle_step_deg"] + "Tomography_setup" + ]["Tilt_angles"]["Tilt_angle_step_deg"] - tilts = np.arange(mintilt, maxtilt + tiltstep, tiltstep) - return tilts + return np.arange(mintilt, maxtilt + tiltstep, tiltstep) -def parse_mdoc(mdoc_file, series=False): +def parse_mdoc( + mdoc_file: PathLike, + series: bool = False, +) -> Tuple[dict, Union[np.ndarray, float]]: """Parse experimental parameters from a SerialEM MDOC file. Parameters ---------- - mdoc_file : str - Name of MDOC file - series : bool, optional - If True, the MDOC files originated from a multiscan SerialEM acquisition. - If False, the files originated from a single scan SerialEM acquisition. + mdoc_file + Name of a SerialEM MDOC file + series + If ``True``, the MDOC files originated from a multiscan SerialEM acquisition. + If ``False``, the files originated from a single scan SerialEM acquisition. Returns ------- - _type_ - _description_ + metadata : dict + A dictionary containing the metadata read from the MDOC file + tilt : Union[np.ndarray, float] + If ``series`` is true, tilt will be a single float value, otherwise + it will be an ndarray containing multiple tilt values. """ - keys = ["PixelSpacing", "Voltage", "ImageFile", "Image Size", "DataMode", - "Magnification", "ExposureTime", "SpotSize", "Defocus"] + keys = [ + "PixelSpacing", + "Voltage", + "ImageFile", + "Image Size", + "DataMode", + "Magnification", + "ExposureTime", + "SpotSize", + "Defocus", + ] metadata = {} - with open(mdoc_file, "r") as f: + tilt = np.array([]) + if isinstance(mdoc_file, str): + mdoc_file = Path(mdoc_file) + with mdoc_file.open("r") as f: lines = f.readlines() - for i in range(0, 35): + for i in range(35): for k in keys: if k in lines[i]: if k == "ImageFile": @@ -168,8 +246,8 @@ def parse_mdoc(mdoc_file, series=False): else: metadata[k] = float(lines[i].split("=")[1].strip()) if series: - for i in range(0, 35): - if 'TiltAngle' in lines[i]: + for i in range(35): + if "TiltAngle" in lines[i]: tilt = float(lines[i].split("=")[1].strip()) else: tilt = [] @@ -180,21 +258,21 @@ def parse_mdoc(mdoc_file, series=False): return metadata, tilt -def load_serialem(mrcfile, mdocfile): +def load_serialem(mrcfile: PathLike, mdocfile: PathLike) -> TomoStack: """ Load a multi-frame series collected by SerialEM. Parameters ---------- - mrc_files : str - MRC file containing tilt series data. + mrc_files + Path to MRC file containing tilt series data. - mdoc_files : str - SerialEM metadata file for tilt series data. + mdoc_files + Path to SerialEM metadata file for tilt series data. Returns - ---------- - stack : TomoStack object + ------- + stack : TomoStack Tilt series """ @@ -202,8 +280,8 @@ def load_serialem(mrcfile, mdocfile): log_level = mrc_logger.getEffectiveLevel() mrc_logger.setLevel(logging.ERROR) - meta, tilts = parse_mdoc(mdocfile) - stack = hspy.load(mrcfile) + meta, _ = parse_mdoc(mdocfile) + stack = hs_load(mrcfile) stack.axes_manager[1].scale = stack.axes_manager[1].scale / 10 stack.axes_manager[2].scale = stack.axes_manager[2].scale / 10 @@ -225,93 +303,110 @@ def load_serialem(mrcfile, mdocfile): return stack -def load_serialem_series(mrcfiles, mdocfiles): +def load_serialem_series( + mrcfiles: Union[List[str], List[Path]], + mdocfiles: Union[List[str], List[Path]], +) -> Tuple[TomoStack, np.ndarray]: """ Load a multi-frame series collected by SerialEM. Parameters ---------- - mrc_files : list of files - MRC files containing multi-frame tilt series data. + mrc_files + List of MRC file paths containing multi-frame tilt series data. - mdoc_files : list of files - SerialEM metadata files for multi-frame tilt series data. + mdoc_files + List of SerialEM metadata file paths for multi-frame tilt series data. Returns - ---------- - stack : TomoStack object + ------- + stack : TomoStack Tilt series resulting by averaging frames at each tilt - + tilts : np.ndarray + The tilt values for each image in the stack """ mrc_logger = logging.getLogger("hyperspy.io_plugins.mrc") log_level = mrc_logger.getEffectiveLevel() mrc_logger.setLevel(logging.ERROR) - stack = [None] * len(mrcfiles) - meta = [None] * len(mdocfiles) + stack = [] + meta = [] tilts = np.zeros(len(mdocfiles)) - for i in range(0, len(mdocfiles)): - meta[i], tilts[i] = parse_mdoc(mdocfiles[i], True) + for i in range(len(mdocfiles)): + mdoc_output = parse_mdoc(mdocfiles[i], series=True) + meta.append(mdoc_output[0]) + tilts[i] = mdoc_output[1] - # tilts = np.array([meta[i]["TiltAngle"] for i in range(0, len(meta))]) tilts_sort = np.argsort(tilts) tilts.sort() - for i in range(0, len(mrcfiles)): - fn = mdocfiles[tilts_sort[i]][:-5] - if fn[-4:].lower() != ".mrc": - fn = fn + ".mrc" - stack[i] = hspy.load(fn) + for i in range(len(mrcfiles)): + mdoc_filename = mdocfiles[tilts_sort[i]] + if isinstance(mdoc_filename, str): + mdoc_filename = Path(mdoc_filename) + + # sometimes files are named "filename.mrc.mdoc", other times + # "filename.mrc" and "filename.mdoc", so need to handle both + if mdoc_filename.stem.lower().endswith(".mrc"): + # "filename.mrc.mdoc" case + mrc_filename = mdoc_filename.parent / mdoc_filename.stem + else: + mrc_filename = mdoc_filename.with_suffix(".mrc") + stack.append(hs_load(mrc_filename)) images_per_tilt = stack[0].data.shape[0] - stack = hspy.stack(stack, show_progressbar=False) + stack = hs_stack(stack, show_progressbar=False) if not stack.metadata.has_item("Acquisition_instrument.TEM"): stack.metadata.add_node("Acquisition_instrument.TEM") stack.metadata.Acquisition_instrument.TEM.magnification = meta[0]["Magnification"] stack.metadata.Acquisition_instrument.TEM.beam_energy = meta[0]["Voltage"] stack.metadata.Acquisition_instrument.TEM.dwell_time = ( - meta[0]["ExposureTime"] * images_per_tilt / (stack.data.shape[2] * stack.data.shape[3]) + meta[0]["ExposureTime"] + * images_per_tilt + / (stack.data.shape[2] * stack.data.shape[3]) ) stack.metadata.Acquisition_instrument.TEM.spot_size = meta[0]["SpotSize"] stack.metadata.Acquisition_instrument.TEM.defocus = meta[0]["Defocus"] stack.metadata.General.original_filename = meta[0]["ImageFile"] logger.info( "SerialEM Multiframe stack successfully loaded. " - "Use etspy.utils.register_serialem_stack to align frames." + "Use etspy.utils.register_serialem_stack to align frames.", ) mrc_logger.setLevel(log_level) return stack, tilts -def parse_mrc_header(filename): +def parse_mrc_header(filename: PathLike) -> dict[str, Any]: """ Read the mrc header and return as dictionary. Args ---------- - filename : str + filename Name of the MRC file to parse Returns - ---------- - headar : dict - Dictionary with header values + ------- + header : dict + Dictionary with header values from an MRC file """ header = {} - with open(filename, "r") as h: + if isinstance(filename, str): + filename = Path(filename) + with filename.open("r") as h: header["nx"], header["ny"], header["nz"] = np.fromfile(h, np.uint32, 3) header["mode"] = np.fromfile(h, np.uint32, 1)[0] header["nxstart"], header["nystart"], header["nzstart"] = np.fromfile( - h, np.uint32, 3 + h, + np.uint32, + 3, ) header["mx"], header["my"], header["mz"] = np.fromfile(h, np.uint32, 3) - header["xlen"], header["ylen"], header["zlen"] = np.fromfile( - h, np.uint32, 3) + header["xlen"], header["ylen"], header["zlen"] = np.fromfile(h, np.uint32, 3) _ = np.fromfile(h, np.uint32, 6) - header["amin"], header["amax"], header["amean"] = np.fromfile( - h, np.uint32, 3) + header["amin"], header["amax"], header["amean"] = np.fromfile(h, np.uint32, 3) _ = np.fromfile(h, np.uint32, 1) header["nextra"] = np.fromfile(h, np.uint32, 1)[0] _ = np.fromfile(h, np.uint16, 1)[0] @@ -328,11 +423,12 @@ def parse_mrc_header(filename): header["idtype"] = np.fromfile(h, np.uint16, 1)[0] header["lens"] = np.fromfile(h, np.uint16, 1)[0] header["nd1"], header["nd2"], header["vd1"], header["vd2"] = np.fromfile( - h, np.uint16, 4 + h, + np.uint16, + 4, ) _ = np.fromfile(h, np.float32, 6) - header["xorg"], header["yorg"], header["zorg"] = np.fromfile( - h, np.float32, 3) + header["xorg"], header["yorg"], header["zorg"] = np.fromfile(h, np.float32, 3) strbits = np.fromfile(h, np.int8, 4) header["cmap"] = "".join([chr(item) for item in strbits]) header["stamp"] = np.fromfile(h, np.int8, 4) @@ -340,86 +436,101 @@ def parse_mrc_header(filename): header["nlabl"] = np.fromfile(h, np.uint32, 1)[0] strbits = np.fromfile(h, np.int8, 800) header["text"] = "".join([chr(item) for item in strbits]) - header["ext_header"] = np.fromfile( - h, np.int16, int(header["nextra"] / 2)) + header["ext_header"] = np.fromfile(h, np.int16, int(header["nextra"] / 2)) return header -def load(filename, tilts=None, **kwargs): +def _load_single_file(filename: Path) -> Tuple[Signal2D, Optional[np.ndarray]]: + """Load a HyperSpy signal and any tilts from a single file.""" + ext = filename.suffix + tilts = None + if ext.lower() in hspy_file_types: + stack = hs_load(filename, reader="HSPY") + if stack.metadata.has_item("Tomography"): + tilts = stack.metadata.Tomography.tilts + elif ext.lower() in dm_file_types: + stack = hs_load(filename) + stack.change_dtype(np.float32) + tilts = get_dm_tilts(stack) + elif ext.lower() in mrc_file_types: + try: + stack = hs_load(filename, reader="mrc") + tilts = get_mrc_tilts(stack, filename) + except TypeError as exc: + msg = "Unable to read MRC with Hyperspy" + raise RuntimeError(msg) from exc + else: + msg = f'Unknown file type "{ext}". Must be one of {known_file_types}' + raise TypeError(msg) + + return stack, tilts + + +def load( + filename: Union[PathLike, List[str], List[Path]], + tilts: Optional[Union[List[float], np.ndarray]] = None, + mdocs: Optional[Union[List[str], List[Path]]] = None, +) -> TomoStack: """ Create a TomoStack object using data from a file. Parameters ---------- - filename : string - Name of file that contains data to be read. Accepted formats (.MRC, - .RAW/.RPL pair, .DM3, .DM4) + filename + Name of file that contains data to be read. + Accepted formats (.MRC, .DM3, .DM4) - tilts : list or NumPy array - List of floats indicating the specimen tilt at each projection + tilts + List of floats indicating the specimen tilt at each projection (optional) - **kwargs: Additional keyword arguments. Possible keys include: - - mdocs (list): List of mdoc files for SerialEM data + mdocs + List of mdoc files for SerialEM data (optional) Returns - ---------- - stack : TomoStack object - + ------- + stack : TomoStack + The resulting TomoStack object """ - hspy_file_types = [".hdf5", ".h5", ".hspy"] - mrc_file_types = [".mrc", ".ali", ".rec"] - dm_file_types = [".dm3", ".dm4"] - known_file_types = hspy_file_types + mrc_file_types + dm_file_types - - if type(filename) is str: - ext = os.path.splitext(filename)[1] - if ext.lower() in hspy_file_types: - stack = hspy.load(filename, reader='HSPY') - if stack.metadata.has_item("Tomography"): - tilts = stack.metadata.Tomography.tilts - elif ext.lower() in dm_file_types: - stack = hspy.load(filename) - stack.change_dtype(np.float32) - tilts = get_dm_tilts(stack) - elif ext.lower() in mrc_file_types: - try: - stack = hspy.load(filename, reader="mrc") - tilts = get_mrc_tilts(stack, filename) - except TypeError: - raise RuntimeError("Unable to read MRC with Hyperspy") - else: - raise TypeError("Unknown file type %s. Must be %s one of " % (ext, [i for i in known_file_types])) - - elif type(filename) is list: - ext = os.path.splitext(filename[0])[1] + if isinstance(filename, (str, Path)): + if isinstance(filename, str): + # coerce filename to Path + filename = Path(filename) + stack, tilts = _load_single_file(filename) + + elif isinstance(filename, list): + first_filename = filename[0] + if isinstance(first_filename, str): + first_filename = Path(first_filename) + ext = first_filename.suffix if ext.lower() in dm_file_types: - s = hspy.load(filename) + s = hs_load(filename) tilts = [i.metadata.Acquisition_instrument.TEM.Stage.tilt_alpha for i in s] sorted_order = np.argsort(tilts) tilts = np.sort(tilts) files_sorted = list(np.array(filename)[sorted_order]) del s - stack = hspy.load(files_sorted, stack=True) + stack = hs_load(files_sorted, stack=True) elif ext.lower() == ".mrc": logger.info("Data appears to be a SerialEM multiframe series.") - if 'mdocs' in kwargs.keys(): - mdocfiles = kwargs['mdocs'] + if mdocs is not None: + mdoc_files = mdocs else: - mdocfiles = [i[:-3] + "mdoc" for i in filename] - stack, tilts = load_serialem_series(filename, mdocfiles) + # generate mdoc filenames from mrc filenames + mrc_files = [Path(p) for p in filename] # coerce to Path objects + mdoc_files = [i.with_suffix(".mdoc") for i in mrc_files] + stack, tilts = load_serialem_series(filename, mdoc_files) else: - raise TypeError( - "Unknown file type %s. Must be one of %s " - % (ext, [i for i in known_file_types]) - ) + msg = f'Unknown file type "{ext}". Must be one of {known_file_types}' + raise TypeError(msg) else: - raise TypeError( - "Unknown filename type %s. Must be either a string or list of strings." - % type(filename) + msg = ( + f"Unknown filename type {type(filename)}. " + "Must be either a string, Path, or list of either." ) + raise TypeError(msg) stack = create_stack(stack, tilts) - stack.change_data_type('float32') + stack.change_data_type("float32") if stack.data.min() < 0: stack.data -= stack.data.min() return stack diff --git a/etspy/recon.py b/etspy/recon.py index b524a1e..6ceb3f8 100644 --- a/etspy/recon.py +++ b/etspy/recon.py @@ -1,21 +1,21 @@ -# -*- coding: utf-8 -*- -# -# This file is part of ETSpy - """ Reconstruction module for ETSpy package. @author: Andrew Herzing """ -import numpy as np -import astra + +import copy import logging import multiprocessing as mp +from typing import cast + +import astra +import numpy as np import tqdm -import copy -from scipy.ndimage import gaussian_filter, convolve -import dask -from dask.diagnostics import ProgressBar +from dask.base import compute as dask_compute +from dask.delayed import delayed as dask_delayed +from dask.diagnostics.progress import ProgressBar +from scipy.ndimage import convolve, gaussian_filter ncpus = mp.cpu_count() logger = logging.getLogger(__name__) @@ -26,7 +26,7 @@ def run_alg(sino, iters, cfg, vol_geom, proj_geom): """ Run CPU-based FBP, SIRT, or SART reconstruction algorithm using dask. - Args + Parameters ---------- sino : NumPy array Sinogram of shape (nangles, ny) @@ -40,7 +40,7 @@ def run_alg(sino, iters, cfg, vol_geom, proj_geom): ASTRA projection geometry Returns - ---------- + ------- Numpy array Reconstruction of input sinogram @@ -57,7 +57,17 @@ def run_alg(sino, iters, cfg, vol_geom, proj_geom): return astra.data2d.get(rec_id) -def run_dart(sino, iters, dart_iters, p, thresholds, gray_levels, cfg, vol_geom, proj_geom): +def run_dart( + sino, + iters, + dart_iters, + p, + thresholds, + gray_levels, + cfg, + vol_geom, + proj_geom, +): """ Run discrete algebraic reoncsturction technique (DART) algorithm. @@ -65,7 +75,7 @@ def run_dart(sino, iters, dart_iters, p, thresholds, gray_levels, cfg, vol_geom, K. J. Batenburg and J. Sijbers, "DART: A Practical Reconstruction Algorithm for Discrete Tomography," doi: 10.1109/TIP.2011.2131661. - Args + Parameters ---------- sino : NumPy array Sinogram of shape (nangles, ny) @@ -87,7 +97,7 @@ def run_dart(sino, iters, dart_iters, p, thresholds, gray_levels, cfg, vol_geom, ASTRA projection geometry Returns - ---------- + ------- Numpy array Reconstruction of input sinogram @@ -95,7 +105,7 @@ def run_dart(sino, iters, dart_iters, p, thresholds, gray_levels, cfg, vol_geom, proj_id = astra.create_projector("strip", proj_geom, vol_geom) rec_id = astra.data2d.create("-vol", vol_geom) sino_id = astra.data2d.create("-sino", proj_geom, sino) - mask_id = astra.data2d.create('-vol', vol_geom, 1) + mask_id = astra.data2d.create("-vol", vol_geom, 1) cfg["ReconstructionDataId"] = rec_id cfg["ProjectorId"] = proj_id cfg["ProjectionDataId"] = sino_id @@ -141,12 +151,25 @@ def run_dart(sino, iters, dart_iters, p, thresholds, gray_levels, cfg, vol_geom, return curr_rec -def run(stack, method, niterations=20, constrain=None, thresh=0, cuda=None, thickness=None, ncores=None, - filter="shepp-logan", gray_levels=None, dart_iterations=None, p=0.99, show_progressbar=True): +def run( # noqa: PLR0912, PLR0913, PLR0915 + stack, + method, + niterations=20, + constrain=None, + thresh=0, + cuda=None, + thickness=None, + ncores=None, + bp_filter="shepp-logan", + gray_levels=None, + dart_iterations=None, + p=0.99, + show_progressbar=True, +) -> np.ndarray: """ Perform reconstruction of input tilt series. - Args + Parameters ---------- stack :TomoStack object TomoStack containing the input tilt series @@ -167,7 +190,7 @@ def run(stack, method, niterations=20, constrain=None, thresh=0, cuda=None, thic Limit for the height of the reconstruction ncores : int Number of cores to use for multithreaded CPU-based reconstructions - filter : str + bp_filter : str Filter to use for filtered backprojection gray_levels : list or NumPy array Gray levels for DART reconstruction @@ -179,12 +202,12 @@ def run(stack, method, niterations=20, constrain=None, thresh=0, cuda=None, thic If True, show a progress bar for the reconstruction. Default is True. Returns - ---------- + ------- rec : Numpy array Containing the reconstructed volume """ - if len(stack.data.shape) == 2: + if len(stack.data.shape) == 2: # noqa: PLR2004 nangles, ny = stack.data.shape stack.data = stack.data[:, :, np.newaxis] nx = 1 @@ -192,6 +215,8 @@ def run(stack, method, niterations=20, constrain=None, thresh=0, cuda=None, thic nangles, ny, nx = stack.data.shape thetas = np.pi * stack.metadata.Tomography.tilts / 180.0 + mask_id = None + thresholds = [] if thickness is None: thickness = ny @@ -201,43 +226,50 @@ def run(stack, method, niterations=20, constrain=None, thresh=0, cuda=None, thic proj_geom = astra.create_proj_geom("parallel", 1.0, ny, thetas) vol_geom = astra.create_vol_geom((thickness, ny)) cfg = {} - cfg['option'] = {} + cfg["option"] = {} if cuda: - if method.lower() == 'fbp': + if method.lower() == "fbp": logger.info("Reconstructing with CUDA-accelerated FBP algorithm") - cfg['type'] = 'FBP_CUDA' - cfg["option"]["FilterType"] = filter.lower() + cfg["type"] = "FBP_CUDA" + cfg["option"]["FilterType"] = bp_filter.lower() niterations = 1 - elif method.lower() == 'sirt': + elif method.lower() == "sirt": logger.info( - "Reconstructing with CUDA-accelerated SIRT algorithm (%s iterations)" - % niterations + "Reconstructing with CUDA-accelerated SIRT algorithm (%s iterations)", + niterations, ) - cfg['type'] = 'SIRT_CUDA' + cfg["type"] = "SIRT_CUDA" if constrain: cfg["option"]["MinConstraint"] = thresh - elif method.lower() == 'sart': + elif method.lower() == "sart": logger.info( - "Reconstructing with CUDA-accelerated SART algorithm (%s iterations)" - % niterations + "Reconstructing with CUDA-accelerated SART algorithm (%s iterations)", + niterations, ) - cfg['type'] = 'SART_CUDA' + cfg["type"] = "SART_CUDA" if constrain: cfg["option"]["MinConstraint"] = thresh elif method.lower() == "dart": logger.info( - "Reconstructing with CUDA-accelerated DART algorithm (%s iterations)" - % niterations + "Reconstructing with CUDA-accelerated DART algorithm (%s iterations)", + niterations, ) - cfg['type'] = 'SART_CUDA' - thresholds = [(gray_levels[i] + gray_levels[i + 1]) // 2 for i in range(len(gray_levels) - 1)] + cfg["type"] = "SART_CUDA" + if gray_levels is None: + msg = "gray_levels must be provided for DART" + raise ValueError(msg) + gray_levels = cast(np.ndarray, gray_levels) # explicit type-checking cast + thresholds = [ + (gray_levels[i] + gray_levels[i + 1]) // 2 + for i in range(len(gray_levels) - 1) + ] mask = np.ones([thickness, ny]) - mask_id = astra.data2d.create('-vol', vol_geom, mask) - cfg['option']['MinConstraint'] = 0 - cfg['option']['MaxConstraint'] = 255 - cfg['option']['ReconstructionMaskId'] = mask_id + mask_id = astra.data2d.create("-vol", vol_geom, mask) + cfg["option"]["MinConstraint"] = 0 + cfg["option"]["MaxConstraint"] = 255 + cfg["option"]["ReconstructionMaskId"] = mask_id proj_id = astra.create_projector("cuda", proj_geom, vol_geom) rec_id = astra.data2d.create("-vol", vol_geom) @@ -249,13 +281,22 @@ def run(stack, method, niterations=20, constrain=None, thresh=0, cuda=None, thic cfg["ReconstructionDataId"] = rec_id alg = astra.algorithm.create(cfg) - for i in tqdm.tqdm(range(0, nx), disable=not (show_progressbar)): + for i in tqdm.tqdm(range(nx), disable=not (show_progressbar)): astra.data2d.store(sino_id, stack.data[:, :, i]) astra.data2d.store(rec_id, np.zeros([thickness, ny])) if method.lower() == "dart": astra.data2d.store(mask_id, np.ones([thickness, ny])) - rec[i, :, :] = run_dart(stack.data[:, :, i], niterations, dart_iterations, p, - thresholds, gray_levels, cfg, vol_geom, proj_geom) + rec[i, :, :] = run_dart( + stack.data[:, :, i], + niterations, + dart_iterations, + p, + thresholds, + gray_levels, + cfg, + vol_geom, + proj_geom, + ) else: astra.algorithm.run(alg, niterations) rec[i, :, :] = astra.data2d.get(rec_id) @@ -263,76 +304,81 @@ def run(stack, method, niterations=20, constrain=None, thresh=0, cuda=None, thic if ncores is None: ncores = min(nx, int(0.9 * mp.cpu_count())) - if method.lower() == 'fbp': + if method.lower() == "fbp": logger.info("Reconstructing with CPU-based FBP algorithm") - cfg['type'] = 'FBP' - cfg["option"]["FilterType"] = filter.lower() + cfg["type"] = "FBP" + cfg["option"]["FilterType"] = bp_filter.lower() niterations = 1 - elif method.lower() == 'sirt': + elif method.lower() == "sirt": logger.info("Reconstructing with CPU-based SIRT algorithm") - cfg['type'] = 'SIRT' + cfg["type"] = "SIRT" if constrain: cfg["option"]["MinConstraint"] = thresh - elif method.lower() == 'sart': + elif method.lower() == "sart": logger.info("Reconstructing with CPU-based SART algorithm") - cfg['type'] = 'SART' + cfg["type"] = "SART" if constrain: cfg["option"]["MinConstraint"] = thresh elif method.lower() == "dart": logger.info("Reconstructing with CPU-based DART algorithm") - cfg['type'] = 'SART' - thresholds = [(gray_levels[i] + gray_levels[i + 1]) // 2 for i in range(len(gray_levels) - 1)] + cfg["type"] = "SART" + if gray_levels is None: + msg = "gray_levels must be provided for DART" + raise ValueError(msg) + gray_levels = cast(np.ndarray, gray_levels) # explicit type-checking cast + thresholds = [ + (gray_levels[i] + gray_levels[i + 1]) // 2 + for i in range(len(gray_levels) - 1) + ] mask = np.ones([thickness, ny]) - mask_id = astra.data2d.create('-vol', vol_geom, mask) - cfg['option']['MinConstraint'] = 0 - cfg['option']['MaxConstraint'] = 255 - cfg['option']['ReconstructionMaskId'] = mask_id - - if method.lower() in ['fbp', 'sirt', 'sart']: - tasks = [dask.delayed(run_alg)(stack.data[:, :, i], niterations, cfg, - vol_geom, proj_geom) for i in range(nx)] + mask_id = astra.data2d.create("-vol", vol_geom, mask) + cfg["option"]["MinConstraint"] = 0 + cfg["option"]["MaxConstraint"] = 255 + cfg["option"]["ReconstructionMaskId"] = mask_id + + if method.lower() in ["fbp", "sirt", "sart"]: + tasks = [ + dask_delayed(run_alg)( + stack.data[:, :, i], + niterations, + cfg, + vol_geom, + proj_geom, + ) + for i in range(nx) + ] if show_progressbar: with ProgressBar(): - results = dask.compute(*tasks, num_workers=ncores) + results = dask_compute(*tasks, num_workers=ncores) else: - results = dask.compute(*tasks, num_workers=ncores) + results = dask_compute(*tasks, num_workers=ncores) for i, result in enumerate(results): rec[i] = result - # if ncores == 1: - # for i in tqdm.tqdm(range(0, nx), disable=not (show_progressbar)): - # rec[i] = run_alg(stack.data[:, :, i], niterations, sino_id, alg, rec_id) - # else: - # logger.info("Using %s CPU cores to reconstruct %s slices" % (ncores, nx)) - # with mp.Pool(ncores) as pool: - # for i, result in enumerate( - # pool.starmap(run_alg, - # [(stack.data[:, :, i], niterations, sino_id, alg, rec_id) for i in range(0, nx)],)): - # rec[i] = result - elif method.lower() == 'dart': - tasks = [dask.delayed(run_dart)(stack.data[:, :, i], niterations, dart_iterations, p, - thresholds, gray_levels, cfg, vol_geom, proj_geom) for i in range(nx)] + elif method.lower() == "dart": + tasks = [ + dask_delayed(run_dart)( + stack.data[:, :, i], + niterations, + dart_iterations, + p, + thresholds, + gray_levels, + cfg, + vol_geom, + proj_geom, + ) + for i in range(nx) + ] if show_progressbar: with ProgressBar(): - results = dask.compute(*tasks, num_workers=ncores) + results = dask_compute(*tasks, num_workers=ncores) else: - results = dask.compute(*tasks, num_workers=ncores) + results = dask_compute(*tasks, num_workers=ncores) for i, result in enumerate(results): rec[i] = result - # if ncores == 1: - # for i in tqdm.tqdm(range(0, nx), disable=not (show_progressbar)): - # rec[i] = run_dart(stack.data[:, :, i], niterations, dart_iterations, p, - # alg, proj_id, mask_id, rec_id, sino_id, thresholds, gray_levels) - # else: - # logger.info("Using %s CPU cores to reconstruct %s slices" % (ncores, nx)) - # with mp.Pool(ncores) as pool: - # for i, result in enumerate( - # pool.starmap(run_dart, - # [(stack.data[:, :, i], niterations, dart_iterations, p, - # alg, proj_id, mask_id, rec_id, sino_id, thresholds, gray_levels) - # for i in range(0, nx)],)): - # rec[i] = result + astra.clear() return rec @@ -341,7 +387,7 @@ def dart_segment(rec, thresholds, gray_vals): """ Segmentation step for DART Reconstruction. - Args + Parameters ---------- rec : NumPy array Tomographic reconstruction. @@ -351,7 +397,7 @@ def dart_segment(rec, thresholds, gray_vals): Grayscale values to assign the segmented regions. Returns - ---------- + ------- segmented : NumPy array Segmented version of the reconstruction. @@ -365,30 +411,36 @@ def get_dart_boundaries(segmented): """ Boundary step for DART Reconstruction. - Args + Parameters ---------- segmented : NumPy array Segmented reconstruction. Returns - ---------- + ------- boundaries : NumPy array Boundaries of the segmented reconstruction. """ - kernel = np.array([[1, 1, 1], - [1, -8, 1], - [1, 1, 1]]) - edges = convolve(segmented.astype(np.int32), kernel, mode='constant', cval=0) + kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]]) + edges = convolve(segmented.astype(np.int32), kernel, mode="constant", cval=0) boundaries = edges != 0 return boundaries -def astra_error(sinogram, angles, method='sirt', iterations=50, constrain=True, thresh=0, cuda=False): +def astra_error( + sinogram, + angles, + method="sirt", + iterations=50, + constrain=True, + thresh=0, + cuda=False, +): """ Perform SIRT reconstruction using the Astra toolbox algorithms. - Args + Parameters ---------- sinogram : NumPy array Tilt series data either of the form [angles, x] or [angles, y, x] where @@ -408,8 +460,9 @@ def astra_error(sinogram, angles, method='sirt', iterations=50, constrain=True, cuda : boolean If True, use the CUDA-accelerated Astra algorithms. Otherwise, use the CPU-based algorithms + Returns - ---------- + ------- rec : Numpy array 3D array of the form [y, z, x] containing the reconstructed object. @@ -437,7 +490,7 @@ def astra_error(sinogram, angles, method='sirt', iterations=50, constrain=True, cfg["ProjectorId"] = proj_id cfg["ReconstructionDataId"] = rec_id if constrain: - cfg["option"] = {} + cfg["option"] = {} # pyright: ignore[reportArgumentType] cfg["option"]["MinConstraint"] = thresh alg = astra.algorithm.create(cfg) @@ -452,6 +505,6 @@ def astra_error(sinogram, angles, method='sirt', iterations=50, constrain=True, residual_error[i] = astra.algorithm.get_res_norm(alg) else: curr_id, curr = astra.create_sino(rec[i], proj_id) - residual_error[i] = np.linalg.norm((sinogram - curr)) + residual_error[i] = np.linalg.norm(sinogram - curr) astra.clear() return rec, residual_error diff --git a/etspy/setup.cfg b/etspy/setup.cfg deleted file mode 100644 index b88034e..0000000 --- a/etspy/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[metadata] -description-file = README.md diff --git a/etspy/simulation.py b/etspy/simulation.py index 0c699a7..d544af8 100644 --- a/etspy/simulation.py +++ b/etspy/simulation.py @@ -1,85 +1,89 @@ -# -*- coding: utf-8 -*- -# -# This file is part of ETSpy - """ Simulation module for ETSpy package. @author: Andrew Herzing """ + +from typing import Tuple + +import astra +import hyperspy.api as hs import numpy as np from scipy import ndimage -import astra + +from etspy.api import TomoStack from etspy.io import create_stack -import hyperspy.api as hs -def create_catalyst_model(nparticles=15, particle_density=255, support_density=100, volsize=[600, 600, 600], support_radius=200, size_interval=[5, 12]): +def create_catalyst_model( + nparticles: int = 15, + particle_density: int = 255, + support_density: int = 100, + volsize: Tuple[int, int, int] = (600, 600, 600), + support_radius: int = 200, + size_interval: Tuple[int, int] = (5, 12), +): """ Create a model data array that mimics a hetergeneous catalyst. - Args + Parameters ---------- - nparticles : int + nparticles Number of particles to add - particle_density : int + particle_density Grayscale value to assign to the particles - support_density : int + support_density Grayscale value to assign to the support - volsize : list - X, Y, Z shape of the volume - support_radius : int + volsize + X, Y, Z shape (in that order) of the volume + support_radius Radius (in pixels) of the support - size_interval : list - Upper and lower bounds of the particle size + size_interval + Lower and upper bounds (in that order) of the particle size Returns - ---------- + ------- catalyst : Hyperspy Signal2D Simulated model """ - volsize = np.array(volsize) - center = np.int32(volsize / 2) - size_interval = [5, 12] + volsize_np = np.array(volsize) + center = np.array(volsize_np / 2, dtype=np.int32) - catalyst = np.zeros(volsize, np.uint8) + catalyst = np.zeros(volsize_np, np.uint8) coords = np.zeros([nparticles, 4]) - for i in range(0, nparticles): + for i in range(nparticles): size = 2 * np.random.randint(size_interval[0], size_interval[1]) r = support_radius * 2 overlap = False + x = np.random.randint(0, volsize_np[0]) # initalize x and y before while loop + y = np.random.randint(0, volsize_np[1]) while r > support_radius or overlap: - x = np.random.randint(0, volsize[0]) - y = np.random.randint(0, volsize[1]) + x = np.random.randint(0, volsize_np[0]) + y = np.random.randint(0, volsize_np[1]) r = np.sqrt((x - center[0]) ** 2 + (y - center[1]) ** 2) distance = np.abs(coords[:, 0:2] - np.array([x, y])) - if np.min(distance) < size_interval[1]: - overlap = True - else: - overlap = False + overlap = np.min(distance) < size_interval[1] z_exact = np.int32( - np.sqrt(support_radius**2 - (x - center[0]) ** 2 - (y - center[1]) ** 2) + center[2] + np.sqrt(support_radius**2 - (x - center[0]) ** 2 - (y - center[1]) ** 2) + + center[2], ) zmin = z_exact - np.int32(size / 2) zmax = z_exact + np.int32(size / 2) z_rand = np.random.randint(zmin, zmax) test = np.random.randint(-1, 1) - if test < 0: - z = center[2] - (z_rand - center[2]) - else: - z = z_rand + z = center[2] - (z_rand - center[2]) if test < 0 else z_rand coords[i, :] = [x, y, z, size] - xx, yy, zz = np.mgrid[: volsize[0], : volsize[1], : volsize[2]] + xx, yy, zz = np.mgrid[: volsize_np[0], : volsize_np[1], : volsize_np[2]] support = (xx - center[0]) ** 2 + (yy - center[1]) ** 2 + (zz - center[2]) ** 2 catalyst[support < support_radius**2] = support_density - for i in range(0, nparticles): + for i in range(nparticles): x, y, z, particle_radius = coords[i, :] particle = (xx - x) ** 2 + (yy - y) ** 2 + (zz - z) ** 2 catalyst[particle < particle_radius**2] = particle_density @@ -95,7 +99,7 @@ def create_cylinder_model(radius=30, blur=True, blur_sigma=1.5, add_others=False """ Create a model data array that mimics a needle shaped sample. - Args + Parameters ---------- vol_size : int Size of the volume for the model @@ -106,25 +110,24 @@ def create_cylinder_model(radius=30, blur=True, blur_sigma=1.5, add_others=False blur_sigma : float Sigma value for the Gaussiuan blur add_others : bool - If True, add a second and third cylinder to the model near the periphery. This is useful - for testing the effects of additional objects entering the tilt series field of view. + If True, add a second and third cylinder to the model near the periphery. + This is useful for testing the effects of additional objects entering the + tilt series field of view. + Returns - ---------- + ------- cylinder : Signal2D Simulated cylinder object """ - if add_others: - vol_shape = np.array([400, 400, 400]) - else: - vol_shape = np.array([200, 200, 200]) + vol_shape = np.array([400, 400, 400]) if add_others else np.array([200, 200, 200]) cylinder = np.zeros(vol_shape, np.uint16) - xx, yy = np.ogrid[:vol_shape[1], :vol_shape[2]] + xx, yy = np.ogrid[: vol_shape[1], : vol_shape[2]] center_x, center_y, _ = vol_shape // 2 # Create first cylinder - cylinder1 = (xx - center_x)**2 + (yy - center_y)**2 <= radius**2 + cylinder1 = (xx - center_x) ** 2 + (yy - center_y) ** 2 <= radius**2 if not add_others: # Add the cylinder to the volume @@ -135,18 +138,19 @@ def create_cylinder_model(radius=30, blur=True, blur_sigma=1.5, add_others=False # Create second cylinder radius_cylinder2 = 10 center_x, center_y = [30, 30] - cylinder2 = (xx - center_x)**2 + (yy - center_y)**2 <= radius_cylinder2**2 + cylinder2 = (xx - center_x) ** 2 + (yy - center_y) ** 2 <= radius_cylinder2**2 # Create third cylinder radius_cylinder3 = 15 center_x, center_y = [370, 350] - cylinder3 = (xx - center_x)**2 + (yy - center_y)**2 <= radius_cylinder3**2 + cylinder3 = (xx - center_x) ** 2 + (yy - center_y) ** 2 <= radius_cylinder3**2 # Add the cylinders to the volume + low_thresh, mid_thresh, high_thresh = 150, 230, 270 for i in range(vol_shape[2]): - if i < 150: + if i < low_thresh: cylinder[:, :, i] = 50 * cylinder1 + 10 * cylinder2 - elif i < 270 and i > 230: + elif i < high_thresh and i > mid_thresh: cylinder[:, :, i] = 50 * cylinder1 + 20 * cylinder3 else: cylinder[:, :, i] = 50 * cylinder1 @@ -165,7 +169,7 @@ def create_model_tilt_series(model, angles=None, cuda=None): """ Create a tilt series from a 3D volume. - Args + Parameters ---------- model : NumPy array or Hyperspy Signal2D 3D array or signal containing the model volume to project to a tilt series @@ -173,7 +177,7 @@ def create_model_tilt_series(model, angles=None, cuda=None): Projection angles for tilt series in degrees Returns - ---------- + ------- model : TomoStack object Tilt series of the model data @@ -200,36 +204,48 @@ def create_model_tilt_series(model, angles=None, cuda=None): else: proj_id = astra.create_projector("cuda", proj_geom, vol_geom) - for i in range(0, model.shape[0]): + for i in range(model.shape[0]): sino_id, proj_data[:, :, i] = astra.create_sino(model[i, :, :], proj_id) stack = create_stack(proj_data, angles) return stack -def misalign_stack(stack, min_shift=-5, max_shift=5, tilt_shift=0, tilt_rotate=0, y_only=False, interp_order=3): +def misalign_stack( + stack: TomoStack, + min_shift: int = -5, + max_shift: int = 5, + tilt_shift: int = 0, + tilt_rotate: int = 0, + y_only: bool = False, + interp_order: int = 3, +) -> TomoStack: """ Apply misalignment to a model tilt series. - Args + Parameters ---------- - stack : TomoStack object + stack TomoStack simluation - min_shift : int + min_shift Minimum amount of jitter to apply to the stack - max_shift : int + max_shift Maximum amount of jitter to apply to the stack - tilt_shift : int + tilt_shift Number of pixels by which to offset the tilt axis from the center - tilt_rotate : int + tilt_rotate Amount of rotation to apply to the stack - y_only : bool + y_only If True, limit the application of jitter to the x-direction only. Default is False + interp_order + The order of spline interpolation used by the :py:func:`scipy.ndimage.shift` + or :py:function:`scipy.ndimage.rotate` function. + The order must be in the range 0-5. Returns - ---------- - misaligned : TomoStack object + ------- + misaligned Misaligned copy of the input TomoStack """ @@ -237,11 +253,17 @@ def misalign_stack(stack, min_shift=-5, max_shift=5, tilt_shift=0, tilt_rotate=0 if tilt_shift != 0: misaligned.data = ndimage.shift( - misaligned.data, shift=[0, 0, tilt_shift], order=interp_order + misaligned.data, + shift=[0, 0, tilt_shift], + order=interp_order, ) if tilt_rotate != 0: misaligned.data = ndimage.rotate( - misaligned.data, axes=(1, 2), angle=-tilt_rotate, order=interp_order, reshape=False + misaligned.data, + axes=(1, 2), + angle=-tilt_rotate, + order=interp_order, + reshape=False, ) if (min_shift != 0) or (max_shift != 0): @@ -250,8 +272,12 @@ def misalign_stack(stack, min_shift=-5, max_shift=5, tilt_shift=0, tilt_rotate=0 if y_only: jitter[i, 1] = 0 - misaligned.data[i, :, :] = ndimage.shift(misaligned.data[i, :, :], shift=[jitter[i, 0], jitter[i, 1]], order=interp_order) - misaligned.metadata.Tomography.shifts = jitter + misaligned.data[i, :, :] = ndimage.shift( + misaligned.data[i, :, :], + shift=[jitter[i, 0], jitter[i, 1]], + order=interp_order, + ) + misaligned.metadata.Tomography.shifts = jitter return misaligned @@ -259,7 +285,7 @@ def add_noise(stack, noise_type="gaussian", scale_factor=0.2): """ Apply misalignment to a model tilt series. - Args + Parameters ---------- stack : TomoStack object TomoStack simluation @@ -269,7 +295,7 @@ def add_noise(stack, noise_type="gaussian", scale_factor=0.2): Amount of noise to add Returns - ---------- + ------- noisy : TomoStack object Noisy copy of the input TomoStack @@ -278,7 +304,9 @@ def add_noise(stack, noise_type="gaussian", scale_factor=0.2): if noise_type == "gaussian": noise = np.random.normal( - stack.data.mean(), scale_factor * stack.data.mean(), stack.data.shape + stack.data.mean(), + scale_factor * stack.data.mean(), + stack.data.shape, ) noisy.data = noisy.data + noise if noisy.data.min() < 0: diff --git a/etspy/tests/__init__.py b/etspy/tests/__init__.py new file mode 100644 index 0000000..8fb2ce8 --- /dev/null +++ b/etspy/tests/__init__.py @@ -0,0 +1,14 @@ +"""Tests for the ETSpy package.""" + +from hyperspy.io import load as hs_load + +from etspy.api import etspy_path + + +def hspy_mrc_reader_check(): + """Test loading of an MRC file using the HyperSpy reader.""" + dirname = etspy_path / "tests" / "test_data" / "SerialEM_Multiframe_Test" + files = dirname.glob("*.mrc") + file = next(files) + s = hs_load(file) + return s diff --git a/etspy/tests/test_align.py b/etspy/tests/test_align.py index 1034f34..2f1dbc1 100644 --- a/etspy/tests/test_align.py +++ b/etspy/tests/test_align.py @@ -1,139 +1,175 @@ +"""Tests for the alignment features of ETSpy.""" + +import numpy as np +import pytest + import etspy.api as etspy from etspy import datasets as ds -import pytest -import numpy as np class TestAlignFunctions: + """Test alignment functions.""" + def test_apply_shifts_bad_shape(self): stack = ds.get_needle_data() stack = stack.inav[0:5] shifts = np.zeros(10) - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match=r"Number of shifts \(\d+\) is not consistent with number of images " + r"in the stack \(\d+\)", + ): etspy.align.apply_shifts(stack, shifts) def test_pad_line(self): line = np.zeros(100) - padded = etspy.align.pad_line(line, 200) - assert padded.shape[0] == 200 + padding = 200 + padded = etspy.align.pad_line(line, padding) + assert padded.shape[0] == padding def test_pad_line_uneven_line(self): line = np.zeros(101) - padded = etspy.align.pad_line(line, 200) - assert padded.shape[0] == 200 + padding = 200 + padded = etspy.align.pad_line(line, padding) + assert padded.shape[0] == padding def test_pad_line_uneven_padded(self): line = np.zeros(100) - padded = etspy.align.pad_line(line, 201) - assert padded.shape[0] == 201 + padding = 201 + padded = etspy.align.pad_line(line, padding) + assert padded.shape[0] == padding def test_pad_line_uneven_both(self): line = np.zeros(101) - padded = etspy.align.pad_line(line, 201) - assert padded.shape[0] == 201 + padding = 201 + padded = etspy.align.pad_line(line, padding) + assert padded.shape[0] == padding class TestAlignStackRegister: + """Test alignment using stack reg.""" def test_register_pc(self): stack = ds.get_needle_data() - stack.metadata.Tomography.shifts = \ - stack.metadata.Tomography.shifts[0:20] - reg = stack.inav[0:20].stack_register('PC') - assert type(reg) is etspy.TomoStack - assert reg.axes_manager.signal_shape == \ - stack.inav[0:20].axes_manager.signal_shape - assert reg.axes_manager.navigation_shape == \ - stack.inav[0:20].axes_manager.navigation_shape + stack.metadata.Tomography.shifts = stack.metadata.Tomography.shifts[0:20] + reg = stack.inav[0:20].stack_register("PC") + assert isinstance(reg, etspy.TomoStack) + assert ( + reg.axes_manager.signal_shape == stack.inav[0:20].axes_manager.signal_shape + ) + assert ( + reg.axes_manager.navigation_shape + == stack.inav[0:20].axes_manager.navigation_shape + ) def test_register_com(self): stack = ds.get_needle_data() - stack.metadata.Tomography.shifts = \ - stack.metadata.Tomography.shifts[0:20] - stack.metadata.Tomography.tilts = \ - stack.metadata.Tomography.tilts[0:20] - reg = stack.inav[0:20].stack_register('COM') - assert type(reg) is etspy.TomoStack - assert reg.axes_manager.signal_shape == \ - stack.inav[0:20].axes_manager.signal_shape - assert reg.axes_manager.navigation_shape == \ - stack.inav[0:20].axes_manager.navigation_shape + stack.metadata.Tomography.shifts = stack.metadata.Tomography.shifts[0:20] + stack.metadata.Tomography.tilts = stack.metadata.Tomography.tilts[0:20] + reg = stack.inav[0:20].stack_register("COM") + assert isinstance(reg, etspy.TomoStack) + assert ( + reg.axes_manager.signal_shape == stack.inav[0:20].axes_manager.signal_shape + ) + assert ( + reg.axes_manager.navigation_shape + == stack.inav[0:20].axes_manager.navigation_shape + ) def test_register_stackreg(self): stack = ds.get_needle_data() - stack.metadata.Tomography.shifts = \ - stack.metadata.Tomography.shifts[0:20] - reg = stack.inav[0:20].stack_register('StackReg') - assert type(reg) is etspy.TomoStack - assert reg.axes_manager.signal_shape == \ - stack.inav[0:20].axes_manager.signal_shape - assert reg.axes_manager.navigation_shape == \ - stack.inav[0:20].axes_manager.navigation_shape + stack.metadata.Tomography.shifts = stack.metadata.Tomography.shifts[0:20] + reg = stack.inav[0:20].stack_register("StackReg") + assert isinstance(reg, etspy.TomoStack) + assert ( + reg.axes_manager.signal_shape == stack.inav[0:20].axes_manager.signal_shape + ) + assert ( + reg.axes_manager.navigation_shape + == stack.inav[0:20].axes_manager.navigation_shape + ) def test_register_com_cl(self): stack = ds.get_needle_data() - stack.metadata.Tomography.shifts = \ - stack.metadata.Tomography.shifts[0:20] - reg = stack.inav[0:20].stack_register('COM-CL') - assert type(reg) is etspy.TomoStack - assert reg.axes_manager.signal_shape == \ - stack.inav[0:20].axes_manager.signal_shape - assert reg.axes_manager.navigation_shape == \ - stack.inav[0:20].axes_manager.navigation_shape + stack.metadata.Tomography.shifts = stack.metadata.Tomography.shifts[0:20] + reg = stack.inav[0:20].stack_register("COM-CL") + assert isinstance(reg, etspy.TomoStack) + assert ( + reg.axes_manager.signal_shape == stack.inav[0:20].axes_manager.signal_shape + ) + assert ( + reg.axes_manager.navigation_shape + == stack.inav[0:20].axes_manager.navigation_shape + ) def test_register_unknown_method(self): stack = ds.get_needle_data() - stack.metadata.Tomography.shifts = \ - stack.metadata.Tomography.shifts[0:20] - with pytest.raises(ValueError): - stack.inav[0:20].stack_register('WRONG') + stack.metadata.Tomography.shifts = stack.metadata.Tomography.shifts[0:20] + bad_method = "WRONG" + with pytest.raises( + ValueError, + match=f"Unknown registration method: '{bad_method.lower()}'. " + "Must be 'PC', 'StackReg', or 'COM'", + ): + stack.inav[0:20].stack_register(bad_method) class TestTiltAlign: + """Test tilt alignment functions.""" + def test_tilt_align_com(self): stack = ds.get_needle_data() - reg = stack.stack_register('PC') - ali = reg.tilt_align(method='CoM', locs=[64, 128, 192]) + reg = stack.stack_register("PC") + ali = reg.tilt_align(method="CoM", locs=[64, 128, 192]) tilt_axis = ali.metadata.Tomography.tiltaxis assert abs(-2.7 - tilt_axis) < 1.0 def test_tilt_align_com_no_locs(self): stack = ds.get_needle_data() - reg = stack.stack_register('PC') - ali = reg.tilt_align(method='CoM', locs=None, nslices=None) + reg = stack.stack_register("PC") + ali = reg.tilt_align(method="CoM", locs=None, nslices=None) tilt_axis = ali.metadata.Tomography.tiltaxis assert abs(-2.7 - tilt_axis) < 1.0 def test_tilt_align_com_no_tilts(self): stack = ds.get_needle_data() - reg = stack.stack_register('PC') + reg = stack.stack_register("PC") reg.metadata.Tomography.tilts = None - with pytest.raises(ValueError): - reg.tilt_align(method='CoM', locs=[64, 128, 192]) + with pytest.raises( + ValueError, + match="Tilts are not defined in stack.metadata.Tomography", + ): + reg.tilt_align(method="CoM", locs=[64, 128, 192]) def test_tilt_align_maximage(self): stack = ds.get_needle_data() stack = stack.inav[0:5] stack.metadata.Tomography.shifts = np.zeros([5, 2]) - reg = stack.stack_register('PC') - ali = reg.tilt_align(method='MaxImage') + reg = stack.stack_register("PC") + ali = reg.tilt_align(method="MaxImage") tilt_axis = ali.metadata.Tomography.tiltaxis assert isinstance(tilt_axis, float) def test_tilt_align_unknown_method(self): stack = ds.get_needle_data() - with pytest.raises(ValueError): - stack.tilt_align(method='WRONG') + bad_method = "WRONG" + with pytest.raises( + ValueError, + match=f"Invalid alignment method: '{bad_method.lower()}'. " + "Must be 'CoM' or 'MaxImage'", + ): + stack.tilt_align(method=bad_method) class TestAlignOther: + """Test alignment of a dataset calculated for another.""" def test_align_to_other(self): stack = ds.get_needle_data() stack = stack.inav[0:5] stack.metadata.Tomography.shifts = np.zeros([5, 2]) stack2 = stack.deepcopy() - reg = stack.stack_register('PC') + reg = stack.stack_register("PC") reg2 = reg.align_other(stack2) diff = reg.data - reg2.data assert diff.sum() == 0.0 @@ -143,14 +179,17 @@ def test_align_to_other_no_alignment(self): stack = stack.inav[0:5] stack2 = stack.deepcopy() reg = stack.deepcopy() - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="No transformations have been applied to this stack", + ): reg.align_other(stack2) def test_align_to_other_with_crop(self): stack = ds.get_needle_data() stack = stack.inav[0:5] stack.metadata.Tomography.shifts = np.zeros([5, 2]) - reg = stack.stack_register('PC', crop=True) + reg = stack.stack_register("PC", crop=True) reg2 = reg.align_other(stack) diff = reg.data - reg2.data assert diff.sum() == 0.0 @@ -160,7 +199,7 @@ def test_align_to_other_with_xshift(self): stack = stack.inav[0:5] stack.metadata.Tomography.shifts = np.zeros([5, 2]) stack2 = stack.deepcopy() - reg = stack.stack_register('PC') + reg = stack.stack_register("PC") reg = reg.trans_stack(xshift=10, yshift=5, angle=2) reg2 = reg.align_other(stack2) diff = reg.data - reg2.data diff --git a/etspy/tests/test_base.py b/etspy/tests/test_base.py index 096f2fe..1df02eb 100644 --- a/etspy/tests/test_base.py +++ b/etspy/tests/test_base.py @@ -1,88 +1,112 @@ -import matplotlib -from etspy import datasets as ds -import pytest -import numpy as np -import sys -import io -from etspy.base import CommonStack, TomoStack, RecStack +"""Tests for base functions of ETSpy.""" + import hyperspy.api as hs -# from hyperspy.signals import Signal2D +import numpy as np +import pytest +from matplotlib import pyplot as plt +from matplotlib.figure import Figure + +from etspy import datasets as ds +from etspy.base import CommonStack, RecStack, TomoStack + +NUM_AXES_THREE = 3 def _set_tomo_metadata(s): - tomo_metadata = {"cropped": False, - "shifts": np.zeros([s.data.shape[0], 2]), - "tiltaxis": 0, - "tilts": np.zeros(s.data.shape[0]), - "xshift": 0, - "yshift": 0} + tomo_metadata = { + "cropped": False, + "shifts": np.zeros([s.data.shape[0], 2]), + "tiltaxis": 0, + "tilts": np.zeros(s.data.shape[0]), + "xshift": 0, + "yshift": 0, + } s.metadata.add_node("Tomography") s.metadata.Tomography.add_dictionary(tomo_metadata) return s class TestTomoStack: + """Test creation of a TomoStack.""" + def test_tomostack_create(self): s = hs.signals.Signal2D(np.random.random([10, 100, 100])) stack = TomoStack(s) - assert type(stack) is TomoStack + assert isinstance(stack, TomoStack) class TestFiltering: + """Test filtering of TomoStack data.""" def test_correlation_check(self): stack = ds.get_needle_data() fig = stack.test_correlation() - assert type(fig) is matplotlib.figure.Figure - assert len(fig.axes) == 3 + assert isinstance(fig, Figure) + assert len(fig.axes) == NUM_AXES_THREE def test_image_filter_median(self): stack = ds.get_needle_data() - filt = stack.inav[0:10].filter(method='median') - assert filt.axes_manager.navigation_shape == \ - stack.inav[0:10].axes_manager.navigation_shape - assert filt.axes_manager.signal_shape == \ - stack.inav[0:10].axes_manager.signal_shape + filt = stack.inav[0:10].filter(method="median") + assert ( + filt.axes_manager.navigation_shape + == stack.inav[0:10].axes_manager.navigation_shape + ) + assert ( + filt.axes_manager.signal_shape == stack.inav[0:10].axes_manager.signal_shape + ) def test_image_filter_sobel(self): stack = ds.get_needle_data() - filt = stack.inav[0:10].filter(method='sobel') - assert filt.axes_manager.navigation_shape == \ - stack.inav[0:10].axes_manager.navigation_shape - assert filt.axes_manager.signal_shape == \ - stack.inav[0:10].axes_manager.signal_shape + filt = stack.inav[0:10].filter(method="sobel") + assert ( + filt.axes_manager.navigation_shape + == stack.inav[0:10].axes_manager.navigation_shape + ) + assert ( + filt.axes_manager.signal_shape == stack.inav[0:10].axes_manager.signal_shape + ) def test_image_filter_both(self): stack = ds.get_needle_data() - filt = stack.inav[0:10].filter(method='both') - assert filt.axes_manager.navigation_shape == \ - stack.inav[0:10].axes_manager.navigation_shape - assert filt.axes_manager.signal_shape == \ - stack.inav[0:10].axes_manager.signal_shape + filt = stack.inav[0:10].filter(method="both") + assert ( + filt.axes_manager.navigation_shape + == stack.inav[0:10].axes_manager.navigation_shape + ) + assert ( + filt.axes_manager.signal_shape == stack.inav[0:10].axes_manager.signal_shape + ) def test_image_filter_bpf(self): stack = ds.get_needle_data() - filt = stack.inav[0:10].filter(method='bpf') - assert filt.axes_manager.navigation_shape == \ - stack.inav[0:10].axes_manager.navigation_shape - assert filt.axes_manager.signal_shape == \ - stack.inav[0:10].axes_manager.signal_shape + filt = stack.inav[0:10].filter(method="bpf") + assert ( + filt.axes_manager.navigation_shape + == stack.inav[0:10].axes_manager.navigation_shape + ) + assert ( + filt.axes_manager.signal_shape == stack.inav[0:10].axes_manager.signal_shape + ) def test_image_filter_wrong_name(self): stack = ds.get_needle_data() - with pytest.raises(ValueError): - stack.inav[0:10].filter(method='WRONG') + bad_name = "WRONG" + with pytest.raises( + ValueError, + match=f"Unknown filter method '{bad_name}'. " + "Must be 'median', 'sobel', 'both', or 'bpf'", + ): + stack.inav[0:10].filter(method="WRONG") class TestOperations: + """Test various operations of a TomoStack.""" def test_stack_normalize(self): stack = ds.get_needle_data() norm = stack.normalize() - assert norm.axes_manager.navigation_shape == \ - stack.axes_manager.navigation_shape - assert norm.axes_manager.signal_shape == \ - stack.axes_manager.signal_shape + assert norm.axes_manager.navigation_shape == stack.axes_manager.navigation_shape + assert norm.axes_manager.signal_shape == stack.axes_manager.signal_shape assert norm.data.min() == 0.0 def test_stack_invert(self): @@ -94,289 +118,343 @@ def test_stack_invert(self): hist_inv, bins_inv = np.histogram(invert.data) assert hist[0] > hist_inv[0] - def test_stack_stats(self): + def test_stack_stats(self, capsys): stack = ds.get_needle_data() - stdout = sys.stdout - sys.stdout = io.StringIO() - stack.stats() - out = sys.stdout.getvalue() - sys.stdout = stdout - out = out.split('\n') + # capture output stream to test print statements + captured = capsys.readouterr() + out = captured.out.split("\n") - assert out[0] == 'Mean: %.1f' % stack.data.mean() - assert out[1] == 'Std: %.2f' % stack.data.std() - assert out[2] == 'Max: %.1f' % stack.data.max() - assert out[3] == 'Min: %.1f' % stack.data.min() + assert out[0] == f"Mean: {stack.data.mean():.1f}" + assert out[1] == f"Std: {stack.data.std():.2f}" + assert out[2] == f"Max: {stack.data.max():.1f}" + assert out[3] == f"Min: {stack.data.min():.1f}" def test_set_tilts(self): stack = ds.get_needle_data() - stack.set_tilts(-50, 5) + start, increment = -50, 5 + stack.set_tilts(start, increment) assert stack.axes_manager[0].name == "Tilt" - assert stack.axes_manager[0].scale == 5 + assert stack.axes_manager[0].scale == increment assert stack.axes_manager[0].units == "degrees" - assert stack.axes_manager[0].offset == -50 - assert stack.axes_manager[0].axis.all() == \ - np.arange(-50, stack.data.shape[0] * 5 + -50, 5).all() + assert stack.axes_manager[0].offset == start + assert ( + stack.axes_manager[0].axis.all() + == np.arange( + start, + stack.data.shape[0] * increment + start, + increment, + ).all() + ) def test_set_tilts_no_metadata(self): stack = ds.get_needle_data() del stack.metadata.Tomography - stack.set_tilts(-50, 5) + start, increment = -50, 5 + stack.set_tilts(start, increment) assert stack.axes_manager[0].name == "Tilt" - assert stack.axes_manager[0].scale == 5 + assert stack.axes_manager[0].scale == increment assert stack.axes_manager[0].units == "degrees" - assert stack.axes_manager[0].offset == -50 - assert stack.axes_manager[0].axis.all() == \ - np.arange(-50, stack.data.shape[0] * 5 + -50, 5).all() + assert stack.axes_manager[0].offset == start + assert ( + stack.axes_manager[0].axis.all() + == np.arange( + start, + stack.data.shape[0] * increment + start, + increment, + ).all() + ) class TestTestAlign: + """Test test alignment of a TomoStack.""" def test_test_align_no_slices(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack.test_align() - fig = matplotlib.pylab.gcf() - assert len(fig.axes) == 3 + fig = plt.gcf() + assert len(fig.axes) == NUM_AXES_THREE def test_test_align_with_angle(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack.test_align(tilt_rotation=3.0) - fig = matplotlib.pylab.gcf() - assert len(fig.axes) == 3 + fig = plt.gcf() + assert len(fig.axes) == NUM_AXES_THREE def test_test_align_with_xshift(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack.test_align(tilt_shift=3.0) - fig = matplotlib.pylab.gcf() - assert len(fig.axes) == 3 + fig = plt.gcf() + assert len(fig.axes) == NUM_AXES_THREE def test_test_align_with_thickness(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack.test_align(thickness=200) - fig = matplotlib.pylab.gcf() - assert len(fig.axes) == 3 + fig = plt.gcf() + assert len(fig.axes) == NUM_AXES_THREE def test_test_align_no_cuda(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack.test_align(thickness=200, cuda=False) - fig = matplotlib.pylab.gcf() - assert len(fig.axes) == 3 + fig = plt.gcf() + assert len(fig.axes) == NUM_AXES_THREE class TestAlignOther: + """Test alignment of another TomoStack from an existing one.""" def test_align_other_no_shifts(self): - stack = ds.get_needle_data(False) + stack = ds.get_needle_data(aligned=False) stack2 = stack.deepcopy() - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="No transformations have been applied to this stack", + ): stack.align_other(stack2) def test_align_other_with_shifts(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack2 = stack.deepcopy() stack3 = stack.align_other(stack2) - assert type(stack3) is TomoStack + assert isinstance(stack3, TomoStack) class TestStackRegister: + """Test StackReg alignment of a TomoStack.""" def test_stack_register_unknown_method(self): - stack = ds.get_needle_data(False).inav[0:5] - with pytest.raises(ValueError): - stack.stack_register('UNKNOWN') + stack = ds.get_needle_data(aligned=False).inav[0:5] + bad_method = "UNKNOWN" + with pytest.raises( + ValueError, + match=f"Unknown registration method: '{bad_method.lower()}'. " + "Must be 'PC', 'StackReg', or 'COM'", + ): + stack.stack_register(bad_method) def test_stack_register_pc(self): - stack = ds.get_needle_data(False).inav[0:5] + stack = ds.get_needle_data(aligned=False).inav[0:5] stack.metadata.Tomography.shifts = np.zeros([5, 2]) - reg = stack.stack_register('PC') - assert type(reg) is TomoStack + reg = stack.stack_register("PC") + assert isinstance(reg, TomoStack) def test_stack_register_com(self): - stack = ds.get_needle_data(False).inav[0:5] + stack = ds.get_needle_data(aligned=False).inav[0:5] stack.metadata.Tomography.shifts = np.zeros([5, 2]) stack.metadata.Tomography.tilts = stack.metadata.Tomography.tilts[0:5] - reg = stack.stack_register('COM') - assert type(reg) is TomoStack + reg = stack.stack_register("COM") + assert isinstance(reg, TomoStack) def test_stack_register_stackreg(self): - stack = ds.get_needle_data(False).inav[0:5] + stack = ds.get_needle_data(aligned=False).inav[0:5] stack.metadata.Tomography.shifts = np.zeros([5, 2]) - reg = stack.stack_register('COM-CL') - assert type(reg) is TomoStack + reg = stack.stack_register("COM-CL") + assert isinstance(reg, TomoStack) def test_stack_register_with_crop(self): - stack = ds.get_needle_data(False).inav[0:5] + stack = ds.get_needle_data(aligned=False).inav[0:5] stack.metadata.Tomography.shifts = np.zeros([5, 2]) - reg = stack.stack_register('PC', crop=True) - assert type(reg) is TomoStack + reg = stack.stack_register("PC", crop=True) + assert isinstance(reg, TomoStack) assert np.sum(reg.data.shape) < np.sum(stack.data.shape) class TestErrorPlots: + """Test error plots for TomoStack.""" def test_sirt_error(self): - stack = ds.get_needle_data(True) - rec_stack, error = stack.recon_error(128, iterations=2, - constrain=True, cuda=False) + stack = ds.get_needle_data(aligned=True) + rec_stack, error = stack.recon_error( + 128, + iterations=2, + constrain=True, + cuda=False, + ) assert error.data.shape[0] == rec_stack.data.shape[0] - assert rec_stack.data.shape[1:] ==\ - (stack.data.shape[1], stack.data.shape[1]) + assert rec_stack.data.shape[1:] == (stack.data.shape[1], stack.data.shape[1]) def test_sirt_error_no_slice(self): - stack = ds.get_needle_data(True) - rec_stack, error = stack.recon_error(None, iterations=2, - constrain=True, cuda=False) + stack = ds.get_needle_data(aligned=True) + rec_stack, error = stack.recon_error( + None, + iterations=2, + constrain=True, + cuda=False, + ) assert error.data.shape[0] == rec_stack.data.shape[0] - assert rec_stack.data.shape[1:] ==\ - (stack.data.shape[1], stack.data.shape[1]) - assert (1 - (3.8709e12 / error.data[0])) < 0.001 - assert (1 - (2.8624e12 / error.data[1])) < 0.001 + assert rec_stack.data.shape[1:] == (stack.data.shape[1], stack.data.shape[1]) def test_sirt_error_no_cuda(self): - stack = ds.get_needle_data(True) - rec_stack, error = stack.recon_error(128, iterations=50, - constrain=True, cuda=None) + stack = ds.get_needle_data(aligned=True) + rec_stack, error = stack.recon_error( + 128, + iterations=50, + constrain=True, + cuda=None, + ) assert error.data.shape[0] == rec_stack.data.shape[0] - assert rec_stack.data.shape[1:] ==\ - (stack.data.shape[1], stack.data.shape[1]) - assert (1 - (3.8709e12 / error.data[0])) < 0.001 - assert (1 - (2.8624e12 / error.data[1])) < 0.001 + assert rec_stack.data.shape[1:] == (stack.data.shape[1], stack.data.shape[1]) def test_sart_error(self): - stack = ds.get_needle_data(True) - rec_stack, error = stack.recon_error(128, algorithm='SART', iterations=2, - constrain=True, cuda=False) + stack = ds.get_needle_data(aligned=True) + rec_stack, error = stack.recon_error( + 128, + algorithm="SART", + iterations=2, + constrain=True, + cuda=False, + ) assert error.data.shape[0] == rec_stack.data.shape[0] - assert rec_stack.data.shape[1:] ==\ - (stack.data.shape[1], stack.data.shape[1]) + assert rec_stack.data.shape[1:] == (stack.data.shape[1], stack.data.shape[1]) def test_sart_error_no_slice(self): - stack = ds.get_needle_data(True) - rec_stack, error = stack.recon_error(None, algorithm='SART', iterations=2, - constrain=True, cuda=False) + stack = ds.get_needle_data(aligned=True) + rec_stack, error = stack.recon_error( + None, + algorithm="SART", + iterations=2, + constrain=True, + cuda=False, + ) assert error.data.shape[0] == rec_stack.data.shape[0] - assert rec_stack.data.shape[1:] ==\ - (stack.data.shape[1], stack.data.shape[1]) - assert (1 - (3.8709e12 / error.data[0])) < 0.001 - assert (1 - (2.8624e12 / error.data[1])) < 0.001 + assert rec_stack.data.shape[1:] == (stack.data.shape[1], stack.data.shape[1]) def test_sart_error_no_cuda(self): - stack = ds.get_needle_data(True) - rec_stack, error = stack.recon_error(128, algorithm='SART', iterations=50, - constrain=True, cuda=None) + stack = ds.get_needle_data(aligned=True) + rec_stack, error = stack.recon_error( + 128, + algorithm="SART", + iterations=50, + constrain=True, + cuda=None, + ) assert error.data.shape[0] == rec_stack.data.shape[0] - assert rec_stack.data.shape[1:] ==\ - (stack.data.shape[1], stack.data.shape[1]) - assert (1 - (3.8709e12 / error.data[0])) < 0.001 - assert (1 - (2.8624e12 / error.data[1])) < 0.001 + assert rec_stack.data.shape[1:] == (stack.data.shape[1], stack.data.shape[1]) class TestTiltAlign: + """Test tilt alignment of a TomoStack.""" def test_tilt_align_com_axis_zero(self): - stack = ds.get_needle_data(True) - ali = stack.tilt_align('CoM', locs=[64, 100, 114]) - assert type(ali) is TomoStack + stack = ds.get_needle_data(aligned=True) + ali = stack.tilt_align("CoM", locs=[64, 100, 114]) + assert isinstance(ali, TomoStack) def test_tilt_align_maximage(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack = stack.inav[0:10] - ali = stack.tilt_align('MaxImage') - assert type(ali) is TomoStack + ali = stack.tilt_align("MaxImage") + assert isinstance(ali, TomoStack) def test_tilt_align_unknown_method(self): - stack = ds.get_needle_data(True) - with pytest.raises(ValueError): - stack.tilt_align('UNKNOWN') + stack = ds.get_needle_data(aligned=True) + bad_method = "UNKNOWN" + with pytest.raises( + ValueError, + match=f"Invalid alignment method: '{bad_method.lower()}'." + " Must be 'CoM' or 'MaxImage'", + ): + stack.tilt_align(bad_method) class TestTransStack: + """Test translation of a TomoStack.""" def test_test_trans_stack_linear(self): - stack = ds.get_needle_data(True) - shifted = stack.trans_stack(1, 1, 1, 'linear') - assert type(shifted) is TomoStack + stack = ds.get_needle_data(aligned=True) + shifted = stack.trans_stack(1, 1, 1, "linear") + assert isinstance(shifted, TomoStack) def test_test_trans_stack_nearest(self): - stack = ds.get_needle_data(True) - shifted = stack.trans_stack(1, 1, 1, 'nearest') - assert type(shifted) is TomoStack + stack = ds.get_needle_data(aligned=True) + shifted = stack.trans_stack(1, 1, 1, "nearest") + assert isinstance(shifted, TomoStack) def test_test_trans_stack_cubic(self): - stack = ds.get_needle_data(True) - shifted = stack.trans_stack(1, 1, 1, 'cubic') - assert type(shifted) is TomoStack + stack = ds.get_needle_data(aligned=True) + shifted = stack.trans_stack(1, 1, 1, "cubic") + assert isinstance(shifted, TomoStack) def test_test_trans_stack_unknown(self): - stack = ds.get_needle_data(True) - with pytest.raises(ValueError): - stack.trans_stack(1, 1, 1, 'UNKNOWN') + stack = ds.get_needle_data(aligned=True) + bad_method = "UNKNOWN" + with pytest.raises( + ValueError, + match=f"Interpolation method '{bad_method}' unknown. " + "Must be 'nearest', 'linear', or 'cubic'", + ): + stack.trans_stack(1, 1, 1, bad_method) class TestReconstruct: + """Test reconstruction of a TomoStack.""" + def test_cuda_detect(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[:, 120:121].deepcopy() - rec = slices.reconstruct('FBP', cuda=None) - assert type(rec) is RecStack + rec = slices.reconstruct("FBP", cuda=None) + assert isinstance(rec, RecStack) class TestManualAlign: + """Test manual alignment of a TomoStack.""" + def test_manual_align_positive_x(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) shifted = stack.manual_align(128, xshift=10) - assert type(shifted) is TomoStack + assert isinstance(shifted, TomoStack) def test_manual_align_negative_x(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) shifted = stack.manual_align(128, xshift=-10) - assert type(shifted) is TomoStack + assert isinstance(shifted, TomoStack) def test_manual_align_positive_y(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) shifted = stack.manual_align(128, yshift=10) - assert type(shifted) is TomoStack + assert isinstance(shifted, TomoStack) def test_manual_align_negative_y(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) shifted = stack.manual_align(128, yshift=-10) - assert type(shifted) is TomoStack + assert isinstance(shifted, TomoStack) def test_manual_align_negative_y_positive_x(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) shifted = stack.manual_align(128, yshift=-10, xshift=10) - assert type(shifted) is TomoStack + assert isinstance(shifted, TomoStack) def test_manual_align_negative_x_positive_y(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) shifted = stack.manual_align(128, yshift=10, xshift=-10) - assert type(shifted) is TomoStack + assert isinstance(shifted, TomoStack) def test_manual_align_negative_y_negative_x(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) shifted = stack.manual_align(128, yshift=-10, xshift=-10) - assert type(shifted) is TomoStack + assert isinstance(shifted, TomoStack) def test_manual_align_positive_y_positive_x(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) shifted = stack.manual_align(128, yshift=10, xshift=10) - assert type(shifted) is TomoStack + assert isinstance(shifted, TomoStack) def test_manual_align_no_shifts(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) shifted = stack.manual_align(128) - assert type(shifted) is TomoStack + assert isinstance(shifted, TomoStack) def test_manual_align_with_display(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) shifted = stack.manual_align(64, display=True) - assert type(shifted) is TomoStack + assert isinstance(shifted, TomoStack) class TestPlotSlices: + """Test plotting slices of a TomoStack.""" + def test_plot_slices(self): rec = RecStack(np.zeros([10, 10, 10])) fig = rec.plot_slices() - assert type(fig) is matplotlib.figure.Figure + assert isinstance(fig, Figure) diff --git a/etspy/tests/test_cuda.py b/etspy/tests/test_cuda.py index 0917363..14f15db 100644 --- a/etspy/tests/test_cuda.py +++ b/etspy/tests/test_cuda.py @@ -1,130 +1,171 @@ -import etspy.datasets as ds -import matplotlib -import etspy -import numpy -from etspy import recon -from etspy.base import TomoStack +"""Tests for the CUDA-enabled functionality of ETSpy.""" + +from typing import Tuple, cast + import astra +import matplotlib.pyplot as plt +import numpy as np import pytest +import etspy.datasets as ds +from etspy import recon +from etspy.base import RecStack, TomoStack + +NUM_FIG_AXES = 3 + @pytest.mark.skipif(not astra.use_cuda(), reason="CUDA not detected") class TestAlignCUDA: + """Test alignment of a TomoStack using CUDA.""" + def test_test_align_cuda(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack.test_align(thickness=200, cuda=True) - fig = matplotlib.pylab.gcf() - assert len(fig.axes) == 3 + fig = plt.gcf() + assert len(fig.axes) == NUM_FIG_AXES @pytest.mark.skipif(not astra.use_cuda(), reason="CUDA not detected") class TestReconCUDA: + """Test reconstruction of a TomoStack using CUDA.""" + def test_recon_fbp_gpu(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - rec = slices.reconstruct('FBP', cuda=True) - assert type(stack) is etspy.base.TomoStack - assert type(rec) is etspy.base.RecStack + rec = slices.reconstruct("FBP", cuda=True) + assert isinstance(stack, TomoStack) + assert isinstance(rec, RecStack) assert rec.data.shape[2] == slices.data.shape[1] def test_recon_sirt_gpu(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - rec = slices.reconstruct('SIRT', - constrain=True, - iterations=2, - thresh=0, - cuda=True) - assert type(stack) is etspy.base.TomoStack - assert type(rec) is etspy.base.RecStack + rec = slices.reconstruct( + "SIRT", + constrain=True, + iterations=2, + thresh=0, + cuda=True, + ) + assert isinstance(stack, TomoStack) + assert isinstance(rec, RecStack) assert rec.data.shape[2] == slices.data.shape[1] def test_recon_sart_gpu(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - rec = slices.reconstruct('SART', - constrain=True, - iterations=2, - thresh=0, - cuda=True) - assert type(stack) is etspy.base.TomoStack - assert type(rec) is etspy.base.RecStack + rec = slices.reconstruct( + "SART", + constrain=True, + iterations=2, + thresh=0, + cuda=True, + ) + assert isinstance(stack, TomoStack) + assert isinstance(rec, RecStack) assert rec.data.shape[2] == slices.data.shape[1] def test_recon_dart_gpu(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - gray_levels = [0., slices.data.max() / 2, slices.data.max()] - rec = slices.reconstruct('DART', - constrain=True, - iterations=2, - thresh=0, - cuda=True, - gray_levels=gray_levels, - dart_iterations=1) - assert type(stack) is etspy.base.TomoStack - assert type(rec) is etspy.base.RecStack + gray_levels = [0.0, slices.data.max() / 2, slices.data.max()] + rec = slices.reconstruct( + "DART", + constrain=True, + iterations=2, + thresh=0, + cuda=True, + gray_levels=gray_levels, + dart_iterations=1, + ) + assert isinstance(stack, TomoStack) + assert isinstance(rec, RecStack) assert rec.data.shape[2] == slices.data.shape[1] @pytest.mark.skipif(not astra.use_cuda(), reason="CUDA not detected") class TestAstraSIRTGPU: + """Test SIRT TomoStack reconstruction using Astra toolbox.""" + def test_astra_sirt_error_gpu(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) [ntilts, ny, nx] = stack.data.shape angles = stack.metadata.Tomography.tilts sino = stack.isig[120, :].data - rec_stack, error = recon.astra_error(sino, angles, iterations=2, - constrain=True, thresh=0, cuda=True) - assert type(error) is numpy.ndarray + rec_stack, error = recon.astra_error( + sino, + angles, + iterations=2, + constrain=True, + thresh=0, + cuda=True, + ) + assert isinstance(error, np.ndarray) assert rec_stack.shape == (2, ny, ny) @pytest.mark.skipif(not astra.use_cuda(), reason="CUDA not detected") class TestReconRunCUDA: + """Test reconstruction of TomoStack using CUDA features.""" + def test_run_fbp_cuda(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - rec = recon.run(slices, 'FBP', cuda=True) - assert rec.data.shape == (1, slices.data.shape[1], slices.data.shape[1]) - assert rec.data.shape[0] == slices.data.shape[2] - assert type(rec) is numpy.ndarray + rec = recon.run(slices, "FBP", cuda=True) + data_shape = cast(Tuple[int, int, int], rec.data.shape) + assert data_shape == (1, slices.data.shape[1], slices.data.shape[1]) + assert data_shape[0] == slices.data.shape[2] + assert isinstance(rec, np.ndarray) def test_run_sirt_cuda(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - rec = recon.run(slices, 'SIRT', niterations=2, cuda=True) - assert rec.data.shape == (1, slices.data.shape[1], slices.data.shape[1]) - assert rec.data.shape[0] == slices.data.shape[2] - assert type(rec) is numpy.ndarray + rec = recon.run(slices, "SIRT", niterations=2, cuda=True) + data_shape = cast(Tuple[int, int, int], rec.data.shape) + assert data_shape == (1, slices.data.shape[1], slices.data.shape[1]) + assert data_shape[0] == slices.data.shape[2] + assert isinstance(rec, np.ndarray) def test_run_sart_cuda(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - rec = recon.run(slices, 'SART', niterations=2, cuda=True) - assert rec.data.shape == (1, slices.data.shape[1], slices.data.shape[1]) - assert rec.data.shape[0] == slices.data.shape[2] - assert type(rec) is numpy.ndarray + rec = recon.run(slices, "SART", niterations=2, cuda=True) + data_shape = cast(Tuple[int, int, int], rec.data.shape) + assert data_shape == (1, slices.data.shape[1], slices.data.shape[1]) + assert data_shape[0] == slices.data.shape[2] + assert isinstance(rec, np.ndarray) def test_run_dart_cuda(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - gray_levels = [0., slices.data.max() / 2, slices.data.max()] - rec = recon.run(slices, 'DART', niterations=2, cuda=False, gray_levels=gray_levels, dart_iterations=1) - assert rec.data.shape == (1, slices.data.shape[1], slices.data.shape[1]) - assert rec.data.shape[0] == slices.data.shape[2] - assert type(rec) is numpy.ndarray + gray_levels = [0.0, slices.data.max() / 2, slices.data.max()] + rec = recon.run( + slices, + "DART", + niterations=2, + cuda=False, + gray_levels=gray_levels, + dart_iterations=1, + ) + data_shape = cast(Tuple[int, int, int], rec.data.shape) + assert data_shape == (1, slices.data.shape[1], slices.data.shape[1]) + assert data_shape[0] == slices.data.shape[2] + assert isinstance(rec, np.ndarray) @pytest.mark.skipif(not astra.use_cuda(), reason="CUDA not detected") class TestStackRegisterCUDA: + """Test StackReg alignment of a TomoStack using CUDA.""" + def test_register_pc_cuda(self): - stack = ds.get_needle_data() - stack.metadata.Tomography.shifts = \ - stack.metadata.Tomography.shifts[0:20] - reg = stack.inav[0:20].stack_register('PC', cuda=True) - assert type(reg) is TomoStack - assert reg.axes_manager.signal_shape == \ - stack.inav[0:20].axes_manager.signal_shape - assert reg.axes_manager.navigation_shape == \ - stack.inav[0:20].axes_manager.navigation_shape + stack = ds.get_needle_data(aligned=False) + stack.metadata.Tomography.shifts = stack.metadata.Tomography.shifts[0:20] + reg = stack.inav[0:20].stack_register("PC", cuda=True) + assert isinstance(reg, TomoStack) + assert ( + reg.axes_manager.signal_shape == stack.inav[0:20].axes_manager.signal_shape + ) + assert ( + reg.axes_manager.navigation_shape + == stack.inav[0:20].axes_manager.navigation_shape + ) diff --git a/etspy/tests/test_datasets.py b/etspy/tests/test_datasets.py index 62fb14d..09ddb12 100644 --- a/etspy/tests/test_datasets.py +++ b/etspy/tests/test_datasets.py @@ -1,31 +1,36 @@ +"""Test dataset generation features of ETSpy.""" + from etspy import datasets as ds -import etspy.api as etspy +from etspy.api import TomoStack class TestExptlDatasetRetrieval: + """Test retrieving experimental data.""" def test_get_experimental_data(self): stack = ds.get_needle_data() - assert type(stack) is etspy.TomoStack + assert isinstance(stack, TomoStack) def test_get_aligned_experimental_data(self): stack = ds.get_needle_data(aligned=True) - assert type(stack) is etspy.TomoStack + assert isinstance(stack, TomoStack) class TestSimDatasetRetrieval: + """Test retrieving simulated data.""" + def test_get_simulated_series(self): stack = ds.get_catalyst_data() - assert type(stack) is etspy.TomoStack + assert isinstance(stack, TomoStack) - def test_get_misaligned_simulated_series(self, misalign=True): + def test_get_misaligned_simulated_series(self): stack = ds.get_catalyst_data(misalign=True) - assert type(stack) is etspy.TomoStack + assert isinstance(stack, TomoStack) def test_get_noisy_simulated_series(self): stack = ds.get_catalyst_data(noise=True) - assert type(stack) is etspy.TomoStack + assert isinstance(stack, TomoStack) - def test_get_noisy_misaligned_simulated_series(self, noise=True, misalign=True): - stack = ds.get_catalyst_data() - assert type(stack) is etspy.TomoStack + def test_get_noisy_misaligned_simulated_series(self): + stack = ds.get_catalyst_data(noise=True, misalign=True) + assert isinstance(stack, TomoStack) diff --git a/etspy/tests/test_io.py b/etspy/tests/test_io.py index bbac6b8..118ae5f 100644 --- a/etspy/tests/test_io.py +++ b/etspy/tests/test_io.py @@ -1,24 +1,21 @@ -import etspy.api as etspy -from etspy.base import TomoStack +"""Tests for the IO functionality of ETSpy.""" + +from typing import cast + +import h5py import hyperspy.api as hs -import os import numpy as np import pytest -import h5py -import glob +from h5py import Dataset +from hyperspy.io import load as hs_load from hyperspy.signals import Signal2D -etspy_path = os.path.dirname(etspy.__file__) - - -def hspy_mrc_reader_check(): - dirname = os.path.join(etspy_path, "tests", - "test_data", "SerialEM_Multiframe_Test") - files = glob.glob(dirname + "/*.mrc") - file = files[0] - s = hs.load(file) - return s +import etspy.api as etspy +from etspy.api import etspy_path +from etspy.base import TomoStack +from etspy.io import MismatchedTiltError +from . import hspy_mrc_reader_check try: hspy_mrc_reader_check() @@ -29,174 +26,184 @@ def hspy_mrc_reader_check(): class TestLoadMRC: + """Test loading an MRC file.""" + def test_load_mrc(self): - filename = os.path.join(etspy_path, "tests", - "test_data", "HAADF.mrc") - stack_orig = hs.load(filename) + filename = etspy_path / "tests" / "test_data" / "HAADF.mrc" + stack_orig = hs_load(filename) stack = etspy.io.load(filename) assert stack.axes_manager.signal_shape == (256, 256) assert stack.axes_manager.navigation_shape == (77,) - assert stack.metadata.has_item('Tomography') - assert type(stack.metadata.Tomography.tilts) is np.ndarray + assert stack.metadata.has_item("Tomography") + assert isinstance(stack.metadata.Tomography.tilts, np.ndarray) assert stack.metadata.Tomography.tilts.shape[0] == stack.data.shape[0] - assert stack.metadata.Tomography.tilts[0] == -76 - assert type(stack) is TomoStack + assert stack.metadata.Tomography.tilts[0] == -76 # noqa: PLR2004 + assert isinstance(stack, TomoStack) assert stack.axes_manager[1].scale == stack_orig.axes_manager[1].scale assert stack.axes_manager[1].units == stack_orig.axes_manager[1].units def test_load_ali(self): - filename = os.path.join(etspy_path, "tests", - "test_data", "HAADF.ali") - stack_orig = hs.load(filename) + filename = etspy_path / "tests" / "test_data" / "HAADF.ali" + stack_orig = hs_load(filename) stack = etspy.io.load(filename) assert stack.axes_manager.signal_shape == (256, 256) assert stack.axes_manager.navigation_shape == (77,) - assert stack.metadata.has_item('Tomography') - assert type(stack.metadata.Tomography.tilts) is np.ndarray + assert stack.metadata.has_item("Tomography") + assert isinstance(stack.metadata.Tomography.tilts, np.ndarray) assert stack.metadata.Tomography.tilts.shape[0] == stack.data.shape[0] - assert stack.metadata.Tomography.tilts[0] == -76 - assert type(stack) is TomoStack + assert stack.metadata.Tomography.tilts[0] == -76 # noqa: PLR2004 + assert isinstance(stack, TomoStack) assert stack.axes_manager[1].scale == stack_orig.axes_manager[1].scale assert stack.axes_manager[1].units == stack_orig.axes_manager[1].units def test_load_mrc_with_rawtlt(self): - filename = os.path.join(etspy_path, "tests", - "test_data", "HAADF.mrc") + filename = etspy_path / "tests" / "test_data" / "HAADF.mrc" stack = etspy.io.load(filename) del stack.original_metadata.fei_header del stack.original_metadata.std_header tilts = etspy.io.get_mrc_tilts(stack, filename) - assert type(tilts) is np.ndarray + assert isinstance(tilts, np.ndarray) assert tilts.shape[0] == stack.data.shape[0] def test_load_mrc_with_bad_rawtlt(self): - filename = os.path.join(etspy_path, "tests", - "test_data", "HAADF.mrc") + filename = etspy_path / "tests" / "test_data" / "HAADF.mrc" stack = etspy.io.load(filename) del stack.original_metadata.fei_header del stack.original_metadata.std_header - stack.data = np.append(stack.data, np.zeros([1, stack.data.shape[1], stack.data.shape[2]]), axis=0) - with pytest.raises(ValueError): + stack.data = np.append( + stack.data, + np.zeros([1, stack.data.shape[1], stack.data.shape[2]]), + axis=0, + ) + with pytest.raises( + ValueError, + match="Number of tilts in .rawtlt file inconsistent with data shape", + ): etspy.io.get_mrc_tilts(stack, filename) class TestHspy: + """Test loading a HyperSpy signal.""" + def test_convert_signal2d(self): tilts = np.arange(-10, 10, 2) data = hs.signals.Signal2D(np.zeros([10, 100, 100])) stack = etspy.io.create_stack(data, tilts=tilts) assert stack.axes_manager.signal_shape == (100, 100) assert stack.axes_manager.navigation_shape == (10,) - assert stack.metadata.has_item('Tomography') - assert type(stack.metadata.Tomography.tilts) is np.ndarray + assert stack.metadata.has_item("Tomography") + assert isinstance(stack.metadata.Tomography.tilts, np.ndarray) assert stack.metadata.Tomography.tilts.shape[0] == stack.data.shape[0] - assert stack.metadata.Tomography.tilts[0] == -10 - assert type(stack) is TomoStack + assert stack.metadata.Tomography.tilts[0] == -10 # noqa: PLR2004 + assert isinstance(stack, TomoStack) def test_load_hspy_hdf5(self): - filename = os.path.join(etspy_path, "tests", - "test_data", "HAADF_Aligned.hdf5") - stack_orig = hs.load(filename, reader='HSPY') + filename = etspy_path / "tests" / "test_data" / "HAADF_Aligned.hdf5" + stack_orig = hs_load(filename, reader="HSPY") stack = etspy.io.load(filename) - with h5py.File(filename, 'r') as h5: - h5_shape = h5['Experiments']['__unnamed__']['data'].shape + with h5py.File(filename, "r") as h5: + h5_data = h5.get("/Experiments/__unnamed__/data") + h5_data = cast(Dataset, h5_data) # cast for type-checking + h5_shape = h5_data.shape assert stack.data.shape[1:] == h5_shape[1:] assert stack.data.shape[0] == h5_shape[0] - assert stack.metadata.has_item('Tomography') - assert type(stack.metadata.Tomography.tilts) is np.ndarray - assert stack.metadata.Tomography.tilts[0] == -76 - assert type(stack) is TomoStack + assert stack.metadata.has_item("Tomography") + assert isinstance(stack.metadata.Tomography.tilts, np.ndarray) + assert stack.metadata.Tomography.tilts[0] == -76 # noqa: PLR2004 + assert isinstance(stack, TomoStack) assert stack.axes_manager[1].scale == stack_orig.axes_manager[1].scale assert stack.axes_manager[1].units == stack_orig.axes_manager[1].units class TestNumpy: + """Test creating a TomoStack from NumPy arrays.""" + def test_numpy_to_stack_no_tilts(self): - stack = etspy.io.create_stack( - np.random.random([50, 100, 100]), tilts=None) - assert type(stack) is etspy.TomoStack + stack = etspy.io.create_stack(np.random.random([50, 100, 100]), tilts=None) + assert isinstance(stack, etspy.TomoStack) assert stack.axes_manager.signal_shape == (100, 100) assert stack.axes_manager.navigation_shape == (50,) - assert stack.metadata.has_item('Tomography') - assert type(stack.metadata.Tomography.tilts) is np.ndarray + assert stack.metadata.has_item("Tomography") + assert isinstance(stack.metadata.Tomography.tilts, np.ndarray) assert stack.metadata.Tomography.tilts[0] == 0 assert stack.metadata.Tomography.tilts.shape[0] == stack.data.shape[0] - assert type(stack) is TomoStack + assert isinstance(stack, TomoStack) def test_numpy_to_stack_with_bad_tilts(self): tilts = np.arange(-50, 50, 2) data = np.random.random([25, 100, 100]) - with pytest.raises(ValueError): + with pytest.raises(MismatchedTiltError): etspy.io.create_stack(data, tilts=tilts) def test_numpy_to_stack_with_tilts(self): tilts = np.arange(-50, 50, 2) - stack = etspy.io.create_stack( - np.random.random([50, 100, 100]), tilts=tilts) - assert type(stack) is etspy.TomoStack + stack = etspy.io.create_stack(np.random.random([50, 100, 100]), tilts=tilts) + assert isinstance(stack, etspy.TomoStack) assert stack.axes_manager.signal_shape == (100, 100) assert stack.axes_manager.navigation_shape == (50,) - assert stack.metadata.has_item('Tomography') - assert type(stack.metadata.Tomography.tilts) is np.ndarray - assert stack.metadata.Tomography.tilts[0] == -50 + assert stack.metadata.has_item("Tomography") + assert isinstance(stack.metadata.Tomography.tilts, np.ndarray) + assert stack.metadata.Tomography.tilts[0] == -50 # noqa: PLR2004 assert stack.metadata.Tomography.tilts.shape[0] == stack.data.shape[0] - assert type(stack) is TomoStack + assert isinstance(stack, TomoStack) class TestSignal: + """Test creating stacks from HyperSpy signals.""" + def test_signal_to_stack(self): signal = hs.signals.Signal2D(np.random.random([50, 100, 100])) stack = etspy.io.create_stack(signal) - assert stack.axes_manager[0].name == 'Tilt' + assert stack.axes_manager[0].name == "Tilt" assert stack.axes_manager.signal_shape == (100, 100) assert stack.axes_manager.navigation_shape == (50,) - assert stack.metadata.has_item('Tomography') - assert type(stack.metadata.Tomography.tilts) is np.ndarray + assert stack.metadata.has_item("Tomography") + assert isinstance(stack.metadata.Tomography.tilts, np.ndarray) assert stack.metadata.Tomography.tilts[0] == 0 - assert type(stack) is TomoStack + assert isinstance(stack, TomoStack) assert stack.axes_manager[1].scale == signal.axes_manager[1].scale assert stack.axes_manager[1].units == signal.axes_manager[1].units def test_signal_to_stack_bad_tilts(self): signal = hs.signals.Signal2D(np.random.random([50, 100, 100])) tilts = np.zeros(20) - with pytest.raises(ValueError): + with pytest.raises(MismatchedTiltError): etspy.io.create_stack(signal, tilts) class TestDM: + """Test loading DigitalMicrograph data.""" + def test_load_single_dm(self): - filename = os.path.join(etspy_path, "tests", - "test_data", "HAADF.dm3") - signal = hs.load(filename) + filename = etspy_path / "tests" / "test_data" / "HAADF.dm3" + signal = hs_load(filename) stack = etspy.load(filename) - assert stack.axes_manager[0].name == 'Tilt' + assert stack.axes_manager[0].name == "Tilt" assert stack.axes_manager.signal_shape == (64, 64) assert stack.axes_manager.navigation_shape == (91,) - assert stack.metadata.has_item('Tomography') - assert type(stack.metadata.Tomography.tilts) is np.ndarray + assert stack.metadata.has_item("Tomography") + assert isinstance(stack.metadata.Tomography.tilts, np.ndarray) assert stack.metadata.Tomography.tilts[0] < 0 - assert type(stack) is TomoStack + assert isinstance(stack, TomoStack) assert stack.axes_manager[1].scale == signal.axes_manager[1].scale assert stack.axes_manager[1].units == signal.axes_manager[1].units assert stack.axes_manager[2].scale == signal.axes_manager[2].scale assert stack.axes_manager[2].units == signal.axes_manager[2].units def test_load_dm_series(self): - dirname = os.path.join(etspy_path, "tests", - "test_data", "DM_Series_Test") - files = glob.glob(dirname + "/*.dm3") - signal = hs.load(files, stack=True) + dirname = etspy_path / "tests" / "test_data" / "DM_Series_Test" + files = list(dirname.glob("*.dm3")) + signal = hs_load(files, stack=True) stack = etspy.load(files) - assert stack.axes_manager[0].name == 'Tilt' + assert stack.axes_manager[0].name == "Tilt" assert stack.axes_manager.signal_shape == (128, 128) assert stack.axes_manager.navigation_shape == (3,) - assert stack.metadata.has_item('Tomography') - assert type(stack.metadata.Tomography.tilts) is np.ndarray + assert stack.metadata.has_item("Tomography") + assert isinstance(stack.metadata.Tomography.tilts, np.ndarray) assert stack.metadata.Tomography.tilts[0] < 0 - assert type(stack) is TomoStack + assert isinstance(stack, TomoStack) assert stack.axes_manager[1].scale == signal.axes_manager[1].scale assert stack.axes_manager[1].units == signal.axes_manager[1].units assert stack.axes_manager[2].scale == signal.axes_manager[2].scale @@ -205,65 +212,67 @@ def test_load_dm_series(self): @pytest.mark.skipif(hspy_mrc_broken is True, reason="Hyperspy MRC reader broken") class TestSerialEM: + """Test loading SerialEM data.""" + def test_load_serialem_series(self): - dirname = os.path.join(etspy_path, "tests", - "test_data", "SerialEM_Multiframe_Test") - files = glob.glob(dirname + "/*.mrc") + dirname = etspy_path / "tests" / "test_data" / "SerialEM_Multiframe_Test" + files = list(dirname.glob("*.mrc")) stack = etspy.load(files) assert stack.axes_manager.signal_shape == (1024, 1024) assert stack.axes_manager.navigation_shape == (2, 3) - assert stack.metadata.has_item('Tomography') - assert type(stack.metadata.Tomography.tilts) is np.ndarray + assert stack.metadata.has_item("Tomography") + assert isinstance(stack.metadata.Tomography.tilts, np.ndarray) assert stack.metadata.Tomography.tilts[0] < 0 - assert type(stack) is TomoStack + assert isinstance(stack, TomoStack) def test_load_serialem(self): - dirname = os.path.join(etspy_path, "tests", - "test_data", "SerialEM_Multiframe_Test") - # file = glob.glob(dirname + "/*.mrc")[0] - stack = etspy.load(dirname + "/test_000.mrc") + dirname = etspy_path / "tests" / "test_data" / "SerialEM_Multiframe_Test" + stack = etspy.load(dirname / "test_000.mrc") assert stack.axes_manager.signal_shape == (1024, 1024) assert stack.axes_manager.navigation_shape == (2,) - assert stack.metadata.has_item('Tomography') - assert type(stack.metadata.Tomography.tilts) is np.ndarray + assert stack.metadata.has_item("Tomography") + assert isinstance(stack.metadata.Tomography.tilts, np.ndarray) assert stack.metadata.Tomography.tilts[0] == 0.0 - assert type(stack) is TomoStack + assert isinstance(stack, TomoStack) def test_load_serial_em_explicit(self): - dirname = os.path.join(etspy_path, "tests", - "test_data", "SerialEM_Multiframe_Test") - mrcfile = glob.glob(dirname + "/*.mrc")[0] - mdocfile = mrcfile[:-3] + "mdoc" + dirname = etspy_path / "tests" / "test_data" / "SerialEM_Multiframe_Test" + mrcfile = next(dirname.glob("*.mrc")) + mdocfile = mrcfile.with_suffix(".mdoc") stack = etspy.io.load_serialem(mrcfile, mdocfile) - assert type(stack) is Signal2D + assert isinstance(stack, Signal2D) class TestUnknown: + """Tests to ensure unknown filename types generate the expected errors.""" + def test_load_unknown_string(self): - filename = os.path.join(etspy_path, "tests", - "test_data", "HAADF.NONE") + filename = etspy_path / "tests" / "test_data" / "HAADF.NONE" with pytest.raises(TypeError): etspy.load(filename) def test_load_unknown_list(self): - filename = os.path.join(etspy_path, "tests", - "test_data", "HAADF.NONE") + filename = etspy_path / "tests" / "test_data" / "HAADF.NONE" files = [filename, filename] with pytest.raises(TypeError): - etspy.load(files) + etspy.load(files) # pyright: ignore[reportArgumentType] def test_load_unknown_type(self): filename = np.zeros(10) with pytest.raises(TypeError): - etspy.load(filename) + etspy.load(filename) # pyright: ignore[reportArgumentType] class TestMRCHeader: + """Test reading an MRC file header.""" + def test_mrc_header_parser(self): - filename = os.path.join(etspy_path, "tests", - "test_data", "HAADF.mrc") + filename = etspy_path / "tests" / "test_data" / "HAADF.mrc" header = etspy.io.parse_mrc_header(filename) - print(header) - assert type(header) is dict - assert header['nx'] == 256 - assert header['nextra'] == 131072 + expected = { + "nx": 256, + "nextra": 131072, + } + assert isinstance(header, dict) + assert header["nx"] == expected["nx"] + assert header["nextra"] == expected["nextra"] diff --git a/etspy/tests/test_recon.py b/etspy/tests/test_recon.py index a4e6f35..4d330db 100644 --- a/etspy/tests/test_recon.py +++ b/etspy/tests/test_recon.py @@ -1,145 +1,202 @@ -import etspy -from etspy import recon -from etspy import datasets as ds -import numpy +"""Test the reconstruction module of ETSpy.""" + +from typing import Tuple, cast + +import numpy as np import pytest +from etspy import datasets as ds +from etspy import recon +from etspy.base import RecStack, TomoStack + class TestReconstruction: + """Test TomoStack reconstruction methods.""" def test_recon_no_tilts(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack.metadata.Tomography.tilts = None slices = stack.isig[120:121, :].deepcopy() with pytest.raises(TypeError): - slices.reconstruct('FBP') + slices.reconstruct("FBP") def test_recon_single_slice(self): - stack = ds.get_needle_data(True) - # tilts = stack.metadata.Tomography.tilts + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120, :] - rec = recon.run(slices, 'FBP', cuda=False) - assert type(stack) is etspy.base.TomoStack - assert type(rec) is numpy.ndarray - assert rec.data.shape[2] == slices.data.shape[1] + rec = recon.run(slices, "FBP", cuda=False) + assert isinstance(stack, TomoStack) + assert isinstance(rec, np.ndarray) + data_shape = rec.data.shape + data_shape = cast(Tuple[int, int, int], data_shape) # cast for type checking + assert data_shape[2] == slices.data.shape[1] def test_recon_unknown_algorithm(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - with pytest.raises(ValueError): - slices.reconstruct('UNKNOWN') + bad_method = "UNKNOWN" + with pytest.raises( + ValueError, + match=f"Unknown reconstruction algorithm: '{bad_method}'", + ): + slices.reconstruct(bad_method) def test_recon_fbp_cpu(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - rec = slices.reconstruct('FBP', cuda=False) - assert type(stack) is etspy.base.TomoStack - assert type(rec) is etspy.base.RecStack + rec = slices.reconstruct("FBP", cuda=False) + assert isinstance(stack, TomoStack) + assert isinstance(rec, RecStack) assert rec.data.shape[2] == slices.data.shape[1] def test_recon_fbp_cpu_multicore(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:122, :].deepcopy() - rec = slices.reconstruct('FBP', cuda=False) - assert type(stack) is etspy.base.TomoStack - assert type(rec) is etspy.base.RecStack + rec = slices.reconstruct("FBP", cuda=False) + assert isinstance(stack, TomoStack) + assert isinstance(rec, RecStack) assert rec.data.shape[2] == slices.data.shape[1] def test_recon_sirt_cpu(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - rec = slices.reconstruct('SIRT', - constrain=True, - iterations=2, - thresh=0, - cuda=False) - assert type(stack) is etspy.base.TomoStack - assert type(rec) is etspy.base.RecStack + rec = slices.reconstruct( + "SIRT", + constrain=True, + iterations=2, + thresh=0, + cuda=False, + ) + assert isinstance(stack, TomoStack) + assert isinstance(rec, RecStack) assert rec.data.shape[2] == slices.data.shape[1] def test_recon_sart_cpu(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - rec = slices.reconstruct('SART', - constrain=True, - iterations=2, - thresh=0, - cuda=False) - assert type(stack) is etspy.base.TomoStack - assert type(rec) is etspy.base.RecStack + rec = slices.reconstruct( + "SART", + constrain=True, + iterations=2, + thresh=0, + cuda=False, + ) + assert isinstance(stack, TomoStack) + assert isinstance(rec, RecStack) assert rec.data.shape[2] == slices.data.shape[1] def test_recon_dart_cpu(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - gray_levels = [0., slices.data.max() / 2, slices.data.max()] - rec = slices.reconstruct('DART', iterations=2, cuda=False, gray_levels=gray_levels, dart_iterations=1, ncores=1) - assert type(stack) is etspy.base.TomoStack - assert type(rec) is etspy.base.RecStack + gray_levels = [0.0, slices.data.max() / 2, slices.data.max()] + rec = slices.reconstruct( + "DART", + iterations=2, + cuda=False, + gray_levels=gray_levels, + dart_iterations=1, + ncores=1, + ) + assert isinstance(stack, TomoStack) + assert isinstance(rec, RecStack) assert rec.data.shape[2] == slices.data.shape[1] def test_recon_dart_cpu_multicore(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:122, :].deepcopy() - gray_levels = [0., slices.data.max() / 2, slices.data.max()] - rec = slices.reconstruct('DART', iterations=2, cuda=False, gray_levels=gray_levels, dart_iterations=1, ncores=1) - assert type(stack) is etspy.base.TomoStack - assert type(rec) is etspy.base.RecStack + gray_levels = [0.0, slices.data.max() / 2, slices.data.max()] + rec = slices.reconstruct( + "DART", + iterations=2, + cuda=False, + gray_levels=gray_levels, + dart_iterations=1, + ncores=1, + ) + assert isinstance(stack, TomoStack) + assert isinstance(rec, RecStack) assert rec.data.shape[2] == slices.data.shape[1] class TestReconRun: + """Test the reconstruction "run" method.""" + def test_run_fbp_no_cuda(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - rec = recon.run(slices, 'FBP', cuda=False) - assert rec.data.shape == (1, slices.data.shape[1], slices.data.shape[1]) - assert rec.data.shape[0] == slices.data.shape[2] - assert type(rec) is numpy.ndarray + rec = recon.run(slices, "FBP", cuda=False) + data_shape = cast(Tuple[int, int, int], rec.data.shape) + assert data_shape == (1, slices.data.shape[1], slices.data.shape[1]) + assert data_shape[0] == slices.data.shape[2] + assert isinstance(rec, np.ndarray) def test_run_sirt_no_cuda(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - rec = recon.run(slices, 'SIRT', niterations=2, cuda=False) - assert rec.data.shape == (1, slices.data.shape[1], slices.data.shape[1]) - assert rec.data.shape[0] == slices.data.shape[2] - assert type(rec) is numpy.ndarray + rec = recon.run(slices, "SIRT", niterations=2, cuda=False) + data_shape = cast(Tuple[int, int, int], rec.data.shape) + assert data_shape == (1, slices.data.shape[1], slices.data.shape[1]) + assert data_shape[0] == slices.data.shape[2] + assert isinstance(rec, np.ndarray) def test_run_sart_no_cuda(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - rec = recon.run(slices, 'SART', niterations=2, cuda=False) - assert rec.data.shape == (1, slices.data.shape[1], slices.data.shape[1]) - assert rec.data.shape[0] == slices.data.shape[2] - assert type(rec) is numpy.ndarray + rec = recon.run(slices, "SART", niterations=2, cuda=False) + data_shape = cast(Tuple[int, int, int], rec.data.shape) + assert data_shape == (1, slices.data.shape[1], slices.data.shape[1]) + assert data_shape[0] == slices.data.shape[2] + assert isinstance(rec, np.ndarray) def test_run_dart_no_cuda(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) slices = stack.isig[120:121, :].deepcopy() - gray_levels = [0., slices.data.max() / 2, slices.data.max()] - rec = recon.run(slices, 'DART', niterations=2, cuda=False, gray_levels=gray_levels, dart_iterations=1) - assert rec.data.shape == (1, slices.data.shape[1], slices.data.shape[1]) - assert rec.data.shape[0] == slices.data.shape[2] - assert type(rec) is numpy.ndarray + gray_levels = [0.0, slices.data.max() / 2, slices.data.max()] + rec = recon.run( + slices, + "DART", + niterations=2, + cuda=False, + gray_levels=gray_levels, + dart_iterations=1, + ) + data_shape = cast(Tuple[int, int, int], rec.data.shape) + assert data_shape == (1, slices.data.shape[1], slices.data.shape[1]) + assert data_shape[0] == slices.data.shape[2] + assert isinstance(rec, np.ndarray) class TestAstraError: + """Test Astra toolbox errors.""" + def test_astra_sirt_error_cpu(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) [ntilts, ny, nx] = stack.data.shape angles = stack.metadata.Tomography.tilts sino = stack.isig[120, :].data - rec_stack, error = recon.astra_error(sino, angles, iterations=2, - constrain=True, thresh=0, cuda=False) - assert type(error) is numpy.ndarray + rec_stack, error = recon.astra_error( + sino, + angles, + iterations=2, + constrain=True, + thresh=0, + cuda=False, + ) + assert isinstance(error, np.ndarray) assert rec_stack.shape == (2, ny, ny) def test_astra_sart_error_cpu(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) [ntilts, ny, nx] = stack.data.shape angles = stack.metadata.Tomography.tilts sino = stack.isig[120, :].data - rec_stack, error = recon.astra_error(sino, angles, method='SART', iterations=2, - constrain=True, thresh=0, cuda=False) - assert type(error) is numpy.ndarray + rec_stack, error = recon.astra_error( + sino, + angles, + method="SART", + iterations=2, + constrain=True, + thresh=0, + cuda=False, + ) + assert isinstance(error, np.ndarray) assert rec_stack.shape == (2, ny, ny) diff --git a/etspy/tests/test_simulation.py b/etspy/tests/test_simulation.py index 152df4a..3fb3ca3 100644 --- a/etspy/tests/test_simulation.py +++ b/etspy/tests/test_simulation.py @@ -1,76 +1,88 @@ -from etspy import simulation as sim +"""Test simulation functionality in ETSpy.""" + import numpy as np from hyperspy.signals import Signal2D +from etspy import simulation as sim + class TestModels: + """Test model creation.""" + def test_catalyst_model(self): - stack = sim.create_catalyst_model(0, volsize=[10, 10, 10]) + stack = sim.create_catalyst_model(0, volsize=(10, 10, 10)) assert stack.data.shape == (10, 10, 10) def test_catalyst_model_with_particle(self): - stack = sim.create_catalyst_model(1, volsize=[20, 20, 20], support_radius=5, size_interval=[2, 3]) + stack = sim.create_catalyst_model( + 1, + volsize=(20, 20, 20), + support_radius=5, + size_interval=(2, 3), + ) assert stack.data.shape == (20, 20, 20) def test_cylinder_model(self): model = sim.create_cylinder_model() assert model.data.shape == (200, 200, 200) - assert type(model) is Signal2D + assert isinstance(model, Signal2D) def test_cylinder_model_with_others(self): model = sim.create_cylinder_model(add_others=True) assert model.data.shape == (400, 400, 400) - assert type(model) is Signal2D + assert isinstance(model, Signal2D) def test_tilt_series_model(self): - stack = sim.create_catalyst_model(0, volsize=[10, 10, 10]) + stack = sim.create_catalyst_model(0, volsize=(10, 10, 10)) proj = sim.create_model_tilt_series(stack, np.arange(0, 15, 5)) assert proj.data.shape == (3, 10, 10) def test_tilt_series_model_no_cuda(self): - stack = sim.create_catalyst_model(0, volsize=[10, 10, 10]) + stack = sim.create_catalyst_model(0, volsize=(10, 10, 10)) proj = sim.create_model_tilt_series(stack, np.arange(0, 15, 5), cuda=False) assert proj.data.shape == (3, 10, 10) def test_tilt_series_model_no_tilts(self): - stack = sim.create_catalyst_model(0, volsize=[10, 10, 10]) + stack = sim.create_catalyst_model(0, volsize=(10, 10, 10)) proj = sim.create_model_tilt_series(stack, None) assert proj.data.shape == (90, 10, 10) class TestModifications: + """Test modifications to model during creation.""" + def test_misalign_stack(self): - model = sim.create_catalyst_model(0, volsize=[10, 10, 10]) + model = sim.create_catalyst_model(0, volsize=(10, 10, 10)) stack = sim.create_model_tilt_series(model) shifted = sim.misalign_stack(stack) assert shifted.data.shape == (90, 10, 10) def test_misalign_stack_with_shift(self): - model = sim.create_catalyst_model(0, volsize=[10, 10, 10]) + model = sim.create_catalyst_model(0, volsize=(10, 10, 10)) stack = sim.create_model_tilt_series(model) shifted = sim.misalign_stack(stack, tilt_shift=2) assert shifted.data.shape == (90, 10, 10) def test_misalign_stack_with_xonly(self): - model = sim.create_catalyst_model(0, volsize=[10, 10, 10]) + model = sim.create_catalyst_model(0, volsize=(10, 10, 10)) stack = sim.create_model_tilt_series(model) shifted = sim.misalign_stack(stack, y_only=True) assert shifted.data.shape == (90, 10, 10) def test_misalign_stack_with_rotation(self): - model = sim.create_catalyst_model(0, volsize=[10, 10, 10]) + model = sim.create_catalyst_model(0, volsize=(10, 10, 10)) stack = sim.create_model_tilt_series(model) shifted = sim.misalign_stack(stack, tilt_rotate=2) assert shifted.data.shape == (90, 10, 10) def test_add_noise_gaussian(self): - model = sim.create_catalyst_model(0, volsize=[10, 10, 10]) + model = sim.create_catalyst_model(0, volsize=(10, 10, 10)) stack = sim.create_model_tilt_series(model) - noisy = sim.add_noise(stack, 'gaussian') + noisy = sim.add_noise(stack, "gaussian") assert noisy.data.shape == (90, 10, 10) def test_add_noise_poissanian(self): - model = sim.create_catalyst_model(0, volsize=[10, 10, 10]) + model = sim.create_catalyst_model(0, volsize=(10, 10, 10)) stack = sim.create_model_tilt_series(model) - noisy = sim.add_noise(stack, 'poissonian') + noisy = sim.add_noise(stack, "poissonian") assert noisy.data.shape == (90, 10, 10) diff --git a/etspy/tests/test_utils.py b/etspy/tests/test_utils.py index 73c25ac..2238287 100644 --- a/etspy/tests/test_utils.py +++ b/etspy/tests/test_utils.py @@ -1,24 +1,14 @@ +"""Test utility functions of ETSpy.""" + +import numpy as np +import pytest + from etspy import datasets as ds -import etspy from etspy import io, utils -import pytest -import numpy +from etspy.api import etspy_path from etspy.base import TomoStack -import hyperspy.api as hs -import os -import glob - -etspy_path = os.path.dirname(etspy.__file__) - - -def hspy_mrc_reader_check(): - dirname = os.path.join(etspy_path, "tests", - "test_data", "SerialEM_Multiframe_Test") - files = glob.glob(dirname + "/*.mrc") - file = files[0] - s = hs.load(file) - return s +from . import hspy_mrc_reader_check try: hspy_mrc_reader_check() @@ -30,120 +20,135 @@ def hspy_mrc_reader_check(): @pytest.mark.skipif(hspy_mrc_broken is True, reason="Hyperspy MRC reader broken") class TestMultiframeAverage: + """Test taking a multiframe average of a stack.""" + def test_register_serialem_stack(self): - dirname = os.path.join(etspy_path, "tests", - "test_data", "SerialEM_Multiframe_Test") - files = glob.glob(dirname + "/*.mrc") - stack = io.load(files) + dirname = etspy_path / "tests" / "test_data" / "SerialEM_Multiframe_Test" + files = dirname.glob("*.mrc") + stack = io.load(list(files)) stack_avg = utils.register_serialem_stack(stack, ncpus=1) - assert type(stack_avg) is TomoStack - assert stack_avg.data.shape[0] == 3 + data_shape = 3 + assert isinstance(stack_avg, TomoStack) + assert stack_avg.data.shape[0] == data_shape def test_register_serialem_stack_multicpu(self): - dirname = os.path.join(etspy_path, "tests", - "test_data", "SerialEM_Multiframe_Test") - files = glob.glob(dirname + "/*.mrc") - stack = io.load(files) + dirname = etspy_path / "tests" / "test_data" / "SerialEM_Multiframe_Test" + files = dirname.glob("*.mrc") + stack = io.load(list(files)) stack_avg = utils.register_serialem_stack(stack, ncpus=2) - assert type(stack_avg) is TomoStack - assert stack_avg.data.shape[0] == 3 + data_shape = 3 + assert isinstance(stack_avg, TomoStack) + assert stack_avg.data.shape[0] == data_shape def test_multiaverage(self): - dirname = os.path.join(etspy_path, "tests", - "test_data", "SerialEM_Multiframe_Test") - files = glob.glob(dirname + "/*.mrc") - stack = io.load(files) + dirname = etspy_path / "tests" / "test_data" / "SerialEM_Multiframe_Test" + files = dirname.glob("*.mrc") + stack = io.load(list(files)) ntilts, nframes, ny, nx = stack.data.shape stack_avg = utils.multiaverage(stack.data[0], nframes, ny, nx) - assert type(stack_avg) is numpy.ndarray + assert isinstance(stack_avg, np.ndarray) assert stack_avg.shape == (ny, nx) class TestWeightStack: + """Test weighting a stack.""" + def test_weight_stack_low(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack = stack.inav[0:3] reg = utils.weight_stack(stack, accuracy="low") - assert type(reg) is TomoStack + assert isinstance(reg, TomoStack) def test_weight_stack_medium(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack = stack.inav[0:3] reg = utils.weight_stack(stack, accuracy="medium") - assert type(reg) is TomoStack + assert isinstance(reg, TomoStack) def test_weight_stack_high(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack = stack.inav[0:3] reg = utils.weight_stack(stack, accuracy="high") - assert type(reg) is TomoStack + assert isinstance(reg, TomoStack) def test_weight_stack_bad_accuracy(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack = stack.inav[0:3] - with pytest.raises(ValueError): + bad_accuracy = "wrong" + with pytest.raises( + ValueError, + match=rf"Unknown accuracy level \('{bad_accuracy.lower()}'\). " + "Must be 'low', 'medium', or 'high'.", + ): utils.weight_stack(stack, accuracy="wrong") class TestHelperUtils: + """Test helper utilities.""" + def test_est_angles(self): - est = utils.calc_EST_angles(10) - assert type(est) is numpy.ndarray - assert est.shape[0] == 20 + est = utils.calc_est_angles(10) + data_shape = 20 + assert isinstance(est, np.ndarray) + assert est.shape[0] == data_shape def test_est_angles_error(self): - with pytest.raises(ValueError): - utils.calc_EST_angles(11) + with pytest.raises(ValueError, match="N must be an even number"): + utils.calc_est_angles(11) def test_golden_ratio_angles(self): gr = utils.calc_golden_ratio_angles(10, 5) - assert type(gr) is numpy.ndarray - assert gr.shape[0] == 5 + data_shape = 5 + assert isinstance(gr, np.ndarray) + assert gr.shape[0] == data_shape def test_radial_mask_no_center(self): mask = utils.get_radial_mask([100, 100], None) - assert type(mask) is numpy.ndarray + assert isinstance(mask, np.ndarray) assert mask.shape == (100, 100) def test_radial_mask_with_center(self): mask = utils.get_radial_mask([100, 100], [50, 50]) - assert type(mask) is numpy.ndarray + assert isinstance(mask, np.ndarray) assert mask.shape == (100, 100) class TestWeightingFilter: + """Test weighting filter.""" + def test_weighting_filter_shepp_logan(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack = stack.inav[0:3] filtered = utils.filter_stack(stack, filter_name="shepp-logan", cutoff=0.5) - assert type(filtered) is TomoStack + assert isinstance(filtered, TomoStack) def test_weighting_filter_ram_lak(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack = stack.inav[0:3] filtered = utils.filter_stack(stack, filter_name="ram-lak", cutoff=0.5) - assert type(filtered) is TomoStack + assert isinstance(filtered, TomoStack) def test_weighting_filter_cosine(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack = stack.inav[0:3] filtered = utils.filter_stack(stack, filter_name="cosine", cutoff=0.5) - assert type(filtered) is TomoStack + assert isinstance(filtered, TomoStack) def test_weighting_filter_shepp_hanning(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack = stack.inav[0:3] filtered = utils.filter_stack(stack, filter_name="hanning", cutoff=0.5) - assert type(filtered) is TomoStack + assert isinstance(filtered, TomoStack) def test_weighting_filter_two_dimensional_data(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack = stack.inav[0] filtered = utils.filter_stack(stack, filter_name="hanning", cutoff=0.5) - assert type(filtered) is TomoStack + assert isinstance(filtered, TomoStack) def test_weighting_filter_bad_filter(self): - stack = ds.get_needle_data(True) + stack = ds.get_needle_data(aligned=True) stack = stack.inav[0:3] - with pytest.raises(ValueError): + bad_filter = "wrong" + with pytest.raises(ValueError, match=f"Invalid filter type: {bad_filter}"): utils.filter_stack(stack, filter_name="wrong", cutoff=0.5) diff --git a/etspy/utils.py b/etspy/utils.py index c515fe7..7db603f 100644 --- a/etspy/utils.py +++ b/etspy/utils.py @@ -1,21 +1,19 @@ -# -*- coding: utf-8 -*- -# -# This file is part of ETSpy - """ Utility module for ETSpy package. @author: Andrew Herzing """ -import numpy as np -from etspy.io import create_stack import logging +from multiprocessing import Pool + +import numpy as np import tqdm +from pystackreg import StackReg from scipy import ndimage + from etspy.align import calculate_shifts_stackreg -from pystackreg import StackReg -from multiprocessing import Pool +from etspy.io import create_stack def multiaverage(stack, nframes, ny, nx): @@ -34,7 +32,7 @@ def multiaverage(stack, nframes, ny, nx): Pixels in x-dimension. Returns - ---------- + ------- average : NumPy array Average of all frames at given tilt """ @@ -47,9 +45,10 @@ def _calc_sr_shifts(stack): shifted = np.zeros([nframes, ny, nx]) shifts = _calc_sr_shifts(stack) - for k in range(0, nframes): + for k in range(nframes): shifted[k, :, :] = ndimage.shift( - stack[k, :, :], shift=[shifts[k, 0], shifts[k, 1]] + stack[k, :, :], + shift=[shifts[k, 0], shifts[k, 1]], ) average = shifted.mean(0) return average @@ -65,7 +64,7 @@ def register_serialem_stack(stack, ncpus=1): Signal of shape [ntilts, nframes, ny, nx]. Returns - ---------- + ------- reg : TomoStack object Result of aligning and averaging frames at each tilt with shape [ntilts, ny, nx] @@ -78,20 +77,24 @@ def register_serialem_stack(stack, ncpus=1): if ncpus == 1: reg = np.zeros([ntilts, ny, nx], stack.data.dtype) start = stack.data.shape[0] // 2 - for i in tqdm.tqdm(range(0, ntilts)): + for i in tqdm.tqdm(range(ntilts)): shifted = np.zeros([nframes, ny, nx]) - shifts = calculate_shifts_stackreg(stack.inav[:, i], start, False) - for k in range(0, nframes): + shifts = calculate_shifts_stackreg( + stack.inav[:, i], + start, + show_progressbar=False, + ) + for k in range(nframes): shifted[k, :, :] = ndimage.shift( - stack.data[i, k, :, :], shift=[shifts[k, 0], shifts[k, 1]] + stack.data[i, k, :, :], + shift=[shifts[k, 0], shifts[k, 1]], ) reg[i, :, :] = shifted.mean(0) else: with Pool(ncpus) as pool: reg = pool.starmap( multiaverage, - [(stack.inav[:, i].data, nframes, ny, nx) - for i in range(0, ntilts)], + [(stack.inav[:, i].data, nframes, ny, nx) for i in range(ntilts)], ) reg = np.array(reg) @@ -118,10 +121,11 @@ def register_serialem_stack(stack, ncpus=1): def weight_stack(stack, accuracy="medium"): """ - Apply a weighting window to a stack along the direction perpendicular to the tilt axis. + Apply a weighting window to a stack perpendicular to the tilt axis. - This weighting is useful for reducing the effects of mass introduced at the edges of as stack when - determining alignments based on the center of mass. As described in: + This weighting is useful for reducing the effects of mass introduced at the + edges of as stack when determining alignments based on the center of mass. + As described in: T. Sanders. Physically motivated global alignment method for electron tomography, Advanced Structural and Chemical Imaging vol. 1 (2015) pp 1-11. @@ -130,116 +134,160 @@ def weight_stack(stack, accuracy="medium"): Parameters ---------- stack : TomoStack - Stack to be weighted. - - accuracy : string - Level of accuracy for determining the weighting. Acceptable values are 'low', 'medium', and 'high'. + The stack to be weighted. + accuracy : str, optional + A string indicating the accuracy level for weighting. Options are: + 'low', 'medium', 'high', or any other string for default. Default is 'medium'. Returns - ---------- - reg : TomoStack object - Result of aligning and averaging frames at each tilt with shape [ntilts, ny, nx] + ------- + stackw : object + The weighted version of the input stack. """ - stackw = stack.deepcopy() - - [ntilts, ny, nx] = stack.data.shape - alpha = np.sum(stack.data, (1, 2)).min() - beta = np.sum(stack.data, (1, 2)).argmin() - v = np.arange(ntilts) - v[beta] = 0 - - wg = np.zeros([ny, nx]) - - if accuracy.lower() == "low": - num = 800 - delta = 0.025 - elif accuracy.lower() == "medium": - num = 2000 - delta = 0.01 - elif accuracy.lower() == "high": - num = 20000 - delta = 0.001 - else: - raise ValueError( - "Unknown accuracy level. Must be 'low', 'medium', or 'high'.") - - r = np.arange(1, ny + 1) - r = 2 / (ny - 1) * (r - 1) - 1 - r = np.cos(np.pi * r**2) / 2 + 1 / 2 - s = np.zeros(ntilts) - for p in range(1, int(num / 10) + 1): - rp = r ** (p * delta * 10) - for x in range(0, nx): - wg[:, x] = rp - for i in range(0, ntilts): - if v[i]: - if np.sum(stack.data[i, :, :] * wg) < alpha: - v[i] = 0 - s[i] = (p - 1) * 10 - if v.sum() == 0: + # Set the parameters based on the accuracy input + # with default of "medium" + niterations = 2000 + delta = 0.01 + if accuracy.lower(): + if accuracy == "low": + niterations = 800 + delta = 0.025 + elif accuracy == "medium": + pass + elif accuracy == "high": + niterations = 20000 + delta = 0.001 + else: + msg = ( + f"Unknown accuracy level ('{accuracy.lower()}'). " + "Must be 'low', 'medium', or 'high'." + ) + raise ValueError(msg) + + weighted_stack = stack.deepcopy() + + # Get stack dimensions + ntilts, ny, nx = weighted_stack.data.shape + + # Compute the minimum total projected mass and the corresponding + # slice index (min_slice) + min_mass, min_slice = np.min( + np.sum(np.sum(weighted_stack.data, axis=2), axis=1), + ), np.argmin(np.sum(np.sum(weighted_stack.data, axis=2), axis=1)) + + # Initialize the window array + window = np.zeros([ny, nx]) + + # Initialize the status vector (1 means unmarked, 0 means marked) and mark + # the reference slice (min_slice) + status = np.ones(ntilts) + status[min_slice] = 0 + + # Generate the weighting profile `r` based on a non-linear cosine function + r = np.arange(ny) + r = 2 / (ny - 1) * r - 1 + r = np.cos(np.pi * r**2) / 2 + 0.5 + + # Initialize adjustment factors for each slice + adjustments = np.zeros(ntilts) + + # Coarse adjustment loop + # In this step, the applied window is made increasingly restrictive in 10 pixel + # increments. Whenever the the windowed mass of a projection drops below the value + # of min_alpha, that projection is marked and the window restriction is not carried + # any further for that projection. + + power = 10 # initialize power + for power in np.linspace(10, niterations, niterations // 10): + # Compute the power-weighted profile for the current iteration + r_power = r ** (power * delta) + window = r_power[:, np.newaxis] # Broadcasting across all columns + + # Compute the weighted sum for all slices at once using vectorization + weighted_mass = np.sum( + weighted_stack.data * window[np.newaxis, :, :], axis=(1, 2), + ) + + # Update the status and adjustments for slices with weighted sums below min_mass + update_mask = (status != 0) & (weighted_mass < min_mass) + status[update_mask] = 0 + adjustments[update_mask] = power - 10 + + # Break early if all slices are marked + if not np.any(status): # More efficient than np.sum(status) break - for i in range(0, ntilts): - if v[i]: - s[i] = (p - 1) * 10 - - v = np.arange(1, ntilts + 1) - v[beta] = 0 - for j in range(0, ntilts): - if j != beta: - for p in range(1, 10): - rp = r ** ((p + s[j]) * delta) - for x in range(0, nx): - wg[:, x] = rp - if np.sum(stack.data[i, :, :] * wg) < alpha: - s[j] = p + s[j] - v[i] = 0 - break - for i in range(0, ntilts): - if v[i]: - s[i] = s[i] + 10 - - for i in range(0, ntilts): - for x in range(0, nx): - wg[:, x] = r ** (s[i] * delta) - stackw.data[i, :, :] = stack.data[i, :, :] * wg - return stackw - - -def calc_EST_angles(N): + + # Set window for any unmarked slices to the most restricive used + # in the rest of the slices + adjustments[np.where(status != 0)] = power - 10 + + # Fine adjustment loop + # In this step the severity of the window is calculated again using the value + # calculated in the coarse step and the window is made more restrictive in 1 + # pixel increments. + status = np.ones(ntilts) + status[min_slice] = 0 + + for j in range(ntilts): + if j != min_slice: + for power in np.linspace(1, 10, 10): + # Apply fine adjustments to the weight profile and + # update the weight grid + r_power = r ** ((power + adjustments[j]) * delta) + window[:] = r_power[:, np.newaxis] + + if np.sum(weighted_stack.data[j, :, :] * window) < min_mass: + adjustments[j] = (power - 1) + adjustments[j] + status[j] = 0 + break + + # Restrict the window of any unmarked projections + adjustments[status != 0] += 10 + + # Apply the final window to the entire stack + for i in range(ntilts): + window[:] = (r ** (adjustments[i] * delta))[:, np.newaxis] + weighted_stack.data[i, :, :] *= window + + return weighted_stack + + +def calc_est_angles(num_points): """ Caculate angles used for equally sloped tomography (EST). See: - J. Miao, F. Forster, and O. Levi. Equally sloped tomography with oversampling reconstruction. - Phys. Rev. B, 72 (2005) 052103. + J. Miao, F. Forster, and O. Levi. Equally sloped tomography with + oversampling reconstruction. Phys. Rev. B, 72 (2005) 052103. https://doi.org/10.1103/PhysRevB.72.052103 Parameters ---------- - N : integer + num_points : integer Number of points in scan. Returns - ---------- + ------- angles : Numpy array Angles in degrees for equally sloped tomography. """ - if np.mod(N, 2) != 0: - raise ValueError("N must be an even number") + if np.mod(num_points, 2) != 0: + msg = "N must be an even number" + raise ValueError(msg) - angles = np.zeros(2 * N) + angles = np.zeros(2 * num_points) - n = np.arange(N / 2 + 1, N + 1, dtype="int") - theta1 = -np.arctan((N + 2 - 2 * n) / N) + n = np.arange(num_points / 2 + 1, num_points + 1, dtype="int") + theta1 = -np.arctan((num_points + 2 - 2 * n) / num_points) theta1 = np.pi / 2 - theta1 - n = np.arange(1, N + 1, dtype="int") - theta2 = np.arctan((N + 2 - 2 * n) / N) + n = np.arange(1, num_points + 1, dtype="int") + theta2 = np.arctan((num_points + 2 - 2 * n) / num_points) - n = np.arange(1, N / 2 + 1, dtype="int") - theta3 = -np.pi / 2 + np.arctan((N + 2 - 2 * n) / N) + n = np.arange(1, num_points / 2 + 1, dtype="int") + theta3 = -np.pi / 2 + np.arctan((num_points + 2 - 2 * n) / num_points) angles = np.concatenate([theta1, theta2, theta3], axis=0) angles = angles * 180 / np.pi @@ -265,7 +313,7 @@ def calc_golden_ratio_angles(tilt_range, nangles): Number of angles to calculate. Returns - ---------- + ------- thetas : Numpy Array Angles in degrees for golden ratio sampling over the provided tilt range. @@ -290,15 +338,20 @@ def get_radial_mask(mask_shape, center=None): Location of mask center (x,y). Returns - ---------- + ------- mask : Numpy Array Logical array that is True in the masked region and False outside of it. """ if center is None: center = [int(i / 2) for i in mask_shape] - radius = min(center[0], center[1], mask_shape[1] - center[0], mask_shape[0] - center[1]) - yy, xx = np.ogrid[0: mask_shape[0], 0: mask_shape[1]] + radius = min( + center[0], + center[1], + mask_shape[1] - center[0], + mask_shape[0] - center[1], + ) + yy, xx = np.ogrid[0 : mask_shape[0], 0 : mask_shape[1]] mask = np.sqrt((xx - center[0]) ** 2 + (yy - center[1]) ** 2) mask = mask < radius return mask @@ -319,7 +372,7 @@ def filter_stack(stack, filter_name="shepp-logan", cutoff=0.5): corresponds to the Nyquist frequency. Returns - ---------- + ------- result : TomoStack Filtered version of the input TomoStack. @@ -328,41 +381,50 @@ def filter_stack(stack, filter_name="shepp-logan", cutoff=0.5): filter_length = max(64, 2 ** (int(np.ceil(np.log2(2 * ny))))) freq_indices = np.arange(filter_length // 2 + 1) - filter = np.linspace( - cutoff / filter_length, 1 - cutoff / filter_length, len(freq_indices) + ffilter = np.linspace( + cutoff / filter_length, + 1 - cutoff / filter_length, + len(freq_indices), ) omega = 2 * np.pi * freq_indices / filter_length if filter_name == "ram-lak": pass elif filter_name == "shepp-logan": - filter[1:] = filter[1:] * np.sinc(omega[1:] / (2 * np.pi)) - elif filter_name in ["hanning", "hann",]: - filter[1:] = filter[1:] * (1 + np.cos(omega[1:])) / 2 + ffilter[1:] = ffilter[1:] * np.sinc(omega[1:] / (2 * np.pi)) + elif filter_name in [ + "hanning", + "hann", + ]: + ffilter[1:] = ffilter[1:] * (1 + np.cos(omega[1:])) / 2 elif filter_name in [ "cosine", "cos", ]: - filter[1:] = filter[1:] * np.cos(omega[1:] / 2) + ffilter[1:] = ffilter[1:] * np.cos(omega[1:] / 2) else: - raise ValueError("Invalid filter type: %s." % filter_name) + msg = f"Invalid filter type: {filter_name}" + raise ValueError(msg) - filter = np.concatenate((filter, filter[-2:0:-1])) + ffilter = np.concatenate((ffilter, ffilter[-2:0:-1])) - nfilter = filter.shape[0] + nfilter = ffilter.shape[0] pad_length = int((nfilter - ny) / 2) - if len(stack.data.shape) == 2: + if len(stack.data.shape) == 2: # noqa: PLR2004 padded = np.pad(stack.data, [[0, 0], [pad_length, pad_length]]) proj_fft = np.fft.fft(padded, axis=1) - filtered = np.fft.ifft(proj_fft * filter, axis=1).real + filtered = np.fft.ifft(proj_fft * ffilter, axis=1).real filtered = filtered[:, pad_length:-pad_length] - elif len(stack.data.shape) == 3: + elif len(stack.data.shape) == 3: # noqa: PLR2004 padded = np.pad(stack.data, [[0, 0], [pad_length, pad_length], [0, 0]]) proj_fft = np.fft.fft(padded, axis=1) - filtered = np.fft.ifft(proj_fft * filter[:, np.newaxis], axis=1).real + filtered = np.fft.ifft(proj_fft * ffilter[:, np.newaxis], axis=1).real filtered = filtered[:, pad_length:-pad_length, :] + else: + msg = "Method can only be applied to 2 or 3-dimensional stacks" + raise ValueError(msg) result = stack.deepcopy() result.data = filtered return result diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..b701013 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,2810 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = "*" +files = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] + +[[package]] +name = "appnope" +version = "0.1.4" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = ">=3.6" +files = [ + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, +] + +[[package]] +name = "astra-toolbox" +version = "2.2.0" +description = "Python interface to the ASTRA Toolbox" +optional = false +python-versions = "*" +files = [ + {file = "astra_toolbox-2.2.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:831c4e97be58a8510fc2645fb45540b7d630ff6df1bc9957f509099c37bf294b"}, + {file = "astra_toolbox-2.2.0-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:4297f7d87acf4c5aa7b93636878be052ba68c2b4ac5817c561b495c272565532"}, + {file = "astra_toolbox-2.2.0-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:5ebc6ae2efea72ff1281053b52c2954bdd0bf11ae593b6a99cbbbecac29eb25b"}, + {file = "astra_toolbox-2.2.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:f3e978664d2423083e4c26a3002266e806efa79e645a2b7ff69417f1eab1e44a"}, +] + +[package.dependencies] +numpy = "*" +nvidia-cuda-runtime-cu12 = "*" +nvidia-cufft-cu12 = "*" +scipy = "*" +six = "*" + +[[package]] +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "cloudpickle" +version = "3.0.0" +description = "Pickler class to extend the standard pickle.Pickler functionality" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cloudpickle-3.0.0-py3-none-any.whl", hash = "sha256:246ee7d0c295602a036e86369c77fecda4ab17b506496730f2f576d9016fd9c7"}, + {file = "cloudpickle-3.0.0.tar.gz", hash = "sha256:996d9a482c6fb4f33c1a35335cf8afd065d2a56e973270364840712d9131a882"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "comm" +version = "0.2.2" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.8" +files = [ + {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, + {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, +] + +[package.dependencies] +traitlets = ">=4" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "contourpy" +version = "1.3.0" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.9" +files = [ + {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, + {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"}, + {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"}, + {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"}, + {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"}, + {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"}, + {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"}, + {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"}, + {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"}, + {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"}, + {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"}, + {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"}, + {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"}, +] + +[package.dependencies] +numpy = ">=1.23" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "dask" +version = "2024.8.0" +description = "Parallel PyData with Task Scheduling" +optional = false +python-versions = ">=3.9" +files = [ + {file = "dask-2024.8.0-py3-none-any.whl", hash = "sha256:250ea3df30d4a25958290eec4f252850091c6cfaed82d098179c3b25bba18309"}, + {file = "dask-2024.8.0.tar.gz", hash = "sha256:f1fec39373d2f101bc045529ad4e9b30e34e6eb33b7aa0fa7073aec7b1bf9eee"}, +] + +[package.dependencies] +click = ">=8.1" +cloudpickle = ">=1.5.0" +fsspec = ">=2021.09.0" +importlib-metadata = {version = ">=4.13.0", markers = "python_version < \"3.12\""} +numpy = {version = ">=1.21", optional = true, markers = "extra == \"array\""} +packaging = ">=20.0" +partd = ">=1.4.0" +pyyaml = ">=5.3.1" +toolz = ">=0.10.0" + +[package.extras] +array = ["numpy (>=1.21)"] +complete = ["dask[array,dataframe,diagnostics,distributed]", "lz4 (>=4.3.2)", "pyarrow (>=7.0)", "pyarrow-hotfix"] +dataframe = ["dask-expr (>=1.1,<1.2)", "dask[array]", "pandas (>=2.0)"] +diagnostics = ["bokeh (>=2.4.2)", "jinja2 (>=2.10.3)"] +distributed = ["distributed (==2024.8.0)"] +test = ["pandas[test]", "pre-commit", "pytest", "pytest-cov", "pytest-rerunfailures", "pytest-timeout", "pytest-xdist"] + +[[package]] +name = "debugpy" +version = "1.8.5" +description = "An implementation of the Debug Adapter Protocol for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "debugpy-1.8.5-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7e4d594367d6407a120b76bdaa03886e9eb652c05ba7f87e37418426ad2079f7"}, + {file = "debugpy-1.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4413b7a3ede757dc33a273a17d685ea2b0c09dbd312cc03f5534a0fd4d40750a"}, + {file = "debugpy-1.8.5-cp310-cp310-win32.whl", hash = "sha256:dd3811bd63632bb25eda6bd73bea8e0521794cda02be41fa3160eb26fc29e7ed"}, + {file = "debugpy-1.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:b78c1250441ce893cb5035dd6f5fc12db968cc07f91cc06996b2087f7cefdd8e"}, + {file = "debugpy-1.8.5-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:606bccba19f7188b6ea9579c8a4f5a5364ecd0bf5a0659c8a5d0e10dcee3032a"}, + {file = "debugpy-1.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db9fb642938a7a609a6c865c32ecd0d795d56c1aaa7a7a5722d77855d5e77f2b"}, + {file = "debugpy-1.8.5-cp311-cp311-win32.whl", hash = "sha256:4fbb3b39ae1aa3e5ad578f37a48a7a303dad9a3d018d369bc9ec629c1cfa7408"}, + {file = "debugpy-1.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:345d6a0206e81eb68b1493ce2fbffd57c3088e2ce4b46592077a943d2b968ca3"}, + {file = "debugpy-1.8.5-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:5b5c770977c8ec6c40c60d6f58cacc7f7fe5a45960363d6974ddb9b62dbee156"}, + {file = "debugpy-1.8.5-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a65b00b7cdd2ee0c2cf4c7335fef31e15f1b7056c7fdbce9e90193e1a8c8cb"}, + {file = "debugpy-1.8.5-cp312-cp312-win32.whl", hash = "sha256:c9f7c15ea1da18d2fcc2709e9f3d6de98b69a5b0fff1807fb80bc55f906691f7"}, + {file = "debugpy-1.8.5-cp312-cp312-win_amd64.whl", hash = "sha256:28ced650c974aaf179231668a293ecd5c63c0a671ae6d56b8795ecc5d2f48d3c"}, + {file = "debugpy-1.8.5-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:3df6692351172a42af7558daa5019651f898fc67450bf091335aa8a18fbf6f3a"}, + {file = "debugpy-1.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd04a73eb2769eb0bfe43f5bfde1215c5923d6924b9b90f94d15f207a402226"}, + {file = "debugpy-1.8.5-cp38-cp38-win32.whl", hash = "sha256:8f913ee8e9fcf9d38a751f56e6de12a297ae7832749d35de26d960f14280750a"}, + {file = "debugpy-1.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:a697beca97dad3780b89a7fb525d5e79f33821a8bc0c06faf1f1289e549743cf"}, + {file = "debugpy-1.8.5-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:0a1029a2869d01cb777216af8c53cda0476875ef02a2b6ff8b2f2c9a4b04176c"}, + {file = "debugpy-1.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84c276489e141ed0b93b0af648eef891546143d6a48f610945416453a8ad406"}, + {file = "debugpy-1.8.5-cp39-cp39-win32.whl", hash = "sha256:ad84b7cde7fd96cf6eea34ff6c4a1b7887e0fe2ea46e099e53234856f9d99a34"}, + {file = "debugpy-1.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:7b0fe36ed9d26cb6836b0a51453653f8f2e347ba7348f2bbfe76bfeb670bfb1c"}, + {file = "debugpy-1.8.5-py2.py3-none-any.whl", hash = "sha256:55919dce65b471eff25901acf82d328bbd5b833526b6c1364bd5133754777a44"}, + {file = "debugpy-1.8.5.zip", hash = "sha256:b2112cfeb34b4507399d298fe7023a16656fc553ed5246536060ca7bd0e668d0"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "executing" +version = "2.1.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.8" +files = [ + {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, + {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + +[[package]] +name = "flexcache" +version = "0.3" +description = "Saves and loads to the cache a transformed versions of a source object." +optional = false +python-versions = ">=3.9" +files = [ + {file = "flexcache-0.3-py3-none-any.whl", hash = "sha256:d43c9fea82336af6e0115e308d9d33a185390b8346a017564611f1466dcd2e32"}, + {file = "flexcache-0.3.tar.gz", hash = "sha256:18743bd5a0621bfe2cf8d519e4c3bfdf57a269c15d1ced3fb4b64e0ff4600656"}, +] + +[package.dependencies] +typing-extensions = "*" + +[package.extras] +test = ["pytest", "pytest-cov", "pytest-mpl", "pytest-subtests"] + +[[package]] +name = "flexparser" +version = "0.3.1" +description = "Parsing made fun ... using typing." +optional = false +python-versions = ">=3.9" +files = [ + {file = "flexparser-0.3.1-py3-none-any.whl", hash = "sha256:2e3e2936bec1f9277f777ef77297522087d96adb09624d4fe4240fd56885c013"}, + {file = "flexparser-0.3.1.tar.gz", hash = "sha256:36f795d82e50f5c9ae2fde1c33f21f88922fdd67b7629550a3cc4d0b40a66856"}, +] + +[package.dependencies] +typing-extensions = "*" + +[package.extras] +test = ["pytest", "pytest-cov", "pytest-mpl", "pytest-subtests"] + +[[package]] +name = "fonttools" +version = "4.53.1" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"}, + {file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"}, + {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d"}, + {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0"}, + {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41"}, + {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f"}, + {file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4"}, + {file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671"}, + {file = "fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1"}, + {file = "fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923"}, + {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719"}, + {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3"}, + {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb"}, + {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2"}, + {file = "fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88"}, + {file = "fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02"}, + {file = "fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58"}, + {file = "fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8"}, + {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60"}, + {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f"}, + {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"}, + {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f"}, + {file = "fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670"}, + {file = "fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab"}, + {file = "fonttools-4.53.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749"}, + {file = "fonttools-4.53.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2"}, + {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb"}, + {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f"}, + {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d"}, + {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169"}, + {file = "fonttools-4.53.1-cp38-cp38-win32.whl", hash = "sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d"}, + {file = "fonttools-4.53.1-cp38-cp38-win_amd64.whl", hash = "sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8"}, + {file = "fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a"}, + {file = "fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31"}, + {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c"}, + {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407"}, + {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb"}, + {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122"}, + {file = "fonttools-4.53.1-cp39-cp39-win32.whl", hash = "sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb"}, + {file = "fonttools-4.53.1-cp39-cp39-win_amd64.whl", hash = "sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb"}, + {file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d"}, + {file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "pycairo", "scipy"] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.1.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "fsspec" +version = "2024.9.0" +description = "File-system specification" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fsspec-2024.9.0-py3-none-any.whl", hash = "sha256:a0947d552d8a6efa72cc2c730b12c41d043509156966cca4fb157b0f2a0c574b"}, + {file = "fsspec-2024.9.0.tar.gz", hash = "sha256:4b0afb90c2f21832df142f292649035d80b421f60a9e1c027802e5a0da2b04e8"}, +] + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +dev = ["pre-commit", "ruff"] +doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] +test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] +test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] +tqdm = ["tqdm"] + +[[package]] +name = "h5py" +version = "3.11.0" +description = "Read and write HDF5 files from Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "h5py-3.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1625fd24ad6cfc9c1ccd44a66dac2396e7ee74940776792772819fc69f3a3731"}, + {file = "h5py-3.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c072655ad1d5fe9ef462445d3e77a8166cbfa5e599045f8aa3c19b75315f10e5"}, + {file = "h5py-3.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77b19a40788e3e362b54af4dcf9e6fde59ca016db2c61360aa30b47c7b7cef00"}, + {file = "h5py-3.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:ef4e2f338fc763f50a8113890f455e1a70acd42a4d083370ceb80c463d803972"}, + {file = "h5py-3.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbd732a08187a9e2a6ecf9e8af713f1d68256ee0f7c8b652a32795670fb481ba"}, + {file = "h5py-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75bd7b3d93fbeee40860fd70cdc88df4464e06b70a5ad9ce1446f5f32eb84007"}, + {file = "h5py-3.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c416f8eb0daae39dabe71415cb531f95dce2d81e1f61a74537a50c63b28ab3"}, + {file = "h5py-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:083e0329ae534a264940d6513f47f5ada617da536d8dccbafc3026aefc33c90e"}, + {file = "h5py-3.11.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a76cae64080210389a571c7d13c94a1a6cf8cb75153044fd1f822a962c97aeab"}, + {file = "h5py-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3736fe21da2b7d8a13fe8fe415f1272d2a1ccdeff4849c1421d2fb30fd533bc"}, + {file = "h5py-3.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6ae84a14103e8dc19266ef4c3e5d7c00b68f21d07f2966f0ca7bdb6c2761fb"}, + {file = "h5py-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:21dbdc5343f53b2e25404673c4f00a3335aef25521bd5fa8c707ec3833934892"}, + {file = "h5py-3.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:754c0c2e373d13d6309f408325343b642eb0f40f1a6ad21779cfa9502209e150"}, + {file = "h5py-3.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:731839240c59ba219d4cb3bc5880d438248533366f102402cfa0621b71796b62"}, + {file = "h5py-3.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ec9df3dd2018904c4cc06331951e274f3f3fd091e6d6cc350aaa90fa9b42a76"}, + {file = "h5py-3.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:55106b04e2c83dfb73dc8732e9abad69d83a436b5b82b773481d95d17b9685e1"}, + {file = "h5py-3.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f4e025e852754ca833401777c25888acb96889ee2c27e7e629a19aee288833f0"}, + {file = "h5py-3.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c4b760082626120031d7902cd983d8c1f424cdba2809f1067511ef283629d4b"}, + {file = "h5py-3.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67462d0669f8f5459529de179f7771bd697389fcb3faab54d63bf788599a48ea"}, + {file = "h5py-3.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:d9c944d364688f827dc889cf83f1fca311caf4fa50b19f009d1f2b525edd33a3"}, + {file = "h5py-3.11.0.tar.gz", hash = "sha256:7b7e8f78072a2edec87c9836f25f34203fd492a4475709a18b417a33cfb21fa9"}, +] + +[package.dependencies] +numpy = ">=1.17.3" + +[[package]] +name = "hyperspy" +version = "2.1.1" +description = "Multidimensional data analysis toolbox" +optional = false +python-versions = ">=3.8" +files = [ + {file = "hyperspy-2.1.1-py3-none-any.whl", hash = "sha256:234fd08605fe1d66c4eddb258e457e283fd8754859451ba10980f54faa897820"}, + {file = "hyperspy-2.1.1.tar.gz", hash = "sha256:c7cf31d874e0548ff52cd8b62ea953e4dbffebc577bdd5cb350156223d660251"}, +] + +[package.dependencies] +cloudpickle = "*" +dask = {version = ">=2021.5.1", extras = ["array"]} +importlib-metadata = ">=3.6" +jinja2 = "*" +matplotlib = ">=3.1.3" +natsort = "*" +numpy = ">=1.20.0" +packaging = "*" +pint = ">=0.10" +pooch = "*" +prettytable = ">=2.3" +python-dateutil = ">=2.5.0" +pyyaml = "*" +requests = "*" +rosettasciio = {version = "*", extras = ["hdf5"]} +scikit-image = ">=0.18" +scipy = ">=1.6.0" +sympy = ">=1.6" +tqdm = ">=4.9.0" +traits = ">=4.5.0" + +[package.extras] +all = ["hyperspy[gui-jupyter]", "hyperspy[gui-traitsui]", "hyperspy[ipython]", "hyperspy[learning]", "hyperspy[speed]"] +coverage = ["pytest-cov"] +dev = ["black", "hyperspy[all]", "hyperspy[coverage]", "hyperspy[doc]", "hyperspy[tests]"] +doc = ["IPython", "holospy", "numpydoc", "pickleshare", "pydata-sphinx-theme", "setuptools-scm", "sphinx (>=1.7)", "sphinx-copybutton", "sphinx-design", "sphinx-favicon", "sphinx-gallery", "sphinxcontrib-mermaid", "sphinxcontrib-towncrier (>=0.3.0a0)", "towncrier"] +gui-jupyter = ["hyperspy-gui-ipywidgets (>=2.0)", "ipympl"] +gui-traitsui = ["hyperspy-gui-traitsui (>=2.0)"] +ipython = ["IPython (>7.0,!=8.0)", "ipyparallel"] +learning = ["scikit-learn (>=1.0.1)"] +speed = ["numba", "numexpr"] +tests = ["pytest (>=3.6)", "pytest-instafail", "pytest-mpl", "pytest-rerunfailures", "pytest-xdist", "setuptools-scm"] + +[[package]] +name = "hyperspy-gui-ipywidgets" +version = "2.0.2" +description = "ipywidgets GUI elements for the HyperSpy framework." +optional = false +python-versions = ">=3.8" +files = [ + {file = "hyperspy_gui_ipywidgets-2.0.2-py3-none-any.whl", hash = "sha256:0d36e0db9ad0b32268beab4790e8cb878bb39834a4a4c9892302a821b000059a"}, + {file = "hyperspy_gui_ipywidgets-2.0.2.tar.gz", hash = "sha256:f5550f8e3e5e8a9d50cb91b1bd9f6c6350dabeb39466ef1c3838539a9864a75a"}, +] + +[package.dependencies] +hyperspy = ">=2.0rc0" +ipympl = "*" +ipywidgets = ">=7.0" +link-traits = "*" + +[package.extras] +dev = ["black", "hyperspy-gui-ipywidgets[doc]", "hyperspy-gui-ipywidgets[tests]"] +tests = ["pytest", "pytest-cov", "pytest-rerunfailures", "setuptools-scm"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "imageio" +version = "2.35.1" +description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." +optional = false +python-versions = ">=3.8" +files = [ + {file = "imageio-2.35.1-py3-none-any.whl", hash = "sha256:6eb2e5244e7a16b85c10b5c2fe0f7bf961b40fcb9f1a9fd1bd1d2c2f8fb3cd65"}, + {file = "imageio-2.35.1.tar.gz", hash = "sha256:4952dfeef3c3947957f6d5dedb1f4ca31c6e509a476891062396834048aeed2a"}, +] + +[package.dependencies] +numpy = "*" +pillow = ">=8.3.2" + +[package.extras] +all-plugins = ["astropy", "av", "imageio-ffmpeg", "psutil", "tifffile"] +all-plugins-pypy = ["av", "imageio-ffmpeg", "psutil", "tifffile"] +build = ["wheel"] +dev = ["black", "flake8", "fsspec[github]", "pytest", "pytest-cov"] +docs = ["numpydoc", "pydata-sphinx-theme", "sphinx (<6)"] +ffmpeg = ["imageio-ffmpeg", "psutil"] +fits = ["astropy"] +full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "itk", "numpy (>2)", "numpydoc", "pillow-heif", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "rawpy", "sphinx (<6)", "tifffile", "wheel"] +gdal = ["gdal"] +itk = ["itk"] +linting = ["black", "flake8"] +pillow-heif = ["pillow-heif"] +pyav = ["av"] +rawpy = ["numpy (>2)", "rawpy"] +test = ["fsspec[github]", "pytest", "pytest-cov"] +tifffile = ["tifffile"] + +[[package]] +name = "importlib-metadata" +version = "8.5.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + +[[package]] +name = "importlib-resources" +version = "6.4.5" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, + {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "ipykernel" +version = "6.29.5" +description = "IPython Kernel for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, + {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +matplotlib-inline = ">=0.1" +nest-asyncio = "*" +packaging = "*" +psutil = "*" +pyzmq = ">=24" +tornado = ">=6.1" +traitlets = ">=5.4.0" + +[package.extras] +cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipympl" +version = "0.9.4" +description = "Matplotlib Jupyter Extension" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ipympl-0.9.4-py3-none-any.whl", hash = "sha256:5b0c08c6f4f6ea655ba58239363457c10fb921557f5038c1a46db4457d6d6b0e"}, + {file = "ipympl-0.9.4.tar.gz", hash = "sha256:cfb53c5b4fcbcee6d18f095eecfc6c6c474303d5b744e72cc66e7a2804708907"}, +] + +[package.dependencies] +ipython = "<9" +ipython-genutils = "*" +ipywidgets = ">=7.6.0,<9" +matplotlib = ">=3.4.0,<4" +numpy = "*" +pillow = "*" +traitlets = "<6" + +[package.extras] +docs = ["myst-nb", "sphinx (>=1.5)", "sphinx-book-theme", "sphinx-copybutton", "sphinx-thebe", "sphinx-togglebutton"] + +[[package]] +name = "ipython" +version = "8.18.1" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, + {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +prompt-toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" +typing-extensions = {version = "*", markers = "python_version < \"3.10\""} + +[package.extras] +all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] + +[[package]] +name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +optional = false +python-versions = "*" +files = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] + +[[package]] +name = "ipywidgets" +version = "8.1.5" +description = "Jupyter interactive widgets" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245"}, + {file = "ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17"}, +] + +[package.dependencies] +comm = ">=0.1.3" +ipython = ">=6.1.0" +jupyterlab-widgets = ">=3.0.12,<3.1.0" +traitlets = ">=4.3.1" +widgetsnbextension = ">=4.0.12,<4.1.0" + +[package.extras] +test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "jedi" +version = "0.19.1" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, + {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, + {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.13" +description = "Jupyter interactive widgets for JupyterLab" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54"}, + {file = "jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed"}, +] + +[[package]] +name = "kiwisolver" +version = "1.4.7" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.8" +files = [ + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"}, + {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"}, +] + +[[package]] +name = "lazy-loader" +version = "0.4" +description = "Makes it easy to load subpackages and functions on demand." +optional = false +python-versions = ">=3.7" +files = [ + {file = "lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc"}, + {file = "lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +dev = ["changelist (==0.5)"] +lint = ["pre-commit (==3.7.0)"] +test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] + +[[package]] +name = "link-traits" +version = "1.0.3" +description = "A fork to traitlets' link and dlink to link traits in addition to traitlets." +optional = false +python-versions = "*" +files = [ + {file = "link_traits-1.0.3.tar.gz", hash = "sha256:4e1f104ce3bcb9a1c16f838d19ff45ec7be9704df6565690de19b0a05d5820a9"}, +] + +[package.dependencies] +traits = "*" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "llvmlite" +version = "0.43.0" +description = "lightweight wrapper around basic LLVM functionality" +optional = false +python-versions = ">=3.9" +files = [ + {file = "llvmlite-0.43.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a289af9a1687c6cf463478f0fa8e8aa3b6fb813317b0d70bf1ed0759eab6f761"}, + {file = "llvmlite-0.43.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d4fd101f571a31acb1559ae1af30f30b1dc4b3186669f92ad780e17c81e91bc"}, + {file = "llvmlite-0.43.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d434ec7e2ce3cc8f452d1cd9a28591745de022f931d67be688a737320dfcead"}, + {file = "llvmlite-0.43.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6912a87782acdff6eb8bf01675ed01d60ca1f2551f8176a300a886f09e836a6a"}, + {file = "llvmlite-0.43.0-cp310-cp310-win_amd64.whl", hash = "sha256:14f0e4bf2fd2d9a75a3534111e8ebeb08eda2f33e9bdd6dfa13282afacdde0ed"}, + {file = "llvmlite-0.43.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8d0618cb9bfe40ac38a9633f2493d4d4e9fcc2f438d39a4e854f39cc0f5f98"}, + {file = "llvmlite-0.43.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0a9a1a39d4bf3517f2af9d23d479b4175ead205c592ceeb8b89af48a327ea57"}, + {file = "llvmlite-0.43.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1da416ab53e4f7f3bc8d4eeba36d801cc1894b9fbfbf2022b29b6bad34a7df2"}, + {file = "llvmlite-0.43.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977525a1e5f4059316b183fb4fd34fa858c9eade31f165427a3977c95e3ee749"}, + {file = "llvmlite-0.43.0-cp311-cp311-win_amd64.whl", hash = "sha256:d5bd550001d26450bd90777736c69d68c487d17bf371438f975229b2b8241a91"}, + {file = "llvmlite-0.43.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f99b600aa7f65235a5a05d0b9a9f31150c390f31261f2a0ba678e26823ec38f7"}, + {file = "llvmlite-0.43.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:35d80d61d0cda2d767f72de99450766250560399edc309da16937b93d3b676e7"}, + {file = "llvmlite-0.43.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eccce86bba940bae0d8d48ed925f21dbb813519169246e2ab292b5092aba121f"}, + {file = "llvmlite-0.43.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df6509e1507ca0760787a199d19439cc887bfd82226f5af746d6977bd9f66844"}, + {file = "llvmlite-0.43.0-cp312-cp312-win_amd64.whl", hash = "sha256:7a2872ee80dcf6b5dbdc838763d26554c2a18aa833d31a2635bff16aafefb9c9"}, + {file = "llvmlite-0.43.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cd2a7376f7b3367019b664c21f0c61766219faa3b03731113ead75107f3b66c"}, + {file = "llvmlite-0.43.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18e9953c748b105668487b7c81a3e97b046d8abf95c4ddc0cd3c94f4e4651ae8"}, + {file = "llvmlite-0.43.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74937acd22dc11b33946b67dca7680e6d103d6e90eeaaaf932603bec6fe7b03a"}, + {file = "llvmlite-0.43.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9efc739cc6ed760f795806f67889923f7274276f0eb45092a1473e40d9b867"}, + {file = "llvmlite-0.43.0-cp39-cp39-win_amd64.whl", hash = "sha256:47e147cdda9037f94b399bf03bfd8a6b6b1f2f90be94a454e3386f006455a9b4"}, + {file = "llvmlite-0.43.0.tar.gz", hash = "sha256:ae2b5b5c3ef67354824fb75517c8db5fbe93bc02cd9671f3c62271626bc041d5"}, +] + +[[package]] +name = "locket" +version = "1.0.0" +description = "File-based locks for Python on Linux and Windows" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3"}, + {file = "locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632"}, +] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "matplotlib" +version = "3.9.2" +description = "Python plotting package" +optional = false +python-versions = ">=3.9" +files = [ + {file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"}, + {file = "matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d94ff717eb2bd0b58fe66380bd8b14ac35f48a98e7c6765117fe67fb7684e64"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab68d50c06938ef28681073327795c5db99bb4666214d2d5f880ed11aeaded66"}, + {file = "matplotlib-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65aacf95b62272d568044531e41de26285d54aec8cb859031f511f84bd8b495a"}, + {file = "matplotlib-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3fd595f34aa8a55b7fc8bf9ebea8aa665a84c82d275190a61118d33fbc82ccae"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8912ef7c2362f7193b5819d17dae8629b34a95c58603d781329712ada83f9447"}, + {file = "matplotlib-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7741f26a58a240f43bee74965c4882b6c93df3e7eb3de160126d8c8f53a6ae6e"}, + {file = "matplotlib-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:ae82a14dab96fbfad7965403c643cafe6515e386de723e498cf3eeb1e0b70cc7"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac43031375a65c3196bee99f6001e7fa5bdfb00ddf43379d3c0609bdca042df9"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be0fc24a5e4531ae4d8e858a1a548c1fe33b176bb13eff7f9d0d38ce5112a27d"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf81de2926c2db243c9b2cbc3917619a0fc85796c6ba4e58f541df814bbf83c7"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ee45bc4245533111ced13f1f2cace1e7f89d1c793390392a80c139d6cf0e6c"}, + {file = "matplotlib-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:306c8dfc73239f0e72ac50e5a9cf19cc4e8e331dd0c54f5e69ca8758550f1e1e"}, + {file = "matplotlib-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:5413401594cfaff0052f9d8b1aafc6d305b4bd7c4331dccd18f561ff7e1d3bd3"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:18128cc08f0d3cfff10b76baa2f296fc28c4607368a8402de61bb3f2eb33c7d9"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4876d7d40219e8ae8bb70f9263bcbe5714415acfdf781086601211335e24f8aa"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9f07a80deab4bb0b82858a9e9ad53d1382fd122be8cde11080f4e7dfedb38b"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c0410f181a531ec4e93bbc27692f2c71a15c2da16766f5ba9761e7ae518413"}, + {file = "matplotlib-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:909645cce2dc28b735674ce0931a4ac94e12f5b13f6bb0b5a5e65e7cea2c192b"}, + {file = "matplotlib-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f32c7410c7f246838a77d6d1eff0c0f87f3cb0e7c4247aebea71a6d5a68cab49"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37e51dd1c2db16ede9cfd7b5cabdfc818b2c6397c83f8b10e0e797501c963a03"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b82c5045cebcecd8496a4d694d43f9cc84aeeb49fe2133e036b207abe73f4d30"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f053c40f94bc51bc03832a41b4f153d83f2062d88c72b5e79997072594e97e51"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbe196377a8248972f5cede786d4c5508ed5f5ca4a1e09b44bda889958b33f8c"}, + {file = "matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:cef2a73d06601437be399908cf13aee74e86932a5ccc6ccdf173408ebc5f6bb2"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0830e188029c14e891fadd99702fd90d317df294c3298aad682739c5533721a"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ba9c1299c920964e8d3857ba27173b4dbb51ca4bab47ffc2c2ba0eb5e2cbc5"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd93b91ab47a3616b4d3c42b52f8363b88ca021e340804c6ab2536344fad9ca"}, + {file = "matplotlib-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6d1ce5ed2aefcdce11904fc5bbea7d9c21fff3d5f543841edf3dea84451a09ea"}, + {file = "matplotlib-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:b2696efdc08648536efd4e1601b5fd491fd47f4db97a5fbfd175549a7365c1b2"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d52a3b618cb1cbb769ce2ee1dcdb333c3ab6e823944e9a2d36e37253815f9556"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:039082812cacd6c6bec8e17a9c1e6baca230d4116d522e81e1f63a74d01d2e21"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6758baae2ed64f2331d4fd19be38b7b4eae3ecec210049a26b6a4f3ae1c85dcc"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:050598c2b29e0b9832cde72bcf97627bf00262adbc4a54e2b856426bb2ef0697"}, + {file = "matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mpmath" +version = "1.3.0" +description = "Python library for arbitrary-precision floating-point arithmetic" +optional = false +python-versions = "*" +files = [ + {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, + {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, +] + +[package.extras] +develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] +docs = ["sphinx"] +gmpy = ["gmpy2 (>=2.1.0a4)"] +tests = ["pytest (>=4.6)"] + +[[package]] +name = "natsort" +version = "8.4.0" +description = "Simple yet flexible natural sorting in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c"}, + {file = "natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581"}, +] + +[package.extras] +fast = ["fastnumbers (>=2.0.0)"] +icu = ["PyICU (>=1.0.0)"] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +files = [ + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, +] + +[[package]] +name = "networkx" +version = "3.2.1" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.9" +files = [ + {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, + {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, +] + +[package.extras] +default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] + +[[package]] +name = "numba" +version = "0.60.0" +description = "compiling Python code using LLVM" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numba-0.60.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d761de835cd38fb400d2c26bb103a2726f548dc30368853121d66201672e651"}, + {file = "numba-0.60.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:159e618ef213fba758837f9837fb402bbe65326e60ba0633dbe6c7f274d42c1b"}, + {file = "numba-0.60.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1527dc578b95c7c4ff248792ec33d097ba6bef9eda466c948b68dfc995c25781"}, + {file = "numba-0.60.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe0b28abb8d70f8160798f4de9d486143200f34458d34c4a214114e445d7124e"}, + {file = "numba-0.60.0-cp310-cp310-win_amd64.whl", hash = "sha256:19407ced081d7e2e4b8d8c36aa57b7452e0283871c296e12d798852bc7d7f198"}, + {file = "numba-0.60.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a17b70fc9e380ee29c42717e8cc0bfaa5556c416d94f9aa96ba13acb41bdece8"}, + {file = "numba-0.60.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fb02b344a2a80efa6f677aa5c40cd5dd452e1b35f8d1c2af0dfd9ada9978e4b"}, + {file = "numba-0.60.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f4fde652ea604ea3c86508a3fb31556a6157b2c76c8b51b1d45eb40c8598703"}, + {file = "numba-0.60.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4142d7ac0210cc86432b818338a2bc368dc773a2f5cf1e32ff7c5b378bd63ee8"}, + {file = "numba-0.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:cac02c041e9b5bc8cf8f2034ff6f0dbafccd1ae9590dc146b3a02a45e53af4e2"}, + {file = "numba-0.60.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7da4098db31182fc5ffe4bc42c6f24cd7d1cb8a14b59fd755bfee32e34b8404"}, + {file = "numba-0.60.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38d6ea4c1f56417076ecf8fc327c831ae793282e0ff51080c5094cb726507b1c"}, + {file = "numba-0.60.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:62908d29fb6a3229c242e981ca27e32a6e606cc253fc9e8faeb0e48760de241e"}, + {file = "numba-0.60.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0ebaa91538e996f708f1ab30ef4d3ddc344b64b5227b67a57aa74f401bb68b9d"}, + {file = "numba-0.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:f75262e8fe7fa96db1dca93d53a194a38c46da28b112b8a4aca168f0df860347"}, + {file = "numba-0.60.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:01ef4cd7d83abe087d644eaa3d95831b777aa21d441a23703d649e06b8e06b74"}, + {file = "numba-0.60.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:819a3dfd4630d95fd574036f99e47212a1af41cbcb019bf8afac63ff56834449"}, + {file = "numba-0.60.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b983bd6ad82fe868493012487f34eae8bf7dd94654951404114f23c3466d34b"}, + {file = "numba-0.60.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c151748cd269ddeab66334bd754817ffc0cabd9433acb0f551697e5151917d25"}, + {file = "numba-0.60.0-cp39-cp39-win_amd64.whl", hash = "sha256:3031547a015710140e8c87226b4cfe927cac199835e5bf7d4fe5cb64e814e3ab"}, + {file = "numba-0.60.0.tar.gz", hash = "sha256:5df6158e5584eece5fc83294b949fd30b9f1125df7708862205217e068aabf16"}, +] + +[package.dependencies] +llvmlite = "==0.43.*" +numpy = ">=1.22,<2.1" + +[[package]] +name = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.6.68" +description = "CUDA Runtime native Libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_runtime_cu12-12.6.68-py3-none-manylinux2014_aarch64.whl", hash = "sha256:3d421aa4ff608b2d8c650e0208a0fb28b4b6792a35b42bd2769d802149f85238"}, + {file = "nvidia_cuda_runtime_cu12-12.6.68-py3-none-manylinux2014_x86_64.whl", hash = "sha256:846987485889786d257f6d7bdcf7544a36452936514e20dd710527b896c0fe12"}, + {file = "nvidia_cuda_runtime_cu12-12.6.68-py3-none-win_amd64.whl", hash = "sha256:806b51a1dd266aac41ae09ca6142faee1686d119ced006cb9b76dfd331c75ab8"}, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.2.6.59" +description = "CUFFT native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cufft_cu12-11.2.6.59-py3-none-manylinux2014_aarch64.whl", hash = "sha256:2ea19d2101d309228daeb1045397d8e28eb3ec1ec45f226bdc12ac6e9c1c59d4"}, + {file = "nvidia_cufft_cu12-11.2.6.59-py3-none-manylinux2014_x86_64.whl", hash = "sha256:251df5b20b11bb2af6d3964ac01b85a94094222d081c90f27e8df3bf533d3257"}, + {file = "nvidia_cufft_cu12-11.2.6.59-py3-none-win_amd64.whl", hash = "sha256:998bbd77799dc427f9c48e5d57a316a7370d231fd96121fb018b370f67fc4909"}, +] + +[package.dependencies] +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.6.68" +description = "Nvidia JIT LTO Library" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nvjitlink_cu12-12.6.68-py3-none-manylinux2014_aarch64.whl", hash = "sha256:b3fd0779845f68b92063ab1393abab1ed0a23412fc520df79a8190d098b5cd6b"}, + {file = "nvidia_nvjitlink_cu12-12.6.68-py3-none-manylinux2014_x86_64.whl", hash = "sha256:125a6c2a44e96386dda634e13d944e60b07a0402d391a070e8fb4104b34ea1ab"}, + {file = "nvidia_nvjitlink_cu12-12.6.68-py3-none-win_amd64.whl", hash = "sha256:a55744c98d70317c5e23db14866a8cc2b733f7324509e941fc96276f9f37801d"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "parso" +version = "0.8.4" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + +[[package]] +name = "partd" +version = "1.4.2" +description = "Appendable key-value storage" +optional = false +python-versions = ">=3.9" +files = [ + {file = "partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f"}, + {file = "partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c"}, +] + +[package.dependencies] +locket = "*" +toolz = "*" + +[package.extras] +complete = ["blosc", "numpy (>=1.20.0)", "pandas (>=1.3)", "pyzmq"] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pillow" +version = "10.4.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, + {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, + {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, + {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, + {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, + {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, + {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, + {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, + {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, + {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, + {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, + {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, + {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, + {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, + {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, + {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, + {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, + {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, + {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + +[[package]] +name = "pint" +version = "0.24.3" +description = "Physical quantities module" +optional = false +python-versions = ">=3.9" +files = [ + {file = "Pint-0.24.3-py3-none-any.whl", hash = "sha256:d98667e46fd03a1b94694fbfa104ec30858684d8ab26952e2a348b48059089bb"}, + {file = "pint-0.24.3.tar.gz", hash = "sha256:d54771093e8b94c4e0a35ac638c2444ddf3ef685652bab7675ffecfa0c5c5cdf"}, +] + +[package.dependencies] +appdirs = ">=1.4.4" +flexcache = ">=0.3" +flexparser = ">=0.3" +typing-extensions = "*" + +[package.extras] +babel = ["babel (<=2.8)"] +bench = ["pytest", "pytest-codspeed"] +dask = ["dask"] +mip = ["mip (>=1.13)"] +numpy = ["numpy (>=1.23)"] +pandas = ["pint-pandas (>=0.3)"] +test = ["pytest", "pytest-benchmark", "pytest-cov", "pytest-mpl", "pytest-subtests"] +testbase = ["pytest", "pytest-benchmark", "pytest-cov", "pytest-subtests"] +uncertainties = ["uncertainties (>=3.1.6)"] +xarray = ["xarray"] + +[[package]] +name = "platformdirs" +version = "4.3.6" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pooch" +version = "1.8.2" +description = "A friend to fetch your data files" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pooch-1.8.2-py3-none-any.whl", hash = "sha256:3529a57096f7198778a5ceefd5ac3ef0e4d06a6ddaf9fc2d609b806f25302c47"}, + {file = "pooch-1.8.2.tar.gz", hash = "sha256:76561f0de68a01da4df6af38e9955c4c9d1a5c90da73f7e40276a5728ec83d10"}, +] + +[package.dependencies] +packaging = ">=20.0" +platformdirs = ">=2.5.0" +requests = ">=2.19.0" + +[package.extras] +progress = ["tqdm (>=4.41.0,<5.0.0)"] +sftp = ["paramiko (>=2.7.0)"] +xxhash = ["xxhash (>=1.4.3)"] + +[[package]] +name = "prettytable" +version = "3.11.0" +description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" +optional = false +python-versions = ">=3.8" +files = [ + {file = "prettytable-3.11.0-py3-none-any.whl", hash = "sha256:aa17083feb6c71da11a68b2c213b04675c4af4ce9c541762632ca3f2cb3546dd"}, + {file = "prettytable-3.11.0.tar.gz", hash = "sha256:7e23ca1e68bbfd06ba8de98bf553bf3493264c96d5e8a615c0471025deeba722"}, +] + +[package.dependencies] +wcwidth = "*" + +[package.extras] +tests = ["pytest", "pytest-cov", "pytest-lazy-fixtures"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.47" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, + {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "6.0.0" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, + {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, + {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, + {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, + {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, + {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, + {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, + {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, + {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, + {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pydocstyle" +version = "6.3.0" +description = "Python docstring style checker" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, + {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, +] + +[package.dependencies] +snowballstemmer = ">=2.2.0" + +[package.extras] +toml = ["tomli (>=1.2.3)"] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyparsing" +version = "3.1.4" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c"}, + {file = "pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pystackreg" +version = "0.2.7" +description = "Image registration tool (python implementation of the ImageJ/FIJI Plugin TurboReg/StackReg)" +optional = false +python-versions = "*" +files = [ + {file = "pystackreg-0.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:54664e84d3cd613f8226f13809ebf4ed28301bed80ad938b830639dfa4d7d714"}, + {file = "pystackreg-0.2.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d74a1aaf94a137b3f4c61a42a81e0e4553dca2be4ad5c26f5d77c0c899a9ee"}, + {file = "pystackreg-0.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76ec3f9be311ec980f06795088beaedeff2a73c2ad94c8ab62f2a06194d88fd2"}, + {file = "pystackreg-0.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a86ede4a49025837a693cd25d6f24068b3f7dfd96876c96c92748a353c150f2d"}, + {file = "pystackreg-0.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4bd5255f7bd08d622c470cefabb01ad5bb5011b6efeaac1b4acb837612d04c33"}, + {file = "pystackreg-0.2.7-cp310-cp310-win32.whl", hash = "sha256:ad777ed187db49066be94eecc54501e2f1ddba253a7e27808395673727fa230f"}, + {file = "pystackreg-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:9971c796d969ac8b4099c5b33bce9e9495bcbe1a2ab949bbc4307f41ade3af81"}, + {file = "pystackreg-0.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:172325eb86a18c1b01d355abcae8bd283a017210de1eb00fe5f9dc4bb34ed89b"}, + {file = "pystackreg-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f48ab87aed5899fdc3d19b1aec2c724232306f9f554c50a91481a116ae3e7e6"}, + {file = "pystackreg-0.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8d92b163a200d0887dca388f1c7fdb8d2c67fcd3759a8818f759f4ca3e4eabb"}, + {file = "pystackreg-0.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a769322ef45679f0f3a5c586dfac2cb0032c642fb5cb67d099a5abc8d0ac835a"}, + {file = "pystackreg-0.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a81c586f591be678f9ccd6f65e3db7e8c803076708566904978d5add4f31ed9"}, + {file = "pystackreg-0.2.7-cp311-cp311-win32.whl", hash = "sha256:14d732973fded0918e6e7ffb8ab39cf785b212a2b3811a1da2b6d4a08ac05824"}, + {file = "pystackreg-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:f0929d0f73579134219231c3c5430c27405524f45e313d8440ddb605e9fb64f2"}, + {file = "pystackreg-0.2.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d26158f450814f0cbe7f0e82bfb8885411e7e414604bb18c83703a6c6f369057"}, + {file = "pystackreg-0.2.7-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:145c8da0e665fef1a4c2ace1bfb89a7da5f2d0bcb9105835633f5068e66417a1"}, + {file = "pystackreg-0.2.7-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caa85bb05e3420a22fbc162af6f7f40b641b116996bf3063d2b1705e2551b694"}, + {file = "pystackreg-0.2.7-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:ade10bf20699130a691a21296cf45bc05d215d5ed9e7f1ac1d349be2d4a80d76"}, + {file = "pystackreg-0.2.7-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:20301a7f7d8d2e2ad2364c7fe1fe9d9fac09891c264277d84d1433c2f6a73374"}, + {file = "pystackreg-0.2.7-cp36-cp36m-win32.whl", hash = "sha256:f00c3bbc95e2a5e9a1a2bdd9744f706bda782368d1eda4460d7fd0ac57a0bd79"}, + {file = "pystackreg-0.2.7-cp36-cp36m-win_amd64.whl", hash = "sha256:94d03384a78c114cdc6985bd38904e06eeea7cc5ea749090ce824ee4b753fc4f"}, + {file = "pystackreg-0.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bc3ca3d37be18f8007a7a0ec346eba175d9c1cf0f9092138ec85b554e3252f2b"}, + {file = "pystackreg-0.2.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7c41ef96599df802aa4fc2c2d8ea25faa955a5138fd2b671e25204f1b57108"}, + {file = "pystackreg-0.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:706a041d2803bd5878f7e864d8bc83919b9c5020d00288dcd9f5f00a8b78dc13"}, + {file = "pystackreg-0.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b7ad9206bbcfdc790553cf8cbb5dc67199cff85d69977458d174f63e71912ef7"}, + {file = "pystackreg-0.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0a271a0e8d1b17d333f800df86ed6e66a400da76e073117b29fe276fbf2cfd2b"}, + {file = "pystackreg-0.2.7-cp37-cp37m-win32.whl", hash = "sha256:66406f463c7a553cef67e7cc8915df1ff3a656b0bc5550d652c7c37d78665d87"}, + {file = "pystackreg-0.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:c45c2b6a4055504da57b1e6e9561bd8eab839c6854dd0a6d9562c59bbfcf3755"}, + {file = "pystackreg-0.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f742982644a219d59a7f21d89e553999bc36077ad9b421453d502b05f8e5ec39"}, + {file = "pystackreg-0.2.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b4145010dad6bdabb964aa2a30ddb68b66746d2c6427f4a992f46fd5ea3352"}, + {file = "pystackreg-0.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b79308d966f514d694079379c95c3930aeee87bde6f5aa3e5843426eb5f037f"}, + {file = "pystackreg-0.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bbbc667bd02ff138fc6975e27ec78269bd748671d0a208d41c3fbe4749ef31f6"}, + {file = "pystackreg-0.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:70b4285619759addce2c4c820e8d653823655045425a6c1bcf01f0162a4cd1f8"}, + {file = "pystackreg-0.2.7-cp38-cp38-win32.whl", hash = "sha256:a7fbd152cf4fc25096e7b9a923cbc3c93eb81eb6fd3967ff39796e878dddead5"}, + {file = "pystackreg-0.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:d151069aa8bd7e195da8774266ffc552a2c45691006f492c1dcdae2a712cd6b5"}, + {file = "pystackreg-0.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c5cd5994e66607970c48b5dcd9245a0ba9fd0ba3e8a893961cad1ea2ea36947"}, + {file = "pystackreg-0.2.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38bd62101b5f28c8d9eec11dae969207301df156d19b921ec41ca5c2cfcfc557"}, + {file = "pystackreg-0.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2f7b49a3136b962867312be8025d240822fa2e5b7e7928fc5f94a780c031a58"}, + {file = "pystackreg-0.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02f56db62e72827ec106cd1d3b10e84a6123525a2764305cbf0f18e78f19d3f"}, + {file = "pystackreg-0.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6e4dc4ec44d16d5ff2500ce4d6b9f7efdc1ca19bb7ba233702a8e8a9efda861b"}, + {file = "pystackreg-0.2.7-cp39-cp39-win32.whl", hash = "sha256:395d057824fccaceb416496a886bd674f9b39350acffc85e063848ecc425894e"}, + {file = "pystackreg-0.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:fb361ba53afe325722c06cea43037f69db50bd2fa83d77ee1ab537b6b76c67fa"}, + {file = "pystackreg-0.2.7.tar.gz", hash = "sha256:c3df8b42bb9f4d44ec7ba44b3c061d7ea7481ab62318c24a25ffdb22afbaf3ab"}, +] + +[package.dependencies] +numpy = "*" +tqdm = "*" + +[[package]] +name = "pytest" +version = "8.3.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-box" +version = "7.2.0" +description = "Advanced Python dictionaries with dot notation access" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python_box-7.2.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:6bdeec791e25258351388b3029a3ec5da302bb9ed3be175493c43cdc6c47f5e3"}, + {file = "python_box-7.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c449f7b3756a71479fa9c61a86e344ac00ed782a66d7662590f0afa294249d18"}, + {file = "python_box-7.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:6b0d61f182d394106d963232854e495b51edc178faa5316a797be1178212d7e0"}, + {file = "python_box-7.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e2d752de8c1204255bf7b0c814c59ef48293c187a7e9fdcd2fefa28024b72032"}, + {file = "python_box-7.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8a6c35ea356a386077935958a5debcd5b229b9a1b3b26287a52dfe1a7e65d99"}, + {file = "python_box-7.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:32ed58ec4d9e5475efe69f9c7d773dfea90a6a01979e776da93fd2b0a5d04429"}, + {file = "python_box-7.2.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2a2d664c6a27f7515469b6f1e461935a2038ee130b7d194b4b4db4e85d363618"}, + {file = "python_box-7.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8a5a7365db1aaf600d3e8a2747fcf6833beb5d45439a54318548f02e302e3ec"}, + {file = "python_box-7.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:739f827056ea148cbea3122d4617c994e829b420b1331183d968b175304e3a4f"}, + {file = "python_box-7.2.0-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:2617ef3c3d199f55f63c908f540a4dc14ced9b18533a879e6171c94a6a436f23"}, + {file = "python_box-7.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffd866bed03087b1d8340014da8c3aaae19135767580641df1b4ae6fff6ac0aa"}, + {file = "python_box-7.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:9681f059e7e92bdf20782cd9ea6e533d4711fc7b8c57a462922a025d46add4d0"}, + {file = "python_box-7.2.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:6b59b1e2741c9ceecdf5a5bd9b90502c24650e609cd824d434fed3b6f302b7bb"}, + {file = "python_box-7.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23fae825d809ae7520fdeac88bb52be55a3b63992120a00e381783669edf589"}, + {file = "python_box-7.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:573b1abdcb7bd745fa404444f060ee62fc35a74f067181e55dcb43cfe92f2827"}, + {file = "python_box-7.2.0-py3-none-any.whl", hash = "sha256:a3c90832dd772cb0197fdb5bc06123b6e1b846899a1b53d9c39450d27a584829"}, + {file = "python_box-7.2.0.tar.gz", hash = "sha256:551af20bdab3a60a2a21e3435120453c4ca32f7393787c3a5036e1d9fc6a0ede"}, +] + +[package.extras] +all = ["msgpack", "ruamel.yaml (>=0.17)", "toml"] +msgpack = ["msgpack"] +pyyaml = ["PyYAML"] +ruamel-yaml = ["ruamel.yaml (>=0.17)"] +toml = ["toml"] +tomli = ["tomli", "tomli-w"] +yaml = ["ruamel.yaml (>=0.17)"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "pyzmq" +version = "26.2.0" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629"}, + {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea"}, + {file = "pyzmq-26.2.0-cp310-cp310-win32.whl", hash = "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2"}, + {file = "pyzmq-26.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971"}, + {file = "pyzmq-26.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa"}, + {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218"}, + {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6"}, + {file = "pyzmq-26.2.0-cp311-cp311-win32.whl", hash = "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4"}, + {file = "pyzmq-26.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5"}, + {file = "pyzmq-26.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003"}, + {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9"}, + {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b"}, + {file = "pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7"}, + {file = "pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a"}, + {file = "pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b"}, + {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726"}, + {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e"}, + {file = "pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5"}, + {file = "pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad"}, + {file = "pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797"}, + {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a"}, + {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0"}, + {file = "pyzmq-26.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b55a4229ce5da9497dd0452b914556ae58e96a4381bb6f59f1305dfd7e53fc8"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9cb3a6460cdea8fe8194a76de8895707e61ded10ad0be97188cc8463ffa7e3a8"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ab5cad923cc95c87bffee098a27856c859bd5d0af31bd346035aa816b081fe1"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ed69074a610fad1c2fda66180e7b2edd4d31c53f2d1872bc2d1211563904cd9"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cccba051221b916a4f5e538997c45d7d136a5646442b1231b916d0164067ea27"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:0eaa83fc4c1e271c24eaf8fb083cbccef8fde77ec8cd45f3c35a9a123e6da097"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9edda2df81daa129b25a39b86cb57dfdfe16f7ec15b42b19bfac503360d27a93"}, + {file = "pyzmq-26.2.0-cp37-cp37m-win32.whl", hash = "sha256:ea0eb6af8a17fa272f7b98d7bebfab7836a0d62738e16ba380f440fceca2d951"}, + {file = "pyzmq-26.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4ff9dc6bc1664bb9eec25cd17506ef6672d506115095411e237d571e92a58231"}, + {file = "pyzmq-26.2.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2eb7735ee73ca1b0d71e0e67c3739c689067f055c764f73aac4cc8ecf958ee3f"}, + {file = "pyzmq-26.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a534f43bc738181aa7cbbaf48e3eca62c76453a40a746ab95d4b27b1111a7d2"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aedd5dd8692635813368e558a05266b995d3d020b23e49581ddd5bbe197a8ab6"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8be4700cd8bb02cc454f630dcdf7cfa99de96788b80c51b60fe2fe1dac480289"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fcc03fa4997c447dce58264e93b5aa2d57714fbe0f06c07b7785ae131512732"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:402b190912935d3db15b03e8f7485812db350d271b284ded2b80d2e5704be780"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8685fa9c25ff00f550c1fec650430c4b71e4e48e8d852f7ddcf2e48308038640"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:76589c020680778f06b7e0b193f4b6dd66d470234a16e1df90329f5e14a171cd"}, + {file = "pyzmq-26.2.0-cp38-cp38-win32.whl", hash = "sha256:8423c1877d72c041f2c263b1ec6e34360448decfb323fa8b94e85883043ef988"}, + {file = "pyzmq-26.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:76589f2cd6b77b5bdea4fca5992dc1c23389d68b18ccc26a53680ba2dc80ff2f"}, + {file = "pyzmq-26.2.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:b1d464cb8d72bfc1a3adc53305a63a8e0cac6bc8c5a07e8ca190ab8d3faa43c2"}, + {file = "pyzmq-26.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4da04c48873a6abdd71811c5e163bd656ee1b957971db7f35140a2d573f6949c"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d049df610ac811dcffdc147153b414147428567fbbc8be43bb8885f04db39d98"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05590cdbc6b902101d0e65d6a4780af14dc22914cc6ab995d99b85af45362cc9"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c811cfcd6a9bf680236c40c6f617187515269ab2912f3d7e8c0174898e2519db"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6835dd60355593de10350394242b5757fbbd88b25287314316f266e24c61d073"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc6bee759a6bddea5db78d7dcd609397449cb2d2d6587f48f3ca613b19410cfc"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c530e1eecd036ecc83c3407f77bb86feb79916d4a33d11394b8234f3bd35b940"}, + {file = "pyzmq-26.2.0-cp39-cp39-win32.whl", hash = "sha256:367b4f689786fca726ef7a6c5ba606958b145b9340a5e4808132cc65759abd44"}, + {file = "pyzmq-26.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:e6fa2e3e683f34aea77de8112f6483803c96a44fd726d7358b9888ae5bb394ec"}, + {file = "pyzmq-26.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:7445be39143a8aa4faec43b076e06944b8f9d0701b669df4af200531b21e40bb"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2ea4ad4e6a12e454de05f2949d4beddb52460f3de7c8b9d5c46fbb7d7222e02c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fc4f7a173a5609631bb0c42c23d12c49df3966f89f496a51d3eb0ec81f4519d6"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:878206a45202247781472a2d99df12a176fef806ca175799e1c6ad263510d57c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17c412bad2eb9468e876f556eb4ee910e62d721d2c7a53c7fa31e643d35352e6"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0d987a3ae5a71c6226b203cfd298720e0086c7fe7c74f35fa8edddfbd6597eed"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:39887ac397ff35b7b775db7201095fc6310a35fdbae85bac4523f7eb3b840e20"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fdb5b3e311d4d4b0eb8b3e8b4d1b0a512713ad7e6a68791d0923d1aec433d919"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:226af7dcb51fdb0109f0016449b357e182ea0ceb6b47dfb5999d569e5db161d5"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bed0e799e6120b9c32756203fb9dfe8ca2fb8467fed830c34c877e25638c3fc"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:29c7947c594e105cb9e6c466bace8532dc1ca02d498684128b339799f5248277"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cdeabcff45d1c219636ee2e54d852262e5c2e085d6cb476d938aee8d921356b3"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35cffef589bcdc587d06f9149f8d5e9e8859920a071df5a2671de2213bef592a"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18c8dc3b7468d8b4bdf60ce9d7141897da103c7a4690157b32b60acb45e333e6"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7133d0a1677aec369d67dd78520d3fa96dd7f3dcec99d66c1762870e5ea1a50a"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a96179a24b14fa6428cbfc08641c779a53f8fcec43644030328f44034c7f1f4"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4f78c88905461a9203eac9faac157a2a0dbba84a0fd09fd29315db27be40af9f"}, + {file = "pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rosettasciio" +version = "0.6" +description = "Reading and writing scientific file formats" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rosettasciio-0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9f0696b97710bd1512671e3cc2ff7c4743394eafef66fb2b9b3f6bafdd6c163a"}, + {file = "rosettasciio-0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b0dfe8f5d4af123610fb042381f210afb39c475a0d196fb6942c54cc73032f39"}, + {file = "rosettasciio-0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39d39cbd6e159879b376deb114ae688499e56707f800c6e53fb8d1dc23b50b81"}, + {file = "rosettasciio-0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56f80a7de51346ebf3ae817618a5c2d225aae000ce2c223e49fbfbb2fc562df3"}, + {file = "rosettasciio-0.6-cp310-cp310-win_amd64.whl", hash = "sha256:ee105dccb31ad0b7a76d10c1f4edb3c7f6c53a1f4907a6a704edef10df29c49e"}, + {file = "rosettasciio-0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a990d0b43d6820215a52511a2a3c8ad5696ebf7a326ddb3be98f9439ee26e710"}, + {file = "rosettasciio-0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b54660dad1085ab00350c990cc129c06425b52079557c0093d47c943b3c5923"}, + {file = "rosettasciio-0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ba0a6951fd864f21c66f07e1e2f716d85e1107311a127a7b79ed2d6790b7c29"}, + {file = "rosettasciio-0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1a7eb42256112c070a6018282c77f60c1ee6123c7f045297e150253c977c53b"}, + {file = "rosettasciio-0.6-cp311-cp311-win_amd64.whl", hash = "sha256:7ae6185e9c2965dbd81c97affe2bd13d571a19e85892a95c03f4494b10dbcccb"}, + {file = "rosettasciio-0.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8cb83340735d57216ddadd2b0053fa0551a061a3ce8deac87f1fbc4a15c240c2"}, + {file = "rosettasciio-0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2becdc1d5f65a3b6081364298702d24afdd46cceb2702bbd738bbbc1f59ad976"}, + {file = "rosettasciio-0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73576cfb3ba88a5c8b1699bb76838a1d857fe399b23797cdc39f9e146af8359c"}, + {file = "rosettasciio-0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10fe816f05d5776136694f70534002e5e1d55cd37637fa52ea497db2d1c48823"}, + {file = "rosettasciio-0.6-cp312-cp312-win_amd64.whl", hash = "sha256:2c3e1e2b21a6ccc8b2fe5211ee1337108241a48c2132eed5522511e1a2af7960"}, + {file = "rosettasciio-0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7cf31e13fd94384bc7963f80170b397f8331f9a097ae7a5bb167fab58f5e7151"}, + {file = "rosettasciio-0.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:380137103ce67ac848e7ec17f2367fc1d1d93ec3c80f44529b091b5ecf2b7327"}, + {file = "rosettasciio-0.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d5e8bea6c0e6cad706cdde3d93296d3abc660f9ff929d2bb82096f5aea4798c"}, + {file = "rosettasciio-0.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b35709a423a7e2c1d738f156e2653e054b6031bbe3610793ec71572e7ac47e89"}, + {file = "rosettasciio-0.6-cp38-cp38-win_amd64.whl", hash = "sha256:7f4592d163640403cbefb67a731b048522a5cbd253f4ea8bd4217ea93cbf4d3a"}, + {file = "rosettasciio-0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0e5c933a81504ce0325930302f87f63bee9848200e4757e136cac24cb3cda1f7"}, + {file = "rosettasciio-0.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2f94f9ab539b3a4a8adac078a0b13ea8903a5b12f91c903bce9c1ef8f2fbdf2"}, + {file = "rosettasciio-0.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:363391b941f3ba6d07aad97ed348137f63b2b170a39410c41a33e3dd6a648b4c"}, + {file = "rosettasciio-0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c48bd71320f4ed7b4890d856ac869d5b09a4e8685b260a54572d2783ddfe83e0"}, + {file = "rosettasciio-0.6-cp39-cp39-win_amd64.whl", hash = "sha256:2670089e11e7ddbd40cfc67453be2151e3d3b39d88498d4eb50852563c98884d"}, + {file = "rosettasciio-0.6-py3-none-any.whl", hash = "sha256:1160fcd4901da97eb836f2adee68f5a8afcb46f28164d073b10d51c2b60e4b99"}, + {file = "rosettasciio-0.6.tar.gz", hash = "sha256:6d7f40d0577b4a1905da147697b64994a4abad84c2f2b4eb6cf00330e0bc01a7"}, +] + +[package.dependencies] +dask = {version = ">=2021.3.1", extras = ["array"]} +h5py = {version = ">=2.3", optional = true, markers = "extra == \"hdf5\""} +numpy = ">=1.20" +pint = ">=0.8" +python-box = ">=6,<8" +python-dateutil = "*" +pyyaml = "*" + +[package.extras] +all = ["rosettasciio[blockfile]", "rosettasciio[eds-stream]", "rosettasciio[hdf5]", "rosettasciio[mrcz]", "rosettasciio[scalebar-export]", "rosettasciio[speed]", "rosettasciio[tiff]", "rosettasciio[usid]", "rosettasciio[zspy]"] +blockfile = ["scikit-image (>=0.18)"] +dev = ["rosettasciio[all]", "rosettasciio[doc]", "rosettasciio[tests]", "ruff"] +doc = ["numpydoc", "pooch", "pydata-sphinx-theme (>=0.13)", "setuptools-scm", "sphinx", "sphinx-favicon", "sphinxcontrib-towncrier", "towncrier"] +eds-stream = ["sparse"] +hdf5 = ["h5py (>=2.3)"] +image = ["imageio (>=2.16)"] +mrcz = ["blosc (>=1.5)", "mrcz (>=0.3.6)"] +scalebar-export = ["matplotlib (>=3.5)", "matplotlib-scalebar"] +speed = ["numba (>=0.52)"] +tests = ["filelock", "pooch", "pytest (>=3.6)", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "setuptools-scm"] +tiff = ["imagecodecs", "tifffile (>=2022.7.28)"] +usid = ["pyUSID (>=0.0.11)"] +zspy = ["msgpack", "zarr"] + +[[package]] +name = "ruff" +version = "0.6.6" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.6.6-py3-none-linux_armv6l.whl", hash = "sha256:f5bc5398457484fc0374425b43b030e4668ed4d2da8ee7fdda0e926c9f11ccfb"}, + {file = "ruff-0.6.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:515a698254c9c47bb84335281a170213b3ee5eb47feebe903e1be10087a167ce"}, + {file = "ruff-0.6.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6bb1b4995775f1837ab70f26698dd73852bbb82e8f70b175d2713c0354fe9182"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c546f412dfae8bb9cc4f27f0e45cdd554e42fecbb34f03312b93368e1cd0a6"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:59627e97364329e4eae7d86fa7980c10e2b129e2293d25c478ebcb861b3e3fd6"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94c3f78c3d32190aafbb6bc5410c96cfed0a88aadb49c3f852bbc2aa9783a7d8"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:704da526c1e137f38c8a067a4a975fe6834b9f8ba7dbc5fd7503d58148851b8f"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:efeede5815a24104579a0f6320660536c5ffc1c91ae94f8c65659af915fb9de9"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e368aef0cc02ca3593eae2fb8186b81c9c2b3f39acaaa1108eb6b4d04617e61f"}, + {file = "ruff-0.6.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2653fc3b2a9315bd809725c88dd2446550099728d077a04191febb5ea79a4f79"}, + {file = "ruff-0.6.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bb858cd9ce2d062503337c5b9784d7b583bcf9d1a43c4df6ccb5eab774fbafcb"}, + {file = "ruff-0.6.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:488f8e15c01ea9afb8c0ba35d55bd951f484d0c1b7c5fd746ce3c47ccdedce68"}, + {file = "ruff-0.6.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:aefb0bd15f1cfa4c9c227b6120573bb3d6c4ee3b29fb54a5ad58f03859bc43c6"}, + {file = "ruff-0.6.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a4c0698cc780bcb2c61496cbd56b6a3ac0ad858c966652f7dbf4ceb029252fbe"}, + {file = "ruff-0.6.6-py3-none-win32.whl", hash = "sha256:aadf81ddc8ab5b62da7aae78a91ec933cbae9f8f1663ec0325dae2c364e4ad84"}, + {file = "ruff-0.6.6-py3-none-win_amd64.whl", hash = "sha256:0adb801771bc1f1b8cf4e0a6fdc30776e7c1894810ff3b344e50da82ef50eeb1"}, + {file = "ruff-0.6.6-py3-none-win_arm64.whl", hash = "sha256:4b4d32c137bc781c298964dd4e52f07d6f7d57c03eae97a72d97856844aa510a"}, + {file = "ruff-0.6.6.tar.gz", hash = "sha256:0fc030b6fd14814d69ac0196396f6761921bd20831725c7361e1b8100b818034"}, +] + +[[package]] +name = "scikit-image" +version = "0.24.0" +description = "Image processing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "scikit_image-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb3bc0264b6ab30b43c4179ee6156bc18b4861e78bb329dd8d16537b7bbf827a"}, + {file = "scikit_image-0.24.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9c7a52e20cdd760738da38564ba1fed7942b623c0317489af1a598a8dedf088b"}, + {file = "scikit_image-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93f46e6ce42e5409f4d09ce1b0c7f80dd7e4373bcec635b6348b63e3c886eac8"}, + {file = "scikit_image-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39ee0af13435c57351a3397eb379e72164ff85161923eec0c38849fecf1b4764"}, + {file = "scikit_image-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ac7913b028b8aa780ffae85922894a69e33d1c0bf270ea1774f382fe8bf95e7"}, + {file = "scikit_image-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:272909e02a59cea3ed4aa03739bb88df2625daa809f633f40b5053cf09241831"}, + {file = "scikit_image-0.24.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:190ebde80b4470fe8838764b9b15f232a964f1a20391663e31008d76f0c696f7"}, + {file = "scikit_image-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59c98cc695005faf2b79904e4663796c977af22586ddf1b12d6af2fa22842dc2"}, + {file = "scikit_image-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa27b3a0dbad807b966b8db2d78da734cb812ca4787f7fbb143764800ce2fa9c"}, + {file = "scikit_image-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:dacf591ac0c272a111181afad4b788a27fe70d213cfddd631d151cbc34f8ca2c"}, + {file = "scikit_image-0.24.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6fccceb54c9574590abcddc8caf6cefa57c13b5b8b4260ab3ff88ad8f3c252b3"}, + {file = "scikit_image-0.24.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ccc01e4760d655aab7601c1ba7aa4ddd8b46f494ac46ec9c268df6f33ccddf4c"}, + {file = "scikit_image-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18836a18d3a7b6aca5376a2d805f0045826bc6c9fc85331659c33b4813e0b563"}, + {file = "scikit_image-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8579bda9c3f78cb3b3ed8b9425213c53a25fa7e994b7ac01f2440b395babf660"}, + {file = "scikit_image-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:82ab903afa60b2da1da2e6f0c8c65e7c8868c60a869464c41971da929b3e82bc"}, + {file = "scikit_image-0.24.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef04360eda372ee5cd60aebe9be91258639c86ae2ea24093fb9182118008d009"}, + {file = "scikit_image-0.24.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e9aadb442360a7e76f0c5c9d105f79a83d6df0e01e431bd1d5757e2c5871a1f3"}, + {file = "scikit_image-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e37de6f4c1abcf794e13c258dc9b7d385d5be868441de11c180363824192ff7"}, + {file = "scikit_image-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4688c18bd7ec33c08d7bf0fd19549be246d90d5f2c1d795a89986629af0a1e83"}, + {file = "scikit_image-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:56dab751d20b25d5d3985e95c9b4e975f55573554bd76b0aedf5875217c93e69"}, + {file = "scikit_image-0.24.0.tar.gz", hash = "sha256:5d16efe95da8edbeb363e0c4157b99becbd650a60b77f6e3af5768b66cf007ab"}, +] + +[package.dependencies] +imageio = ">=2.33" +lazy-loader = ">=0.4" +networkx = ">=2.8" +numpy = ">=1.23" +packaging = ">=21" +pillow = ">=9.1" +scipy = ">=1.9" +tifffile = ">=2022.8.12" + +[package.extras] +build = ["Cython (>=3.0.4)", "build", "meson-python (>=0.15)", "ninja", "numpy (>=2.0.0rc1)", "packaging (>=21)", "pythran", "setuptools (>=67)", "spin (==0.8)", "wheel"] +data = ["pooch (>=1.6.0)"] +developer = ["ipython", "pre-commit", "tomli"] +docs = ["PyWavelets (>=1.1.1)", "dask[array] (>=2022.9.2)", "ipykernel", "ipywidgets", "kaleido", "matplotlib (>=3.6)", "myst-parser", "numpydoc (>=1.7)", "pandas (>=1.5)", "plotly (>=5.10)", "pooch (>=1.6)", "pydata-sphinx-theme (>=0.15.2)", "pytest-doctestplus", "pytest-runner", "scikit-learn (>=1.1)", "seaborn (>=0.11)", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-gallery (>=0.14)", "sphinx_design (>=0.5)", "tifffile (>=2022.8.12)"] +optional = ["PyWavelets (>=1.1.1)", "SimpleITK", "astropy (>=5.0)", "cloudpickle (>=0.2.1)", "dask[array] (>=2021.1.0)", "matplotlib (>=3.6)", "pooch (>=1.6.0)", "pyamg", "scikit-learn (>=1.1)"] +test = ["asv", "numpydoc (>=1.7)", "pooch (>=1.6.0)", "pytest (>=7.0)", "pytest-cov (>=2.11.0)", "pytest-doctestplus", "pytest-faulthandler", "pytest-localserver"] + +[[package]] +name = "scipy" +version = "1.13.1" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, + {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f"}, + {file = "scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94"}, + {file = "scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa"}, + {file = "scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59"}, + {file = "scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884"}, + {file = "scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16"}, + {file = "scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d"}, + {file = "scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c"}, + {file = "scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2"}, + {file = "scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c"}, +] + +[package.dependencies] +numpy = ">=1.22.4,<2.3" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] +test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "sympy" +version = "1.13.3" +description = "Computer algebra system (CAS) in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sympy-1.13.3-py3-none-any.whl", hash = "sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73"}, + {file = "sympy-1.13.3.tar.gz", hash = "sha256:b27fd2c6530e0ab39e275fc9b683895367e51d5da91baa8d3d64db2565fec4d9"}, +] + +[package.dependencies] +mpmath = ">=1.1.0,<1.4" + +[package.extras] +dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] + +[[package]] +name = "tifffile" +version = "2024.8.30" +description = "Read and write TIFF files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "tifffile-2024.8.30-py3-none-any.whl", hash = "sha256:8bc59a8f02a2665cd50a910ec64961c5373bee0b8850ec89d3b7b485bf7be7ad"}, + {file = "tifffile-2024.8.30.tar.gz", hash = "sha256:2c9508fe768962e30f87def61819183fb07692c258cb175b3c114828368485a4"}, +] + +[package.dependencies] +numpy = "*" + +[package.extras] +all = ["defusedxml", "fsspec", "imagecodecs (>=2023.8.12)", "lxml", "matplotlib", "zarr"] +codecs = ["imagecodecs (>=2023.8.12)"] +plot = ["matplotlib"] +test = ["cmapfile", "czifile", "dask", "defusedxml", "fsspec", "imagecodecs", "lfdfiles", "lxml", "ndtiff", "oiffile", "psdtags", "pytest", "roifile", "xarray", "zarr"] +xml = ["defusedxml", "lxml"] +zarr = ["fsspec", "zarr"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "toolz" +version = "0.12.1" +description = "List processing tools and functional utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, + {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, +] + +[[package]] +name = "tornado" +version = "6.4.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">=3.8" +files = [ + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, +] + +[[package]] +name = "tqdm" +version = "4.66.5" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, + {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "traits" +version = "6.4.3" +description = "Observable typed attributes for Python classes" +optional = false +python-versions = ">=3.7" +files = [ + {file = "traits-6.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:64bfbd0fc4c8fa25eb55d6d46a8f98992852acaf904618879dbbba87a09289aa"}, + {file = "traits-6.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1e97f8a45c161715dfb69d5248cb54210057b5bd649caf08041465374dadbc1"}, + {file = "traits-6.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb126a6485edab740b2f238e9b02c830f7c8f7c1112609b4b5d280aef3e9421"}, + {file = "traits-6.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35f0bd177409697a33e95d5e5066670db122f0b5451e7a0ddffc9752d2bf3f48"}, + {file = "traits-6.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57109bca87c1e9ec125332a758c95cdf940540efc4c53a30df8d1dfa119472b9"}, + {file = "traits-6.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46cc41d2c65b374fbc600f39fe4bd68bbd01bfbdd6629c29e39686a854d4cfd0"}, + {file = "traits-6.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2faa1467ac1da29f295a90ef0867474be79edb5aea133811c0e2403ac645adf9"}, + {file = "traits-6.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:053d7c176cc83b228d497cf96171792fb96b91a342bb9666071d513b4e064e66"}, + {file = "traits-6.4.3-cp310-cp310-win32.whl", hash = "sha256:371c2b24daa7534206c8353d2fc62663b7068c25eb5f9a2a865e115e940abedf"}, + {file = "traits-6.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:fa8bc65a24dc3638f94f4fbc7d60c1838bb4e569c73317d37ab57fb3f23abb08"}, + {file = "traits-6.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8b098ecde49f78bc2587f43880d1344a8a81c9862244ad7e63d48409a8855b5c"}, + {file = "traits-6.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8348b7f54f4a4be6c60872c23c5ec00db2322591d12d4eee3f3c7f472585504a"}, + {file = "traits-6.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f791cca287a428dda5adc833915789f60dea1241e9ec21bde47668538fb01f40"}, + {file = "traits-6.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5d8eaa97adfb079872e760fab29452506f3cbe03e37a055737dae620d06ad5c"}, + {file = "traits-6.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f622915217b5edfb95bf043a9eb75e7ab7c2f0697b59a92b3481e58c883b1e7"}, + {file = "traits-6.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3ed9ea3e7f830460fe39aeb170816152df87d8e99947034e74f2b58178599253"}, + {file = "traits-6.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:22d17a47dead1d78fcd7c85dc961e646c4b924ee6f0005b5b7020b88aeeee2ad"}, + {file = "traits-6.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:79f937db0b0bd61272867f4a104d86a132cffab6cf8046f590fed800d621d9bd"}, + {file = "traits-6.4.3-cp311-cp311-win32.whl", hash = "sha256:c51635d076b4c2919e7fd7e82198cd7c3d5d0beee2cc4a5c366f2706dee1e465"}, + {file = "traits-6.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:c0e8ef49bcf8ab4e880009de934ce06a4f3fe47b3a064807eebd0e80798c0ab5"}, + {file = "traits-6.4.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:dba809d945d599980694b69c5d5756115959a3899afa9354f0994eebdc843e14"}, + {file = "traits-6.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e1600875fc3b1cd0502ce0d4317479e0ae0b91e4fa6f14fff1cc525eb1ad088e"}, + {file = "traits-6.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92add65d02b4d9ebd6b607948eff84c3a4dfaa642335e3d63c5d8c5a52f08e98"}, + {file = "traits-6.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13181f5433dc2d2fc2d9ee3f7ee3d0ede734c4eb53311ab9710500e7c45ae2c2"}, + {file = "traits-6.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2203ab21df7fd58d0eefb26c56501654ababcafe5e9299bac83dc1ce559be2f4"}, + {file = "traits-6.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:44a663117ebd197dbba4a9222ac0ecbb7a6bff7cf97e051924e987685025edd9"}, + {file = "traits-6.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:83f531a45d681bbe133953c5d5a5a4b572d06239672b3346fb927706bee0c9a9"}, + {file = "traits-6.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7b1f5763ae1e6b1cab0ce7c82f7da3a642170666fba32dd643181f564e576d4d"}, + {file = "traits-6.4.3-cp312-cp312-win32.whl", hash = "sha256:f1253043fb8f034c4342b78e56490a1f2386ed6a645cf5e33e3aa428b4b680b6"}, + {file = "traits-6.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:a01968bff6b13a0dc64842caf598ffc628bd973610b309d14b50fa92ce493a56"}, + {file = "traits-6.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:48394bbc474c6e160897d339791750e75aec638c426c0e930f54e308a43ada47"}, + {file = "traits-6.4.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b6777907777c595103e1c11c423eea907e895921b763eab0c8a213cc81d2224"}, + {file = "traits-6.4.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21e69c544a48223f5bb662fbf2b1810d90be6e1bc45245345cbddd446658b4b3"}, + {file = "traits-6.4.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2ca6513b6449877dc2f7e9c10aab7b2b6fca47d95920139dbefd1098eb4f2f1"}, + {file = "traits-6.4.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e53c14dd203d7527eeab52e3ea62dc360df634b1814e79c1015470d1ea254dd"}, + {file = "traits-6.4.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:01ae3e7567d4b06dcabb2d2c32e5fced42f05ebb1709cf41e7f4d816f3ce3eb0"}, + {file = "traits-6.4.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d4b7d5297b3c7541fda86a244f873aa29a9a09e7fd281b63f44ba3b864de6380"}, + {file = "traits-6.4.3-cp37-cp37m-win32.whl", hash = "sha256:2b09fad29dd5b153c510f699e65da87f04c5c7d25d03d2376b9f802e52c35219"}, + {file = "traits-6.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6b75ffe02d9b2f870b647df9a78740f65d9d599594060ab0985dcad3391d2b28"}, + {file = "traits-6.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4755a4ee9ea6bc9287e03e4ff5f0b80d12f62c10c1cb1b9fd4c8648650a485c8"}, + {file = "traits-6.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:370b892835e057e9353ea26a31127dccc427f72eb8f400200bf4d0ed4fa4293a"}, + {file = "traits-6.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d3f0887f6261f7595a80a97a995a229109e2902b66bf370caa4468ffe5b7f56"}, + {file = "traits-6.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:309131c6633eb80a6a73b90d0aef83dfbb8c81c81452c81dda0f80dc38df7cf6"}, + {file = "traits-6.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:996bdc5ffcb2fc1edc08ecfa86cb42fe49b80d1b71abcd8379c852b87c1d1ec6"}, + {file = "traits-6.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b6bda9dd716ab4ee296a24d3948dbc84beb2b06b2aadf3f56fe293750a234f40"}, + {file = "traits-6.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f950691085c32ac30d66d90e1347fd7971ffc118c1ba36c39aca7d3826f61b3"}, + {file = "traits-6.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dba974e6277353f01d5cd780b2efbf22142fc81e7907da3a50c6b49ed67f10bc"}, + {file = "traits-6.4.3-cp38-cp38-win32.whl", hash = "sha256:c7b8983528582c3b22f04b341470be512ca43d8036c96acd6a038fdb0ddd9769"}, + {file = "traits-6.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:cdf92353fd3522e409b245c025b424294264724ce6fa6c12b47fab56e66d915d"}, + {file = "traits-6.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab854a855947a544f7b6644086499d13e7a8f0c23dda9dbfc2f50b4439848303"}, + {file = "traits-6.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1951ef5a5702f80a83b825747969bd910eaa7f093658d092342eae8004617580"}, + {file = "traits-6.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd8467a47c4369e85efeb86a48d5e4540209b0f58f2a66ddad65d8245cddc86"}, + {file = "traits-6.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220b8faac11ae71045a9d0e65ef32e9298fc545f108605a149a81f38fdbfec9d"}, + {file = "traits-6.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51589af37bac46d8af91593a0f44116d416db830a59491f603e982fe2d96bed5"}, + {file = "traits-6.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:109f835eafe8106a76245b9dc6455d52cd315d60965987c3d27061f9f4a6fc22"}, + {file = "traits-6.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d3b5977372d983c39856ebdbd8389b82458b0907c1fc3a801f831ccb1de740a1"}, + {file = "traits-6.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:79fe4de34f6c5cb9aa5ba21dfbbf4ba232bd1b74882a49c3fe10883806ebf422"}, + {file = "traits-6.4.3-cp39-cp39-win32.whl", hash = "sha256:8af9f2bafea38a9747798a90b61ac5f806cc6d930889d993c899bb0862e0d3e1"}, + {file = "traits-6.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:723bb5c8b0f7f1125092f6ed8c1fc21d9673d64b6ed33b30f1933ab5de357f7d"}, + {file = "traits-6.4.3.tar.gz", hash = "sha256:a9bbfd9e0c08b7de07e86ef64e69cb96a29c2105a43bf832cd8b162fa1e22f44"}, +] + +[package.extras] +docs = ["Sphinx", "enthought-sphinx-theme", "pygments (<2.15)", "sphinx-copybutton"] +examples = ["numpy", "pillow"] +test = ["Cython", "PySide6", "Sphinx", "flake8", "flake8-ets", "mypy", "numpy", "pyface", "pygments (<2.15)", "setuptools", "traitsui"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[[package]] +name = "widgetsnbextension" +version = "4.0.13" +description = "Jupyter interactive widgets for Jupyter Notebook" +optional = false +python-versions = ">=3.7" +files = [ + {file = "widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71"}, + {file = "widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6"}, +] + +[[package]] +name = "zipp" +version = "3.20.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "9b0300943ae26edf8c606064291d57674b1f28a28be72c342691847f2d821214" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..34deaec --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,61 @@ +[tool.poetry] +name = "etspy" +version = "0.8.0" +description = "Suite of tools for processing and reconstruction of electron tomography data" +authors = [ + "Andrew A. Herzing ", + "Joshua Taillon " +] +license = "NIST Public License" +readme = "README.md" +homepage = "https://pages.nist.gov/etspy/" +repository = "https://github.com/usnistgov/etspy/" +documentation = "https://pages.nist.gov/etspy/docs/" +packages = [ + { include = "etspy" } +] + +[tool.poetry.dependencies] +python = "^3.9" +pystackreg = "^0.2.7" +astra-toolbox = "^2.2.0" +hyperspy = "^2.1.1" +hyperspy-gui-ipywidgets = "^2.0.2" +ipykernel = "^6.29.5" +numba = "^0.60.0" +numpy = "^1.26.4" + +[tool.poetry.group.dev.dependencies] +pydocstyle = "^6.3.0" +pytest = "^8.3.3" +ruff = "^0.6.6" +isort = "^5.13.2" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.isort] +profile = "black" +src_paths = ["etspy"] + +[tool.ruff] + +[tool.ruff.lint] +select = [ + "F", "E", "W", "I", "N", "D", "YTT", "UP", "S", "FBT", "B", "A", "COM", "C4", + "DTZ", "T10", "EM", "EXE", "ISC", "ICN", "G", "INP", "PIE", "T20", "PYI", + "PT", "Q", "RSE", "RET", "SLF", "SIM", "TID", "TCH", "ARG", "PTH", "ERA", "PL", + "TRY", "RUF" +] +ignore = ["FBT001", "FBT002", "TRY003", "RET504"] +exclude = ["*.ipynb"] + +[tool.ruff.lint.per-file-ignores] +"etspy/tests/*" = ["S101", "D102"] + +[tool.ruff.lint.pylint] +max-args = 10 + +[tool.ruff.lint.pydocstyle] +convention = "numpy" diff --git a/setup.py b/setup.py deleted file mode 100644 index 3860285..0000000 --- a/setup.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Setup file for ETSpy package.""" - -from setuptools import setup - -setup( - name='etspy', - version='0.8', - author='Andrew A. Herzing', - description='Suite of tools for processing and reconstruction of electron ' - 'tomography data', - packages=['etspy'], - install_requires=[ - 'pystackreg', - ], - package_data={ - # Include all data files in the data directory - "etspy": ["tests/test_data/*", - "tests/test_data/DM_Series_Test/*", - "tests/test_data/SerialEM_Multiframe_Test/*" - ], - }, -)