From 9b22f18b9fca62bf108fa526ec236f469c4e265d Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 4 Mar 2026 15:58:55 +0000 Subject: [PATCH 01/36] update doctring typehints for MatchedBranch --- topostats/classes.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/topostats/classes.py b/topostats/classes.py index 1cc3bb4db9f..cc1b6c76f22 100644 --- a/topostats/classes.py +++ b/topostats/classes.py @@ -856,21 +856,21 @@ class MatchedBranch: Attributes ---------- - ordered_coords : npt.NDArray[np.int32] + ordered_coords : npt.NDArray[np.int32], optional Numpy array of ordered coordinates. - heights : npt.NDArray[np.number] + heights : npt.NDArray[np.number], optional Numpy array of heights. - distances : npt.NDArray[np.number] + distances : npt.NDArray[np.number], optional Numpy array of distances. - fwhm : float + fwhm : float, optional Full-width half maximum. - fwhm_half_maxs : list[float] + fwhm_half_maxs : list[float], optional Half-maximums from a matched branch. - fwhm_peaks : list[int | float] + fwhm_peaks : list[float], optional Peaks from a matched branch. - angles : float + angles : float | list[float], optional Angle between branches. - branch_statistics : dict[str, float | int | list[Any] | str] + branch_statistics : dict[str, float | int | list[Any] | str], optional Dictionary of branch statistics, ``fwhm``, ``fwhm_half_maxs`` and ``fwhm_peaks``. """ From da0dd7d214a43396b3d1af1f0fd457c6767f50ef Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 4 Mar 2026 16:04:07 +0000 Subject: [PATCH 02/36] update type hints for UnMatchedBranch --- topostats/classes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topostats/classes.py b/topostats/classes.py index cc1b6c76f22..796808147b4 100644 --- a/topostats/classes.py +++ b/topostats/classes.py @@ -935,11 +935,11 @@ class UnMatchedBranch: Attributes ---------- - angles : float + angles : float | list[float] Angle between branches. """ - angles: float | list[float] | None = None + angles: float | list[float] def __str__(self) -> str: """ From 676b9e997df86a6afe6a2c88b8c92c984a62206f Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 4 Mar 2026 17:08:07 +0000 Subject: [PATCH 03/36] set molecule_data outside then inside if-statement resolves typing error for later "molecule_data[i] = ..." as NoneType is not sub-scriptable --- topostats/tracing/ordered_tracing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/topostats/tracing/ordered_tracing.py b/topostats/tracing/ordered_tracing.py index ea822389610..f587b38f5b0 100644 --- a/topostats/tracing/ordered_tracing.py +++ b/topostats/tracing/ordered_tracing.py @@ -668,8 +668,9 @@ def run_nodestats_tracing(self) -> dict[str, npt.NDArray]: # each molecule this is stored along with the original topology_flip = self.compile_trace(reverse_min_conf_crossing=True)[1] - molecule_data = {} if ordered_traces is not None else None + molecule_data = None if ordered_traces is not None: + molecule_data = {} for i, mol_trace in enumerate(ordered_traces): if len(mol_trace) > 3: # if > 4 coords to trace self.mol_tracing_stats["circular"] = linear_or_circular(mol_trace[:, :2]) From 9cd75f99ed11a68211731f7661b9823bf73cfb91 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 4 Mar 2026 17:13:18 +0000 Subject: [PATCH 04/36] correct return type hint for remove_common_values --- topostats/tracing/ordered_tracing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topostats/tracing/ordered_tracing.py b/topostats/tracing/ordered_tracing.py index f587b38f5b0..88b386cf6d6 100644 --- a/topostats/tracing/ordered_tracing.py +++ b/topostats/tracing/ordered_tracing.py @@ -214,7 +214,7 @@ def compile_images( @staticmethod def remove_common_values( ordered_array: npt.NDArray, common_value_check_array: npt.NDArray, retain: list = () - ) -> np.array: + ) -> npt.ArrayLike: """ Remove common values in common_value_check_array from ordered_array while retaining specified coordinates. @@ -229,7 +229,7 @@ def remove_common_values( Returns ------- - np.array + npt.ArrayLike Unique ordered_array values and retained coordinates. Retains the order of ordered_array. """ # Convert the arrays to sets for faster common value lookup From aed52e78f8b9152e4b19e353d76d6a0a8cb399e1 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 4 Mar 2026 19:19:48 +0000 Subject: [PATCH 05/36] updating OrderedTrace doctsrings --- topostats/classes.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/topostats/classes.py b/topostats/classes.py index 796808147b4..357c986c600 100644 --- a/topostats/classes.py +++ b/topostats/classes.py @@ -1025,23 +1025,23 @@ class OrderedTrace: """ Class for Ordered Trace data and attributes. - molecule_data : dict[int, Molecule] + molecule_data : dict[int, Molecule], optional Dictionary of ``Molecule`` objects indexed by molecule number. - tracing_stats : dict | None + tracing_stats : dict, optional Tracing statistics. - grain_molstats : Any | None + grain_molstats : Any, optional Grain molecule statistics. - molecules : int + molecules : int, optional Number of molecules within the grain. - writhe : str + writhe : str, optional The writhe sign, can be either `+`, `-` or `0` for positive, negative or no writhe. - pixel_to_nm_scaling: np.float64 | None + pixel_to_nm_scaling: np.float64, optional Pixel to nm scaling. - images: dict[str, npt.NDArray] | None + images: dict[str, npt.NDArray], optional Diagnostic images produced during processing. - error: bool | None + error: bool, optional Errors encountered? - molecule_statistics : dict[int, dict[str, bool | str | float | None]] | None + molecule_statistics : dict[int, dict[str, bool | str | float | None]], optional Dictionary of molecule statistics, with one entry for each molecule. """ From 1cb93c1aa1191382d1be6ead72dd6dc30a2e58e0 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 4 Mar 2026 19:22:25 +0000 Subject: [PATCH 06/36] add error to collate_molecule_statistics Raises valueError if molecule_data is None --- topostats/classes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/topostats/classes.py b/topostats/classes.py index 357c986c600..e94769e51c6 100644 --- a/topostats/classes.py +++ b/topostats/classes.py @@ -1073,7 +1073,7 @@ def __str__(self) -> str: f"error : {self.error}" ) - def collate_molecule_statistics(self) -> dict[int, dict[str, bool | int | str | None]]: + def collate_molecule_statistics(self) -> dict[int, dict[str, bool | int | str | float | None]]: """ Collate molecule statistics for all molecules to dictionary. @@ -1084,6 +1084,9 @@ def collate_molecule_statistics(self) -> dict[int, dict[str, bool | int | str | dict[int, dict[str, bool | int | str | None]] Dictionary, indexed by molecule where the value is the molecules statistics for the given molecule. """ + if self.molecule_data is None: + raise ValueError("No molecule data found") + self.molecule_statistics = { molecule_number: molecule.collate_molecule_statistics() for molecule_number, molecule in self.molecule_data.items() From 1398614c7d8f9c5061e28c3deae6241eca538b0d Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 4 Mar 2026 19:24:19 +0000 Subject: [PATCH 07/36] update str dunder for when images are None --- topostats/classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topostats/classes.py b/topostats/classes.py index e94769e51c6..6f8c401532b 100644 --- a/topostats/classes.py +++ b/topostats/classes.py @@ -1067,7 +1067,7 @@ def __str__(self) -> str: writhe = {"+": "positive", "-": "negative", "0": "no writhe"}.get(self.writhe) return ( f"number of molecules : {self.molecules}\n" - f"number of images : {len(self.images)}\n" + f"number of images : {len(self.images) if self.images is not None else 'None'}\n" f"writhe : {writhe}\n" f"pixel to nm scaling : {self.pixel_to_nm_scaling}\n" f"error : {self.error}" From d27c27041548dbfffc0e78e0ff6a57ad4be189ca Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 4 Mar 2026 22:33:25 +0000 Subject: [PATCH 08/36] update type hints for GrainCrop image get & set --- topostats/classes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topostats/classes.py b/topostats/classes.py index 6f8c401532b..d6fff72cf1b 100644 --- a/topostats/classes.py +++ b/topostats/classes.py @@ -201,7 +201,7 @@ def __str__(self) -> str: ) @property - def image(self) -> npt.NDArray[float]: + def image(self) -> npt.NDArray[np.floating]: """ Getter for the ``image`` attribute. @@ -213,7 +213,7 @@ def image(self) -> npt.NDArray[float]: return self._image @image.setter - def image(self, value: npt.NDArray[float]): + def image(self, value: npt.NDArray[np.floating]): """ Setter for the ``image`` attribute. From 67da943280215f95278f37f9e58bc6df8aa61835 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 4 Mar 2026 22:56:39 +0000 Subject: [PATCH 09/36] update return type hint for calculate_region_connection_regions --- topostats/grains.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topostats/grains.py b/topostats/grains.py index 82f6980d79c..e0e56a7a82d 100644 --- a/topostats/grains.py +++ b/topostats/grains.py @@ -1139,7 +1139,7 @@ def keep_largest_labelled_region_classes( def calculate_region_connection_regions( grain_mask_tensor: npt.NDArray, classes: tuple[int, int], - ) -> tuple[int, npt.NDArray, dict[int, npt.NDArray[int]]]: + ) -> tuple[int, npt.NDArray, dict[int, npt.NDArray[np.integer]]]: """ Get a list of connection regions between two classes. From 9e66b26282edd2821d66411debaf4e1720e006ef Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Thu, 5 Mar 2026 08:45:52 +0000 Subject: [PATCH 10/36] Set directories to Path types in process_scan --- topostats/processing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topostats/processing.py b/topostats/processing.py index 4d440770f4b..152787ad3a2 100644 --- a/topostats/processing.py +++ b/topostats/processing.py @@ -1059,7 +1059,7 @@ def process_scan( """ # Setup configuration, we use that from the topostats_object.config if not explicitly given an option config = topostats_object.config.copy() - base_dir = config["base_dir"] if base_dir is None else base_dir + base_dir = Path(config["base_dir"] if base_dir is None else base_dir) filter_config = config["filter"] if filter_config is None else filter_config grains_config = config["grains"] if grains_config is None else grains_config grainstats_config = config["grainstats"] if grainstats_config is None else grainstats_config @@ -1071,7 +1071,7 @@ def process_scan( splining_config = config["splining"] if splining_config is None else splining_config curvature_config = config["curvature"] if curvature_config is None else curvature_config plotting_config = config["plotting"].copy() if plotting_config is None else plotting_config - output_dir = config["output_dir"] if output_dir is None else output_dir + output_dir = Path(config["output_dir"] if output_dir is None else output_dir) # Get output paths core_out_path, filter_out_path, grain_out_path, tracing_out_path, topostats_out_path = get_out_paths( From 05f8e38e8104c5aef6b3862e95f12d30f0b93b10 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Thu, 5 Mar 2026 08:47:28 +0000 Subject: [PATCH 11/36] update param type and dir type in process_filters --- topostats/processing.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/topostats/processing.py b/topostats/processing.py index 152787ad3a2..6b5152a45a5 100644 --- a/topostats/processing.py +++ b/topostats/processing.py @@ -1266,7 +1266,7 @@ def process_scan( def process_filters( - topostats_object: dict, + topostats_object: TopoStats, base_dir: str | Path, filter_config: dict, plotting_config: dict, @@ -1280,9 +1280,8 @@ def process_filters( Parameters ---------- - topostats_object : dict[str, Union[npt.NDArray, Path, float]] - A dictionary with keys 'image', 'img_path' and 'pixel_to_nm_scaling' containing a file or frames' image, it's - path and it's pixel to namometre scaling value. + topostats_object : Topostats + TopoStats object - Needs further info base_dir : str | Path Directory to recursively search for files, if not specified the current directory is scanned. filter_config : dict @@ -1300,7 +1299,7 @@ def process_filters( """ # Setup configuration, we use that from the topostats_object.config if not explicitly given an option config = topostats_object.config.copy() - base_dir = config["base_dir"] if base_dir is None else base_dir + base_dir = Path(config["base_dir"] if base_dir is None else base_dir) filter_config = config["filter"] if filter_config is None else filter_config plotting_config = config["plotting"] if plotting_config is None else plotting_config output_dir = config["output_dir"] From d40399858ea3e083446ab4df2664261ceaf8465c Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Thu, 5 Mar 2026 08:50:21 +0000 Subject: [PATCH 12/36] update param type and dir type in process_grains --- topostats/processing.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/topostats/processing.py b/topostats/processing.py index 6b5152a45a5..96449406203 100644 --- a/topostats/processing.py +++ b/topostats/processing.py @@ -1333,7 +1333,7 @@ def process_filters( def process_grains( - topostats_object: dict, + topostats_object: TopoStats, base_dir: str | Path, grains_config: dict, plotting_config: dict, @@ -1347,9 +1347,9 @@ def process_grains( Parameters ---------- - topostats_object : dict[str, Union[npt.NDArray, Path, float]] - A dictionary with keys 'image', 'img_path' and 'pixel_to_nm_scaling' containing a file or frames' image, it's - path and it's pixel to namometre scaling value. + topostats_object : TopoStats + TopoStats object - Needs further info + base_dir : str | Path Directory to recursively search for files, if not specified the current directory is scanned. grains_config : dict @@ -1367,7 +1367,7 @@ def process_grains( """ # Setup configuration, we use that from the topostats_object.config if not explicitly given an option config = topostats_object.config.copy() - base_dir = config["base_dir"] if base_dir is None else base_dir + base_dir = Path(config["base_dir"] if base_dir is None else base_dir) grains_config = config["grains"] if grains_config is None else grains_config plotting_config = config["plotting"] if plotting_config is None else plotting_config output_dir = config["output_dir"] From e9248faf9c1f70eb408ec9e8e89a13fdb14edc70 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Thu, 5 Mar 2026 08:51:12 +0000 Subject: [PATCH 13/36] update return type and dir type in process_grainstats --- topostats/processing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/topostats/processing.py b/topostats/processing.py index 96449406203..9ea985e9bdf 100644 --- a/topostats/processing.py +++ b/topostats/processing.py @@ -1405,7 +1405,7 @@ def process_grainstats( grainstats_config: dict, plotting_config: dict, output_dir: str | Path = "output", -) -> tuple[str, bool]: +) -> tuple[str | None, TopoStats, pd.DataFrame | None]: """ Calculate grain statistics in an image where grains have already been detected. @@ -1428,12 +1428,12 @@ def process_grainstats( Returns ------- - tuple[str, pd.DataFrame] + tuple[str, pd.DataFrame] - Deprecated, needs updating A tuple of the image and a boolean indicating if the image was successfully processed. """ # Setup configuration, we use that from the topostats_object.config if not explicitly given an option config = topostats_object.config.copy() - base_dir = config["base_dir"] if base_dir is None else base_dir + base_dir = Path(config["base_dir"] if base_dir is None else base_dir) grainstats_config = config["grainstats"] if grainstats_config is None else grainstats_config plotting_config = config["plotting"] if plotting_config is None else plotting_config output_dir = config["output_dir"] @@ -1462,7 +1462,7 @@ def process_grainstats( ) except: # noqa: E722 # pylint: disable=bare-except LOGGER.info(f"Grain detection failed for image : {topostats_object.filename}") - return topostats_object + return None, topostats_object, None # Grain Statistics grain_stats = { grain_number: grain_crop.stats for grain_number, grain_crop in topostats_object.grain_crops.items() From 7e447bfe7d5c6dfbbc93af237a7214a82b92b1d7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:06:15 +0000 Subject: [PATCH 14/36] [pre-commit.ci] Fixing issues with pre-commit --- topostats/classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topostats/classes.py b/topostats/classes.py index d6fff72cf1b..9d9922cd686 100644 --- a/topostats/classes.py +++ b/topostats/classes.py @@ -1086,7 +1086,7 @@ def collate_molecule_statistics(self) -> dict[int, dict[str, bool | int | str | """ if self.molecule_data is None: raise ValueError("No molecule data found") - + self.molecule_statistics = { molecule_number: molecule.collate_molecule_statistics() for molecule_number, molecule in self.molecule_data.items() From 2b25eb4e32db9c06c9c7695ca57070fecae5ba63 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Thu, 5 Mar 2026 18:49:10 +0000 Subject: [PATCH 15/36] Update run_nodestats_tracing docstring Adds correct return typehint Co-authored-by: Neil Shephard --- topostats/tracing/ordered_tracing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/topostats/tracing/ordered_tracing.py b/topostats/tracing/ordered_tracing.py index 88b386cf6d6..6c96af91ea9 100644 --- a/topostats/tracing/ordered_tracing.py +++ b/topostats/tracing/ordered_tracing.py @@ -651,7 +651,8 @@ def run_nodestats_tracing(self) -> dict[str, npt.NDArray]: Returns ------- tuple[list, dict, dict] - A list of each molecules ordered trace coordinates, the ordered_tracing stats, and the images. + dict[str, npt.NDArray] + A dictionary of ordered trace images. """ ordered_traces, topology, cross_add, crossing_coords, fwhms = self.compile_trace( reverse_min_conf_crossing=False From 67baf5f62734f077c25b493015897878fc8ad9bd Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Thu, 5 Mar 2026 18:54:25 +0000 Subject: [PATCH 16/36] Update docstring for process_grainstats Add correct return type hint Co-authored-by: Sylvia Whittle <86117496+SylviaWhittle@users.noreply.github.com> --- topostats/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topostats/processing.py b/topostats/processing.py index 9ea985e9bdf..f9e83a24a08 100644 --- a/topostats/processing.py +++ b/topostats/processing.py @@ -1428,7 +1428,7 @@ def process_grainstats( Returns ------- - tuple[str, pd.DataFrame] - Deprecated, needs updating + tuple[str | None, TopoStats, pd.DataFrame | None] A tuple of the image and a boolean indicating if the image was successfully processed. """ # Setup configuration, we use that from the topostats_object.config if not explicitly given an option From 13338664d98cec57ef337be1301882809dbd298e Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Thu, 5 Mar 2026 19:03:16 +0000 Subject: [PATCH 17/36] Update return description for process_grainstats docstring --- topostats/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topostats/processing.py b/topostats/processing.py index f9e83a24a08..649dea755e9 100644 --- a/topostats/processing.py +++ b/topostats/processing.py @@ -1429,7 +1429,7 @@ def process_grainstats( Returns ------- tuple[str | None, TopoStats, pd.DataFrame | None] - A tuple of the image and a boolean indicating if the image was successfully processed. + A tuple of the image name, the updated TopoStats object, and the grain statistics DataFrame or None.``` """ # Setup configuration, we use that from the topostats_object.config if not explicitly given an option config = topostats_object.config.copy() From a8c39f25b8509f01c7dfeae966520faf52ed6a51 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Thu, 5 Mar 2026 19:51:37 +0000 Subject: [PATCH 18/36] Update return typehint for order_from_end Correct to a tuple of str and dict --- topostats/tracing/ordered_tracing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topostats/tracing/ordered_tracing.py b/topostats/tracing/ordered_tracing.py index 6c96af91ea9..8e624b715f4 100644 --- a/topostats/tracing/ordered_tracing.py +++ b/topostats/tracing/ordered_tracing.py @@ -442,7 +442,7 @@ def get_trace_segment(remaining_img: npt.NDArray, ordered_segment_coords: list, return ordered_segment_coords[coord_idx][::-1] # end is endpoint @staticmethod - def order_from_end(last_segment_coord: npt.NDArray, current_segment: npt.NDArray) -> npt.NDArray: + def order_from_end(last_segment_coord: npt.NDArray, current_segment: npt.NDArray) -> tuple[npt.NDArray, bool]: """ Order the current segment to follow from the end of the previous one. From 305ac952195a216477b056c8982815e46f112d40 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Thu, 5 Mar 2026 19:53:28 +0000 Subject: [PATCH 19/36] Update identify_writhes return type hints Corrects the type hint and the docstring --- topostats/tracing/ordered_tracing.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/topostats/tracing/ordered_tracing.py b/topostats/tracing/ordered_tracing.py index 8e624b715f4..741424fe781 100644 --- a/topostats/tracing/ordered_tracing.py +++ b/topostats/tracing/ordered_tracing.py @@ -587,14 +587,16 @@ def check_node_errorless(self) -> bool: return False return True - def identify_writhes(self) -> str | dict: + def identify_writhes(self) -> tuple[str, dict]: """ Identify the writhe topology at each crossing in the image. Returns ------- - str | dict - A string of the whole grain writhe sign, and a dictionary linking each node to it's sign. + writhe_string: str + A string of the whole grain writhe sign + node_to_writhe: dict + a dictionary linking each node to its sign. """ # compile all vectors for each node and their z_idx # - want for each node, ordered vectors according to z_idx From eb68419dcf33705c2f330e1641c3e239aed70b3c Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Tue, 17 Mar 2026 12:47:54 +0000 Subject: [PATCH 20/36] Update param type hints in Filters class and methods --- topostats/filters.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/topostats/filters.py b/topostats/filters.py index a2f1afb5a90..a03af099d5a 100644 --- a/topostats/filters.py +++ b/topostats/filters.py @@ -66,9 +66,9 @@ def __init__( otsu_threshold_multiplier: float = 1.7, threshold_std_dev: dict | None = None, threshold_absolute: dict | None = None, - gaussian_size: float = None, + gaussian_size: float | None = None, gaussian_mode: str = "nearest", - remove_scars: dict = None, + remove_scars: dict | None = None, ): """ Initialise the class. @@ -153,7 +153,7 @@ def __init__( } def median_flatten( - self, image: npt.NDArray, mask: npt.NDArray = None, row_alignment_quantile: float = 0.5 + self, image: npt.NDArray, mask: npt.NDArray | None = None, row_alignment_quantile: float = 0.5 ) -> npt.NDArray: """ Flatten images using median differences. @@ -167,7 +167,7 @@ def median_flatten( ---------- image : npt.NDArray 2-D image of the data to align the rows of. - mask : npt.NDArray + mask : npt.NDArray, optional Boolean array of points to mask (ignore). row_alignment_quantile : float Quantile (in the range 0.0 to 1.0) used for defining the average background. @@ -196,7 +196,7 @@ def median_flatten( return image - def remove_tilt(self, image: npt.NDArray, mask: npt.NDArray = None) -> npt.NDArray: + def remove_tilt(self, image: npt.NDArray, mask: npt.NDArray | None = None) -> npt.NDArray: """ Remove the planar tilt from an image (linear in 2D spaces). @@ -207,7 +207,7 @@ def remove_tilt(self, image: npt.NDArray, mask: npt.NDArray = None) -> npt.NDArr ---------- image : npt.NDArray 2-D image of the data to remove the planar tilt from. - mask : npt.NDArray + mask : npt.NDArray, optional Boolean array of points to mask (ignore). Returns @@ -361,7 +361,7 @@ def model_func(x: float, y: float, a: float, b: float, c: float, d: float) -> fl return image - def remove_quadratic(self, image: npt.NDArray, mask: npt.NDArray = None) -> npt.NDArray: + def remove_quadratic(self, image: npt.NDArray, mask: npt.NDArray | None = None) -> npt.NDArray: """ Remove the quadratic bowing that can be seen in some large-scale AFM images. @@ -372,7 +372,7 @@ def remove_quadratic(self, image: npt.NDArray, mask: npt.NDArray = None) -> npt. ---------- image : npt.NDArray 2-D image of the data to remove the quadratic from. - mask : npt.NDArray + mask : npt.NDArray, optional Boolean array of points to mask (ignore). Returns @@ -445,7 +445,7 @@ def calc_gradient(self, array: npt.NDArray, shape: int) -> npt.NDArray: """ return self.calc_diff(array) / shape - def average_background(self, image: npt.NDArray, mask: npt.NDArray = None) -> npt.NDArray: + def average_background(self, image: npt.NDArray, mask: npt.NDArray | None = None) -> npt.NDArray: """ Zero the background by subtracting the non-masked mean from all pixels. @@ -453,7 +453,7 @@ def average_background(self, image: npt.NDArray, mask: npt.NDArray = None) -> np ---------- image : npt.NDArray Numpy array representing the image. - mask : npt.NDArray + mask : npt.NDArray, optional Mask of the array, should have the same dimensions as image. Returns From 879fc64a9a0aca3bac07fda3fd6d8dc96e69282a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:49:16 +0000 Subject: [PATCH 21/36] [pre-commit.ci] Fixing issues with pre-commit --- topostats/tracing/ordered_tracing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topostats/tracing/ordered_tracing.py b/topostats/tracing/ordered_tracing.py index 741424fe781..ddf6a2cac57 100644 --- a/topostats/tracing/ordered_tracing.py +++ b/topostats/tracing/ordered_tracing.py @@ -595,7 +595,7 @@ def identify_writhes(self) -> tuple[str, dict]: ------- writhe_string: str A string of the whole grain writhe sign - node_to_writhe: dict + node_to_writhe: dict a dictionary linking each node to its sign. """ # compile all vectors for each node and their z_idx From 9fa544c2645e7afb62f962191e73ba2e96dc1155 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Tue, 17 Mar 2026 13:23:47 +0000 Subject: [PATCH 22/36] update typehints in theme module --- topostats/theme.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/topostats/theme.py b/topostats/theme.py index 2dc782b1119..3d4af7489b5 100644 --- a/topostats/theme.py +++ b/topostats/theme.py @@ -5,6 +5,8 @@ import matplotlib as mpl import matplotlib.cm import numpy as np + +from typing import Literal, Sequence from matplotlib.colors import LinearSegmentedColormap, ListedColormap from topostats.logs.logs import LOGGER_NAME @@ -68,13 +70,13 @@ def set_cmap(self, name: str) -> None: self.cmap = mpl.colormaps[name] LOGGER.debug(f"[theme] Colormap set to : {name}") - def get_cmap(self) -> matplotlib.cm: + def get_cmap(self) -> mpl.colors.Colormap | None: """ Return the matplotlib.cm colormap object. Returns ------- - matplotlib.cm + matplotlib.colors.Colormap | None Matplotlib Color map object. """ return self.cmap @@ -91,7 +93,7 @@ def nanoscope() -> LinearSegmentedColormap: LinearSegmentedColormap MatplotLib LinearSegmentedColourmap that replicates Bruker Nanoscope colorscale. """ - cdict = { + cdict: dict[Literal["red", "green", "blue", "alpha"], Sequence[tuple[int | float, ...]]] = { "red": ( (0.0, 0.0, 0.0), (0.124464, 0.0, 0.0), From 480363e273985ce1c2167852ca8bab70100fe657 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 18 Mar 2026 15:27:55 +0000 Subject: [PATCH 23/36] update return type hint for remove_scars Docstring for second value in return tuple needs to be updated --- topostats/scars.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/topostats/scars.py b/topostats/scars.py index cd1f21677ab..e9ab40c6107 100644 --- a/topostats/scars.py +++ b/topostats/scars.py @@ -348,7 +348,7 @@ def remove_scars( threshold_high: float = 0.666, max_scar_width: int = 4, min_scar_length: int = 16, -): +) -> tuple[npt.NDArray, npt.NDArray | None]: """ Remove scars from an image. @@ -385,9 +385,11 @@ class was instantiated. Returns ------- - self.img + img: npt.NDArray The original 2-D image with scars removed, unless the config has run set to False, in which case it will not remove the scars. + first_marked_mask: npt.NDArray | None + Needs updating """ LOGGER.info(f"[{filename}] : Removing scars") From 2bec3a7ac6bc6e06a9e73f0b13d351f779b69288 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 18 Mar 2026 15:29:29 +0000 Subject: [PATCH 24/36] add inline typehints for image dict values These specify that the values in the dict are not None after processing --- topostats/filters.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/topostats/filters.py b/topostats/filters.py index a03af099d5a..7dacfb7ba13 100644 --- a/topostats/filters.py +++ b/topostats/filters.py @@ -515,17 +515,18 @@ def filter_image(self) -> None: # numpydoc: ignore=GL07 ... threshold_method='otsu') filter.filter_image() """ - self.images["initial_median_flatten"] = self.median_flatten( + self.images["initial_median_flatten"]: npt.NDArray = self.median_flatten( self.images["pixels"], mask=None, row_alignment_quantile=self.row_alignment_quantile ) - self.images["initial_tilt_removal"] = self.remove_tilt(self.images["initial_median_flatten"], mask=None) - self.images["initial_quadratic_removal"] = self.remove_quadratic(self.images["initial_tilt_removal"], mask=None) - self.images["initial_nonlinear_polynomial_removal"] = self.remove_nonlinear_polynomial( + self.images["initial_tilt_removal"]: npt.NDArray = self.remove_tilt(self.images["initial_median_flatten"], mask=None) + self.images["initial_quadratic_removal"]: npt.NDArray = self.remove_quadratic(self.images["initial_tilt_removal"], mask=None) + self.images["initial_nonlinear_polynomial_removal"]: npt.NDArray = self.remove_nonlinear_polynomial( self.images["initial_quadratic_removal"], mask=None ) # Remove scars run_scar_removal = self.remove_scars_config.pop("run") + self.images["initial_scar_removal"]: npt.NDArray if run_scar_removal: LOGGER.debug(f"[{self.filename}] : Initial scar removal") self.images["initial_scar_removal"], _ = scars.remove_scars( @@ -538,7 +539,7 @@ def filter_image(self) -> None: # numpydoc: ignore=GL07 self.images["initial_scar_removal"] = self.images["initial_nonlinear_polynomial_removal"] # Zero the data before thresholding, helps with absolute thresholding - self.images["initial_zero_average_background"] = self.average_background( + self.images["initial_zero_average_background"]: npt.NDArray = self.average_background( self.images["initial_scar_removal"], mask=None ) @@ -558,19 +559,20 @@ def filter_image(self) -> None: # numpydoc: ignore=GL07 thresholds=self.thresholds, img_name=self.filename, ) - self.images["masked_median_flatten"] = self.median_flatten( + self.images["masked_median_flatten"]: npt.NDArray = self.median_flatten( self.images["initial_tilt_removal"], self.images["mask"], row_alignment_quantile=self.row_alignment_quantile, ) - self.images["masked_tilt_removal"] = self.remove_tilt(self.images["masked_median_flatten"], self.images["mask"]) - self.images["masked_quadratic_removal"] = self.remove_quadratic( + self.images["masked_tilt_removal"]: npt.NDArray = self.remove_tilt(self.images["masked_median_flatten"], self.images["mask"]) + self.images["masked_quadratic_removal"]: npt.NDArray = self.remove_quadratic( self.images["masked_tilt_removal"], self.images["mask"] ) - self.images["masked_nonlinear_polynomial_removal"] = self.remove_nonlinear_polynomial( + self.images["masked_nonlinear_polynomial_removal"]: npt.NDArray = self.remove_nonlinear_polynomial( self.images["masked_quadratic_removal"], self.images["mask"] ) # Remove scars + self.images["secondary_scar_removal"]: npt.NDArray if run_scar_removal: LOGGER.debug(f"[{self.filename}] : Secondary scar removal") self.images["secondary_scar_removal"], scar_mask = scars.remove_scars( @@ -582,7 +584,7 @@ def filter_image(self) -> None: # numpydoc: ignore=GL07 else: LOGGER.debug(f"[{self.filename}] : Skipping scar removal as requested from config") self.images["secondary_scar_removal"] = self.images["masked_nonlinear_polynomial_removal"] - self.images["final_zero_average_background"] = self.average_background( + self.images["final_zero_average_background"]: npt.NDArray = self.average_background( self.images["secondary_scar_removal"], self.images["mask"] ) self.images["gaussian_filtered"] = self.gaussian_filter(self.images["final_zero_average_background"]) From 22951a32a1c7483612ee307d759abc53b1e3fe83 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 18 Mar 2026 15:59:29 +0000 Subject: [PATCH 25/36] update typing for bound_padded_coordinates_to_image and check functions However, I don' see these being used anywhere, so it may be worth just deleting? --- topostats/utils.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/topostats/utils.py b/topostats/utils.py index 108e15e06ea..2fb6755c9ed 100644 --- a/topostats/utils.py +++ b/topostats/utils.py @@ -246,7 +246,7 @@ def create_empty_dataframe(column_set: str = "grainstats") -> pd.DataFrame: return pd.DataFrame(columns=COLUMN_SETS[column_set]) -def bound_padded_coordinates_to_image(coordinates: npt.NDArray, padding: int, image_shape: tuple) -> tuple: +def bound_padded_coordinates_to_image(coordinates: npt.NDArray, padding: int, image_shape: tuple[int, int]) -> tuple[int, int]: """ Ensure the padding of coordinates points does not fall outside of the image shape. @@ -262,15 +262,15 @@ def bound_padded_coordinates_to_image(coordinates: npt.NDArray, padding: int, im Parameters ---------- coordinates : npt.NDArray - Coordinates of a point on the mask based skeleton. + Coordinates of a point on the mask based skeleton (length = 2). padding : int Number of pixels/cells to pad around the point. - image_shape : tuple + image_shape : tuple[int, int] The shape of the original image from which the pixel is obtained. Returns ------- - tuple + tuple[int, int] Returns a tuple of coordinates that ensure that when the point is padded by the noted padding width in subsequent calculations it will not be outside of the image shape. """ @@ -279,14 +279,14 @@ def bound_padded_coordinates_to_image(coordinates: npt.NDArray, padding: int, im max_col = image_shape[1] - 1 row_coord, col_coord = coordinates - def check(coord: npt.NDArray, max_val: int, padding: int) -> npt.NDArray: + def check(coord: int, max_val: int, padding: int) -> int: """ Check coordinates are within the bounds of the padding. Parameters ---------- - coord : npt.NDArray - Coordinates (length = 2). + coord : int + Coordinate (row_coord or col coord). max_val : int Maximum width in the dimension being checked (max_row or max_col). padding : int @@ -294,8 +294,8 @@ def check(coord: npt.NDArray, max_val: int, padding: int) -> npt.NDArray: Returns ------- - npt.NDArray - Coordinates adjusted for padding. + int + Coordinate adjusted for padding. """ if coord - padding < 0: coord = padding From ab6ebcb1c817fbcd520654b8224c5be545b566d1 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 18 Mar 2026 16:01:13 +0000 Subject: [PATCH 26/36] update type hints for threshold functions --- topostats/thresholds.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/topostats/thresholds.py b/topostats/thresholds.py index 6c5b06bb5ae..ffb969f3e45 100644 --- a/topostats/thresholds.py +++ b/topostats/thresholds.py @@ -15,7 +15,7 @@ # pylint: disable=unused-argument -def threshold(image: npt.NDArray, method: str = None, otsu_threshold_multiplier: float = None, **kwargs: dict) -> float: +def threshold(image: npt.NDArray, method: str, otsu_threshold_multiplier: float | None = None, **kwargs: dict) -> float: """ Thresholding for producing masks. @@ -25,7 +25,7 @@ def threshold(image: npt.NDArray, method: str = None, otsu_threshold_multiplier: 2-D Numpy array of image for thresholding. method : str Method to use for thresholding, currently supported methods are otsu (default), mean and minimum. - otsu_threshold_multiplier : float + otsu_threshold_multiplier : float, optional Factor for scaling the Otsu threshold. **kwargs : dict Additional keyword arguments to pass to skimage methods. @@ -71,7 +71,7 @@ def _get_threshold(method: str = "otsu") -> Callable: raise ValueError(method) -def _threshold_otsu(image: npt.NDArray, otsu_threshold_multiplier: float = None, **kwargs) -> float: +def _threshold_otsu(image: npt.NDArray, otsu_threshold_multiplier: float, **kwargs) -> float: """ Calculate the Otsu threshold. @@ -95,7 +95,7 @@ def _threshold_otsu(image: npt.NDArray, otsu_threshold_multiplier: float = None, return threshold_otsu(image, **kwargs) * otsu_threshold_multiplier -def _threshold_mean(image: npt.NDArray, otsu_threshold_multiplier: float = None, **kwargs) -> float: +def _threshold_mean(image: npt.NDArray, otsu_threshold_multiplier: float| None = None, **kwargs) -> float: """ Calculate the Mean threshold. @@ -106,7 +106,7 @@ def _threshold_mean(image: npt.NDArray, otsu_threshold_multiplier: float = None, ---------- image : npt.NDArray 2-D Numpy array of image for thresholding. - otsu_threshold_multiplier : float + otsu_threshold_multiplier : float, optional Factor for scaling (not used). **kwargs : dict Dictionary of keyword arguments to pass to 'skimage.filters.threshold_mean(**kwargs)'. @@ -119,7 +119,7 @@ def _threshold_mean(image: npt.NDArray, otsu_threshold_multiplier: float = None, return threshold_mean(image, **kwargs) -def _threshold_minimum(image: npt.NDArray, otsu_threshold_multiplier: float = None, **kwargs) -> float: +def _threshold_minimum(image: npt.NDArray, otsu_threshold_multiplier: float | None = None, **kwargs) -> float: """ Calculate the Minimum threshold. @@ -130,7 +130,7 @@ def _threshold_minimum(image: npt.NDArray, otsu_threshold_multiplier: float = No ---------- image : npt.NDArray 2-D Numpy array of image for thresholding. - otsu_threshold_multiplier : float + otsu_threshold_multiplier : float, optional Factor for scaling (not used). **kwargs : dict Dictionary of keyword arguments to pass to 'skimage.filters.threshold_minimum(**kwargs)'. @@ -143,7 +143,7 @@ def _threshold_minimum(image: npt.NDArray, otsu_threshold_multiplier: float = No return threshold_minimum(image, **kwargs) -def _threshold_yen(image: npt.NDArray, otsu_threshold_multiplier: float = None, **kwargs) -> float: +def _threshold_yen(image: npt.NDArray, otsu_threshold_multiplier: float | None = None, **kwargs) -> float: """ Calculate the Yen threshold. @@ -154,7 +154,7 @@ def _threshold_yen(image: npt.NDArray, otsu_threshold_multiplier: float = None, ---------- image : npt.NDArray 2-D Numpy array of image for thresholding. - otsu_threshold_multiplier : float + otsu_threshold_multiplier : float, optional Factor for scaling (not used). **kwargs : dict Dictionary of keyword arguments to pass to 'skimage.filters.threshold_yen(**kwargs)'. @@ -167,7 +167,7 @@ def _threshold_yen(image: npt.NDArray, otsu_threshold_multiplier: float = None, return threshold_yen(image, **kwargs) -def _threshold_triangle(image: npt.NDArray, otsu_threshold_multiplier: float = None, **kwargs) -> float: +def _threshold_triangle(image: npt.NDArray, otsu_threshold_multiplier: float | None = None, **kwargs) -> float: """ Calculate the triangle threshold. @@ -178,7 +178,7 @@ def _threshold_triangle(image: npt.NDArray, otsu_threshold_multiplier: float = N ---------- image : npt.NDArray 2-D Numpy array of image for thresholding. - otsu_threshold_multiplier : float + otsu_threshold_multiplier : float, optional Factor for scaling (not used). **kwargs : dict Dictionary of keyword arguments to pass to 'skimage.filters.threshold_triangle(**kwargs)'. From 15b5d60ca0b75c53c5ffd7498dfb8ca5f765fe7f Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 18 Mar 2026 16:25:34 +0000 Subject: [PATCH 27/36] convert return val of triangle height to float To match return value type hint --- topostats/measure/feret.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topostats/measure/feret.py b/topostats/measure/feret.py index 218ec6903b4..2c00ee317f4 100644 --- a/topostats/measure/feret.py +++ b/topostats/measure/feret.py @@ -233,7 +233,7 @@ def triangle_height(base1: npt.NDArray | list, base2: npt.NDArray | list, apex: base1_base2 = np.asarray(base1) - np.asarray(base2) base1_apex = np.asarray(base1) - np.asarray(apex) # Ensure arrays are 3-D see note in https://numpy.org/doc/2.0/reference/generated/numpy.cross.html - return np.linalg.norm(cross2d(base1_base2, base1_apex)) / np.linalg.norm(base1_base2) + return np.linalg.norm(cross2d(base1_base2, base1_apex)) / np.linalg.norm(base1_base2).astype(float) def cross2d(x: npt.NDArray, y: npt.NDArray) -> npt.NDArray: From 0fbbef81b5ec42f78b4cd8f16f7ad241971e0d81 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 18 Mar 2026 16:28:16 +0000 Subject: [PATCH 28/36] update return type hints for min_max_feret and get_feret_from_mask Currently very broad value type, I couldn't parse what they could be from the code --- topostats/measure/feret.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/topostats/measure/feret.py b/topostats/measure/feret.py index 2c00ee317f4..ef8041eb8cb 100644 --- a/topostats/measure/feret.py +++ b/topostats/measure/feret.py @@ -14,6 +14,7 @@ from collections.abc import Generator from math import sqrt from pathlib import Path +from typing import Any import matplotlib.pyplot as plt import numpy as np @@ -351,7 +352,7 @@ def sort_clockwise(coordinates: npt.NDArray) -> npt.NDArray: def min_max_feret( points: npt.NDArray, axis: int = 0, precision: int = 13 -) -> dict[float, tuple[int, int], float, tuple[int, int]]: +) -> dict[str, Any]: """ Given a list of 2-D points, returns the minimum and maximum feret diameters. @@ -368,8 +369,8 @@ def min_max_feret( Returns ------- - dictionary - Tuple of the minimum feret distance and its coordinates and the maximum feret distance and its coordinates. + dict + Needs updating: Tuple of the minimum feret distance and its coordinates and the maximum feret distance and its coordinates. """ caliper_min_feret = list(rotating_calipers(points, axis)) min_ferets, calipers, min_feret_coords = zip(*caliper_min_feret) @@ -394,7 +395,7 @@ def min_max_feret( } -def get_feret_from_mask(mask_im: npt.NDArray, axis: int = 0) -> tuple[float, tuple[int, int], float, tuple[int, int]]: +def get_feret_from_mask(mask_im: npt.NDArray, axis: int = 0) -> dict[str, Any]: """ Calculate the minimum and maximum feret diameter of the foreground object of a binary mask. @@ -409,8 +410,8 @@ def get_feret_from_mask(mask_im: npt.NDArray, axis: int = 0) -> tuple[float, tup Returns ------- - Tuple[float, Tuple[int, int], float, Tuple[int, int]] - Returns a tuple of the minimum feret and its coordinates and the maximum feret and its coordinates. + dict + Needs updating: Returns a tuple of the minimum feret and its coordinates and the maximum feret and its coordinates. """ eroded = skimage.morphology.erosion(mask_im) outline = mask_im ^ eroded From 09a4faa877b7cb0a65932110047a082ba4250b96 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 14:01:32 +0000 Subject: [PATCH 29/36] [pre-commit.ci] Fixing issues with pre-commit --- topostats/filters.py | 12 +++++++++--- topostats/measure/feret.py | 4 +--- topostats/theme.py | 4 ++-- topostats/thresholds.py | 2 +- topostats/utils.py | 4 +++- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/topostats/filters.py b/topostats/filters.py index 7dacfb7ba13..b10dfba8b10 100644 --- a/topostats/filters.py +++ b/topostats/filters.py @@ -518,8 +518,12 @@ def filter_image(self) -> None: # numpydoc: ignore=GL07 self.images["initial_median_flatten"]: npt.NDArray = self.median_flatten( self.images["pixels"], mask=None, row_alignment_quantile=self.row_alignment_quantile ) - self.images["initial_tilt_removal"]: npt.NDArray = self.remove_tilt(self.images["initial_median_flatten"], mask=None) - self.images["initial_quadratic_removal"]: npt.NDArray = self.remove_quadratic(self.images["initial_tilt_removal"], mask=None) + self.images["initial_tilt_removal"]: npt.NDArray = self.remove_tilt( + self.images["initial_median_flatten"], mask=None + ) + self.images["initial_quadratic_removal"]: npt.NDArray = self.remove_quadratic( + self.images["initial_tilt_removal"], mask=None + ) self.images["initial_nonlinear_polynomial_removal"]: npt.NDArray = self.remove_nonlinear_polynomial( self.images["initial_quadratic_removal"], mask=None ) @@ -564,7 +568,9 @@ def filter_image(self) -> None: # numpydoc: ignore=GL07 self.images["mask"], row_alignment_quantile=self.row_alignment_quantile, ) - self.images["masked_tilt_removal"]: npt.NDArray = self.remove_tilt(self.images["masked_median_flatten"], self.images["mask"]) + self.images["masked_tilt_removal"]: npt.NDArray = self.remove_tilt( + self.images["masked_median_flatten"], self.images["mask"] + ) self.images["masked_quadratic_removal"]: npt.NDArray = self.remove_quadratic( self.images["masked_tilt_removal"], self.images["mask"] ) diff --git a/topostats/measure/feret.py b/topostats/measure/feret.py index ef8041eb8cb..411dc5b9a5c 100644 --- a/topostats/measure/feret.py +++ b/topostats/measure/feret.py @@ -350,9 +350,7 @@ def sort_clockwise(coordinates: npt.NDArray) -> npt.NDArray: return coordinates[order] -def min_max_feret( - points: npt.NDArray, axis: int = 0, precision: int = 13 -) -> dict[str, Any]: +def min_max_feret(points: npt.NDArray, axis: int = 0, precision: int = 13) -> dict[str, Any]: """ Given a list of 2-D points, returns the minimum and maximum feret diameters. diff --git a/topostats/theme.py b/topostats/theme.py index 3d4af7489b5..7f8a9fccd23 100644 --- a/topostats/theme.py +++ b/topostats/theme.py @@ -1,12 +1,12 @@ """Custom Bruker Nanoscope colorscale.""" import logging +from collections.abc import Sequence +from typing import Literal import matplotlib as mpl import matplotlib.cm import numpy as np - -from typing import Literal, Sequence from matplotlib.colors import LinearSegmentedColormap, ListedColormap from topostats.logs.logs import LOGGER_NAME diff --git a/topostats/thresholds.py b/topostats/thresholds.py index ffb969f3e45..171e940d731 100644 --- a/topostats/thresholds.py +++ b/topostats/thresholds.py @@ -95,7 +95,7 @@ def _threshold_otsu(image: npt.NDArray, otsu_threshold_multiplier: float, **kwar return threshold_otsu(image, **kwargs) * otsu_threshold_multiplier -def _threshold_mean(image: npt.NDArray, otsu_threshold_multiplier: float| None = None, **kwargs) -> float: +def _threshold_mean(image: npt.NDArray, otsu_threshold_multiplier: float | None = None, **kwargs) -> float: """ Calculate the Mean threshold. diff --git a/topostats/utils.py b/topostats/utils.py index 2fb6755c9ed..878f759c585 100644 --- a/topostats/utils.py +++ b/topostats/utils.py @@ -246,7 +246,9 @@ def create_empty_dataframe(column_set: str = "grainstats") -> pd.DataFrame: return pd.DataFrame(columns=COLUMN_SETS[column_set]) -def bound_padded_coordinates_to_image(coordinates: npt.NDArray, padding: int, image_shape: tuple[int, int]) -> tuple[int, int]: +def bound_padded_coordinates_to_image( + coordinates: npt.NDArray, padding: int, image_shape: tuple[int, int] +) -> tuple[int, int]: """ Ensure the padding of coordinates points does not fall outside of the image shape. From 66e74f152b275a0558c3ad470c11f93726b1b3b1 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 27 May 2026 10:59:43 +0100 Subject: [PATCH 30/36] use NDArray not ArrayLike --- topostats/tracing/ordered_tracing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topostats/tracing/ordered_tracing.py b/topostats/tracing/ordered_tracing.py index ddf6a2cac57..787ccecc116 100644 --- a/topostats/tracing/ordered_tracing.py +++ b/topostats/tracing/ordered_tracing.py @@ -214,7 +214,7 @@ def compile_images( @staticmethod def remove_common_values( ordered_array: npt.NDArray, common_value_check_array: npt.NDArray, retain: list = () - ) -> npt.ArrayLike: + ) -> npt.NDArray: """ Remove common values in common_value_check_array from ordered_array while retaining specified coordinates. @@ -229,7 +229,7 @@ def remove_common_values( Returns ------- - npt.ArrayLike + npt.NDArray Unique ordered_array values and retained coordinates. Retains the order of ordered_array. """ # Convert the arrays to sets for faster common value lookup From f627bd94c61e34c1e9aee9e8e932fe6bd73db12b Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 27 May 2026 11:04:42 +0100 Subject: [PATCH 31/36] update doctrings for topostats_objects input arguments --- topostats/processing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/topostats/processing.py b/topostats/processing.py index 649dea755e9..bbec0082c1b 100644 --- a/topostats/processing.py +++ b/topostats/processing.py @@ -1025,8 +1025,8 @@ def process_scan( Parameters ---------- topostats_object : TopoStats - A dictionary with keys 'image', 'img_path' and 'pixel_to_nm_scaling' containing a file or frames' image, it's - path and it's pixel to namometre scaling value. + A Topostats object with attributes 'image', 'img_path' and 'pixel_to_nm_scaling' containing a file or frames' image, its + path and its pixel to namometre scaling value. base_dir : str | Path Directory to recursively search for files, if not specified the current directory is scanned. filter_config : dict @@ -1281,7 +1281,7 @@ def process_filters( Parameters ---------- topostats_object : Topostats - TopoStats object - Needs further info + An object of type ``TopoStats`` class with a minimum of ``image_original``, ``filename`` and ``pixel_to_nm_scaling`` attributes which allow filtering to be run. base_dir : str | Path Directory to recursively search for files, if not specified the current directory is scanned. filter_config : dict @@ -1348,7 +1348,7 @@ def process_grains( Parameters ---------- topostats_object : TopoStats - TopoStats object - Needs further info + An object of type ``TopoStats`` class. base_dir : str | Path Directory to recursively search for files, if not specified the current directory is scanned. From 88895efa00a2d50290817370ff2b6b16143576cb Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Tue, 9 Jun 2026 09:27:08 +0100 Subject: [PATCH 32/36] update remove_common_values input args --- topostats/tracing/ordered_tracing.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/topostats/tracing/ordered_tracing.py b/topostats/tracing/ordered_tracing.py index 787ccecc116..918ba80e7b5 100644 --- a/topostats/tracing/ordered_tracing.py +++ b/topostats/tracing/ordered_tracing.py @@ -213,7 +213,7 @@ def compile_images( @staticmethod def remove_common_values( - ordered_array: npt.NDArray, common_value_check_array: npt.NDArray, retain: list = () + ordered_array: npt.NDArray, common_value_check_array: npt.NDArray, retain: list | None = None ) -> npt.NDArray: """ Remove common values in common_value_check_array from ordered_array while retaining specified coordinates. @@ -225,13 +225,17 @@ def remove_common_values( common_value_check_array : npt.NDArray Coordinate array containing any common values to be removed from ordered_array. retain : list, optional - List of possible coordinates to keep, by default (). + List of possible coordinates to keep. Returns ------- npt.NDArray Unique ordered_array values and retained coordinates. Retains the order of ordered_array. """ + + if retain is None: + retain = [] + # Convert the arrays to sets for faster common value lookup set_arr2 = {tuple(row) for row in common_value_check_array} set_retain = {tuple(row) for row in retain} From 2134a1f69e8fe10fcce88db4c9d826d831e36378 Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Tue, 9 Jun 2026 09:47:14 +0100 Subject: [PATCH 33/36] updates to remove_scars --- topostats/scars.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/topostats/scars.py b/topostats/scars.py index e9ab40c6107..cab8bf7aa56 100644 --- a/topostats/scars.py +++ b/topostats/scars.py @@ -348,7 +348,7 @@ def remove_scars( threshold_high: float = 0.666, max_scar_width: int = 4, min_scar_length: int = 16, -) -> tuple[npt.NDArray, npt.NDArray | None]: +) -> tuple[npt.NDArray, npt.NDArray]: """ Remove scars from an image. @@ -388,9 +388,11 @@ class was instantiated. img: npt.NDArray The original 2-D image with scars removed, unless the config has run set to False, in which case it will not remove the scars. - first_marked_mask: npt.NDArray | None - Needs updating + first_marked_mask: npt.NDArray + The scars detected during the first iteration. """ + assert removal_iterations >= 2 + LOGGER.info(f"[{filename}] : Removing scars") first_marked_mask = None @@ -420,5 +422,6 @@ class was instantiated. _remove_marked_scars(img, np.copy(marked_both)) LOGGER.debug("Scars removed") + assert first_marked_mask is not None return img, first_marked_mask From a9bedc215bb33d7282e65cf349b4dd89058051ac Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Tue, 9 Jun 2026 09:48:41 +0100 Subject: [PATCH 34/36] update return docstrings for feret functions --- topostats/measure/feret.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/topostats/measure/feret.py b/topostats/measure/feret.py index 411dc5b9a5c..30d13e513b9 100644 --- a/topostats/measure/feret.py +++ b/topostats/measure/feret.py @@ -368,7 +368,7 @@ def min_max_feret(points: npt.NDArray, axis: int = 0, precision: int = 13) -> di Returns ------- dict - Needs updating: Tuple of the minimum feret distance and its coordinates and the maximum feret distance and its coordinates. + Dictionary containing the minimum and maximum feret diameters of the grain and their corresponding coordinates of contact with the calipers. """ caliper_min_feret = list(rotating_calipers(points, axis)) min_ferets, calipers, min_feret_coords = zip(*caliper_min_feret) @@ -409,7 +409,7 @@ def get_feret_from_mask(mask_im: npt.NDArray, axis: int = 0) -> dict[str, Any]: Returns ------- dict - Needs updating: Returns a tuple of the minimum feret and its coordinates and the maximum feret and its coordinates. + Dictionary containing the minimum and maximum feret diameters of the grain and their corresponding coordinates of contact with the calipers. """ eroded = skimage.morphology.erosion(mask_im) outline = mask_im ^ eroded From 9db4b99f4082464500fd2db19d4a5e49b32c2d2d Mon Sep 17 00:00:00 2001 From: Dan Brady Date: Wed, 17 Jun 2026 15:28:08 +0100 Subject: [PATCH 35/36] address sylvia's feedback --- topostats/scars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topostats/scars.py b/topostats/scars.py index cab8bf7aa56..4038e381eb2 100644 --- a/topostats/scars.py +++ b/topostats/scars.py @@ -391,7 +391,7 @@ class was instantiated. first_marked_mask: npt.NDArray The scars detected during the first iteration. """ - assert removal_iterations >= 2 + assert removal_iterations > 0 LOGGER.info(f"[{filename}] : Removing scars") From 86492cf7b23794e99173332ce9739acfdfc3189b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 14:29:38 +0000 Subject: [PATCH 36/36] [pre-commit.ci] Fixing issues with pre-commit --- topostats/tracing/ordered_tracing.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/topostats/tracing/ordered_tracing.py b/topostats/tracing/ordered_tracing.py index 918ba80e7b5..da4457a83d4 100644 --- a/topostats/tracing/ordered_tracing.py +++ b/topostats/tracing/ordered_tracing.py @@ -232,10 +232,9 @@ def remove_common_values( npt.NDArray Unique ordered_array values and retained coordinates. Retains the order of ordered_array. """ - if retain is None: retain = [] - + # Convert the arrays to sets for faster common value lookup set_arr2 = {tuple(row) for row in common_value_check_array} set_retain = {tuple(row) for row in retain}