diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ddf5dc9af08..ccbffcbb104 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,13 +32,13 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.10 + rev: v0.14.14 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix, --show-fixes] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.12.0 + rev: 26.1.0 hooks: - id: black types: [python] @@ -59,12 +59,12 @@ repos: - id: codespell - repo: https://github.com/rbubley/mirrors-prettier - rev: v3.7.4 + rev: v3.8.1 hooks: - id: prettier - repo: https://github.com/kynan/nbstripout - rev: 0.8.2 + rev: 0.9.0 hooks: - id: nbstripout diff --git a/notebooks/contributing_to_topostats.py b/notebooks/contributing_to_topostats.py index b51ea4d147e..8d9975fd902 100644 --- a/notebooks/contributing_to_topostats.py +++ b/notebooks/contributing_to_topostats.py @@ -6,12 +6,10 @@ @app.cell def _(mo): - mo.md( - r""" + mo.md(r""" #Making use of TopoStats outputs Running TopoStats from the command line produces a .topostats file for each processed image. This file contains all of the data used at different stages within the TopoStats pipeline including filtered images, grain masks, and traces. These files are a really useful resource for using TopoStats outputs to develop new functions for your own analyses. Here we show one example of using the .topostats file to create a height profile of a DNA minicircle, enabling us to measure the distance between helices. - """ - ) + """) return @@ -45,11 +43,9 @@ def _(): @app.cell def _(mo): - mo.md( - r""" + mo.md(r""" First we read in the .topostats file and convert it to a usable dictionary format. - """ - ) + """) return @@ -67,11 +63,9 @@ def _(Path, h5py, hdf5_to_dict): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" Printing the `data_dict` allows us to see what is contained within our .topostats file, including all of the file's metadata and other data generated through TopoStats. - """ - ) + """) return @@ -83,11 +77,9 @@ def _(data_dict, pprint): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" Below is a helper function used to enable visualisation of plots within marimo. We can use it to plot our TopoStats processed image which is stored in `data_dict["image"]`. Note you could also view the raw image using `data_dict[image_original]`. - """ - ) + """) return @@ -134,13 +126,11 @@ def show_image( @app.cell def _(mo): - mo.md( - r""" + mo.md(r""" In the code below, we use the bounding box information for grain 0, stored in `data_dict["ordered_traces"]["above"]["grain_0"]["mol_0"]["bbox"]` to crop the image and centre the grain of interest. We can then extract the height values, provided in `data_dict["ordered_traces"]["above"]["grain_0"]["mol_0"]["heights"]` as well as distances along the trace, provided in `data_dict["ordered_traces"]["above"]["grain_0"]["mol_0"]["distances"]`. - """ - ) + """) return @@ -164,11 +154,9 @@ def _(data_dict, np, show_image): @app.cell def _(mo): - mo.md( - r""" + mo.md(r""" We can use the height trace profiles to count the number of helices within our grain, and to measure the distance between helices. By using a threshold of 4nm, we see that DNA helices are shown as equidistant peaks within the height profile plot. - """ - ) + """) return diff --git a/notebooks/grainstats.py b/notebooks/grainstats.py index 126686bf81e..3179eb7188f 100644 --- a/notebooks/grainstats.py +++ b/notebooks/grainstats.py @@ -6,8 +6,7 @@ @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" #TopoStats - grain stats walkthrough This marimo notebook will walk you through using TopoStats from processing an AFM image through to quantification and data visualisation. @@ -24,8 +23,7 @@ def _(mo): ``` For more information on installing TopoStats and setting up virtual environments, please refer to our installation instructions [here]([https://link-url-here.org](https://afm-spm.github.io/TopoStats/main/installation.html)). - """ - ) + """) return @@ -42,13 +40,11 @@ def _(): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" ##Loading libraries and modules TopoStats is written as a series of modules with various classes and functions. In order to use these interactively we need to `import` them. - """ - ) + """) return @@ -89,14 +85,12 @@ def _(): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" ##Finding files to process When run from the command line, TopoStats needs to find files to process and the `find_files` function helps with this. It requires the directory path (a folder to search in), and the file extension to look for (this example processes a `.spm` file). The function then returns a list of all files within the directory that have the required file extension. To use the `find_files` function within the code block below, it is required that your image files are placed within the same directory as this notebook! - """ - ) + """) return @@ -113,11 +107,9 @@ def _(Path, find_files): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" The image files that were found using `find_files` are listed below: - """ - ) + """) return @@ -130,8 +122,7 @@ def _(image_files): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" ##Loading a configuration file You can specify all options explicitly by hand when instantiating classes or calling methods/functions. However when run @@ -222,23 +213,20 @@ def _(mo): run: true # Whether to make summary plots for output data config: null ``` - """ - ) + """) return @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" To load the configuration file into Python we use the `read_yaml()` function. This saves the options as a dictionary and we can access values by the keys. The example below prints out the top-levels keys and then the keys for the `filter` configuration. Python dictionaries have keys which can be considered as the parameter that is to be configured and each key has an associated value which is the value you wish to set the parameter to. - """ - ) + """) return @@ -252,8 +240,7 @@ def _(BASE_DIR, read_yaml): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" To create a new config file we use the `write_config_with_comments()` function which takes the `args` namespace. There a 4 types of valid config (selected using `args.config`) that can be generated, which are as follows: 1. `default` - The default config includes all the configuration options including some parameters which are not recommended to be changed unless confident on the effects. 2. `simple` - A simple config includes a subset of the default configuration with only options which are likely to be adjusted by users. @@ -261,20 +248,17 @@ def _(mo): 4. `var_to_label` - generates a YAML file which maps variable names used in CSV output to descriptions which can be used when plotting data. These files are generated from the respective files that are part of the TopoStats package. Additional options that are used by the function is the filename (args.filename) the config will be saved as and the output directory (args.output_dir) it will be saved to. - """ - ) + """) return @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" You can look at all of the options using the `json` package to "pretty" print the dictionary which makes it easier to read. Here we print the `filter` section. You can see the options map to those of the `Filter()` class with an additional `"run": true` which is used when running TopoStats at the command line. - """ - ) + """) return @@ -327,15 +311,13 @@ def show_image( @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" We will use the configuration options we have loaded in processing the `minicircle.spm` image. For convenience we save each set of options to their own dictionary and remove the `run` entry as this is not required when running TopoStats interactively. We also set the `plotting_config["image_set"]` to `all` so that all images can be plotted (there are some internal controls that determine whether images are plotted and returned). - """ - ) + """) return @@ -355,13 +337,11 @@ def _(config): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" ##Load scans The first step before processing is to load an AFM scan. This extracts the image data into a numpy array along with the filepath and pixel to nm scaling parameter which is used throughout to scale the pixels within an image. - """ - ) + """) return @@ -379,8 +359,7 @@ def _(LoadScans, config, image_files, my_filename, show_image): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" ## Filtering an image Now that we have loaded some images, we can start processing using TopoStats. The first step is called filtering, and involves a sequence of processing steps to remove noise and AFM-specific imaging artifacts. There are a number of options that we need to specify to optimise filtering, and these are described in detail in the TopoStats [documentation](https://topostats.readthedocs.io/en/dev/topostats.filters.html). @@ -390,8 +369,7 @@ def _(mo): The following section instantiates ("sets up") an object called `filtered_image` of type `Filters` using the first file found (`image_files[0]`) and the various options from the `filter_config` dictionary. - """ - ) + """) return @@ -419,12 +397,10 @@ def _(Filters, all_scan_data, filter_config, my_filename): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" The `filtered_image` now has a a number of numpy arrays saved in the `.images` dictionary that can be accessed and plotted. To view the names of the images (technically the dictionary keys) you can print them with `filter_image.images.keys()`... - """ - ) + """) return @@ -436,11 +412,9 @@ def _(filtered_image): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" You can view the result of TopoStats filtering using the `show_image` function, the final result is stored in the `gaussian_filtered` key. - """ - ) + """) return @@ -452,8 +426,7 @@ def _(filtered_image, show_image): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" TopoStats includes a custom plotting class `Images` which formats plots in a more familiar manner. It has a number of options, please refer to the official documentation on @@ -464,8 +437,7 @@ def _(mo): The class requires a Numpy array, which we have just generated many of during the various filtering stages, and a number of options. Again for convenience we use the `**plotting_config` notation to unpack the key/value pairs stored in the `plotting_config` dictionary. - """ - ) + """) return @@ -477,12 +449,10 @@ def _(plotting_config): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" Here we plot the image after processing and zero-averaging the background but with the `viridis` palette and constraining the `zrange` to be between 0 and 3. - """ - ) + """) return @@ -508,8 +478,7 @@ def _(Images, filtered_image, plotting_config): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" ##Finding grains The next step in processing the image is to find grains - a.k.a the molecules we want to analyse. This is done using the `Grains` class and we have saved the @@ -525,8 +494,7 @@ def _(mo): yourself explicitly they are available as properties of the `filtered_image` that we have processed. As with `Filters` the `Grains` class has a number of methods that carry out the grain finding, but there is a convenience method `find_grains()` which calls all these in the correct order. - """ - ) + """) return @@ -545,11 +513,9 @@ def _(Grains, filtered_image, grain_config): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" The `grains` object now also contains a series of images that we can plot, these can be found as keys within grains.mask_images["above"]. We print the full contents of grains below. - """ - ) + """) return @@ -561,11 +527,9 @@ def _(grains): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" To view the full grains mask, you can plot the `merged_classes` mask as below. The first plot shows the mask with background is 0 (black), and grains in 1 (white). The second plot shows the image and mask overlaid. - """ - ) + """) return @@ -588,18 +552,15 @@ def _(grains, show_image): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" - """ - ) + """) return @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" The grains config enables users to play around with parameters to optimise masking for their individual use cases, it is printed below to show which parameters can be configured. The thresholds can be used in different ways based on the `direction` you want to detect grains. Typically for molecular @@ -608,8 +569,7 @@ def _(mo): This is controlled using the `config["grains"]["direction"]` configuration option which maps to the `Grains(direction=)` option and can take three values `below`, `above` or `both`. - """ - ) + """) return @@ -621,8 +581,7 @@ def _(config, json): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" So far the thresholding method used has been `threshold_method="std_dev"` defined in the configuration file we loaded. This calculates the mean and standard deviation of height across the whole image and then determines the threshold by scaling the standard deviation by a given factor (defined by `threshold_std_dev`) and adding it to the mean to give the `above` threshold and/or subtracting if from the mean to give the `below` threshold. @@ -631,8 +590,7 @@ def _(mo): Other thresholding methods that can be used within TopoStats are described in the documentation [here]("https://github.com/AFM-SPM/TopoStats/blob/main/docs/advanced/thresholding.md"). In the code block below we set `threshold_method="absolute"` and show the resulting masks when two different absolute thresholds of 1.2nm and 2.0nm are used. - """ - ) + """) return @@ -670,8 +628,7 @@ def _(Grains, filtered_image, grain_config, grains, show_image): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" ##Extracting grain statistics Now that the grains have been found we can calculate statistics for each. This is done using the `GrainStats()` class. Again the configuration options from the YAML file map to those of the class and there is a convenience method @@ -683,8 +640,7 @@ def _(mo): The `GrainStats` class returns two objects, a Pandas `pd.DataFrame` of calculated statistics and a `list` of dictionaries containing the grain data to be plotted. We therefore instantiate ("set-up") the `grain_stats` dictionary to hold these results and unpack each to the keys `statistics` and `plots` respectively. - """ - ) + """) return @@ -712,13 +668,11 @@ def _(GrainStats, Grains, grains, grainstats_config, show_image): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" The `grain_stats["statistics"]` is a [Pandas DataFrame](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html). We can print this out as shown below. - """ - ) + """) return @@ -730,11 +684,9 @@ def _(grain_stats): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" Further we can summarise the dataframe for a subset of variables, here we show summary stats for `smallest_bounding_width` and `aspect_ratio` as an example. - """ - ) + """) return @@ -746,11 +698,9 @@ def _(grain_stats): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" We can use the `seaborn` package to produce plots of certain variables from grain_stats table. `seaborn` has a range of different options for data visualisation, below we show how you can create violin and KDE plots to explore the distribution of certain variables. Try playing around with the `col` definition to view plots of different variables. - """ - ) + """) return @@ -776,11 +726,9 @@ def _(grain_stats, plt, sns): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" It could also be useful to compare whether two variables have any relationship with one another, and for this a scatter plot can be used to visualise correlation. Below we show an example of plotting `aspect_ratio` against `height_mean`, but these can be replaced with any other columns from the `grain_stats` table. - """ - ) + """) return diff --git a/notebooks/tracestats.py b/notebooks/tracestats.py index 45029af4e2e..d0d0a028f4f 100644 --- a/notebooks/tracestats.py +++ b/notebooks/tracestats.py @@ -6,8 +6,7 @@ @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" #Walkthrough of DNA tracing in TopoStats Once grain masks have been obtained through TopoStats (as exemplified in the `grainstats.py` notebook), these can be used to obtain DNA traces - smooth contours that sit along molecular backbones. These traces enable extraction of additional features such as DNA topology, height profiles, contour length, number of crossings etc. enabling greater characterisation of DNA than can be obtained through masks alone. @@ -16,8 +15,7 @@ def _(mo): The full DNA tracing pipeline is described in detail on our [documentation website](https://github.com/AFM-SPM/TopoStats/tree/main/docs/advanced) where the relevant files are [`disordered_tracing.md`](https://github.com/AFM-SPM/TopoStats/blob/main/docs/advanced/disordered_tracing.md), [`ordered_tracing.md`](https://github.com/AFM-SPM/TopoStats/blob/main/docs/advanced/ordered_tracing.md), and [`splining.md`](https://github.com/AFM-SPM/TopoStats/blob/main/docs/advanced/splining.md). You can also see our tracing algorithm in action in [Holmes et al. (2025)](https://www.nature.com/articles/s41467-025-60559-x), where we classify and quantify complex topological DNA structures! - """ - ) + """) return @@ -76,14 +74,12 @@ def _(): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" ##Pre-requisites: filtered image and grain masks The following code block is a summarised version of the code within `grainstats.py` to obtain a filtered image and grain mask ready for the DNA tracing pipeline. Here we use an image of DNA plasmids, rather than minicircles, to showcase our method's ability to trace complex DNA structures. Here we have additional configs to parameterise the DNA tracing pipeline, including: `disordered_tracing`, `nodestats`, `ordered_tracing` and `splining`. Furthermore, the `curvature` config allows for parameterisation to quantify curvature of DNA traces. - """ - ) + """) return @@ -209,11 +205,9 @@ def show_image(arr, cmap="afmhot", size=(8, 8), colorbar=True, title=None): @app.cell def _(mo): - mo.md( - r""" + mo.md(r""" We can view the resulting processed image and grains mask using the code below. - """ - ) + """) return @@ -235,8 +229,7 @@ def _(filtered_image, grains, show_image): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" ##Disordered tracing The `disordered_tracing.py` module handles all the functions associated with obtaining single-pixel wide, line representations of masked objects. @@ -244,18 +237,15 @@ def _(mo): The quality and likeness of the resultant pruned skeleton thus depends on the quality of the mask, the effectiveness of smoothing parameters, the method of skeletonisation, and the quality of automating the pruning of incorrect skeletal branches. ![disordered_tracing](https://raw.githubusercontent.com/AFM-SPM/TopoStats/refs/heads/main/docs/_static/images/disordered_tracing/overview.png) - """ - ) + """) return @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" We can run the function `trace_image_disordered` to run the `disordered_tracing` workflow. The function outputs a set of diagnostic images for each grain (stored in `disordered_traces_cropped_data`) as well as each image (stored in `disordered_tracing_images`). - """ - ) + """) return @@ -279,13 +269,11 @@ def _(disordered_tracing_config, grains, image, trace_image_disordered): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" We can inspect the contents of `disordered_traces_cropped_data` using the code below. "Available grain keys" tells us the number of grains that are described within our dictionary, in this case there is `grain_0` and `grain_1` - the two grains that we saw in our masked image. By selecting `grain_0` as an example, we can view the data that is stored for each grain. Here `skeleton` refers to a binary mask of the original grain skeleton, and `pruned_skeleton` is the binary mask following additional processing, known as pruning, to remove spurious branches. - """ - ) + """) return @@ -312,11 +300,9 @@ def _(disordered_traces_cropped_data, pp): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" We can view the pruned skeletons for both grains using the code block below, notice that these are 1 pixel wide traces that sit along the molecular backbone of the DNA molecules from our AFM image. - """ - ) + """) return @@ -338,8 +324,7 @@ def _(disordered_traces_cropped_data, show_image): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" ##Nodestats The `nodestats.py` module handles all the functions associated with identifying and analysing the crossing regions (nodes) and crossing branches in pruned skeletons. @@ -347,18 +332,15 @@ def _(mo): The quality of the resultant metrics and over/underlying branch classifications depend on the quality of the pruned skeleton, the effectiveness of automating the joining of skeleton junction points through the parameters. ![nodestats](https://github.com/AFM-SPM/TopoStats/raw/main/docs/_static/images/nodestats/overview.png)) - """ - ) + """) return @app.cell def _(mo): - mo.md( - r""" + mo.md(r""" We can run the `nodestats` workflow using the `nodestats_image` function as in the code block below. - """ - ) + """) return @@ -399,15 +381,13 @@ def _( @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" Skeleton segments which represent a crossing may not join up perfectly at a single pixel (junction) and as a result of the skeletonisation procedure may be offset from one another and need to be combined to represent the crossing region or "node". Therefore, junctions closer than the `node_joining_length` (set in the config file) of each other define a crossing region, and the pixels which span between the junctions along the skeleton are also labelled as part of the crossing. This is exemplified below where the first image shows all identified junctions. In the leftmost grain we can see two crossings, but 3 junctions are identified. In the bottom image, 2 of the 3 junctions are combined to correctly show two crossings within the grain. - """ - ) + """) return @@ -422,16 +402,14 @@ def _(image_dict, show_image): @app.cell def _(mo): - mo.md( - r""" + mo.md(r""" ##Ordered tracing This module orders the disordered trace pixel-by-pixel (topostats method) or segment-by-segment (nodestats method), giving direction to the trace and creating a path to follow. It identifies how many intertwined molecules are within the grain (found by restarting the trace when using the nodestats method), and outputs additional statistics such as DNA topology. ![ordered tracing](https://github.com/AFM-SPM/TopoStats/raw/main/docs/_static/images/ordered_tracing/overview.png) We can run the full ordered tracing workflow using the `ordered_tracing_image` function as below. - """ - ) + """) return @@ -466,11 +444,9 @@ def _( @app.cell def _(mo): - mo.md( - r""" + mo.md(r""" We can inspect all of the outputs of `ordered_tracing_image` using the code below, we see a combination of images to view the tracing outputs as well as data frames containing statistics such as topology and writhe that were extracted from ordered traces. - """ - ) + """) return @@ -502,11 +478,9 @@ def _( @app.cell def _(mo): - mo.md( - r""" + mo.md(r""" Below we plot `ordered_trace_full_images['ordered_traces']` which shows the ordered trace coordinates, coloured using a sequential colour map with lighter colours indicating the start of the trace, and darker colours indicating the end. - """ - ) + """) return @@ -518,8 +492,7 @@ def _(ordered_trace_full_images, show_image): @app.cell def _(mo): - mo.md( - r""" + mo.md(r""" ##Splining The `splining.py` module handles all the functions associated with smoothing the ordered pixel-wise trace from the "ordered_tracing" step, in order to produce curves which more closely follow the samples structure. @@ -527,8 +500,7 @@ def _(mo): ![splining](https://github.com/AFM-SPM/TopoStats/raw/main/docs/_static/images/splining/overview.png) We can use the `splining_image` function as in the code block below to run the full splining workflow. We can also inspect the outputs produced, notice that we now have two data frames where one consists of statistics extracted from whole grains and the other contains statistics extracted at molecule level (e.g. statistics to describe each molecule if two molecules are interconnected to form a grain.) - """ - ) + """) return @@ -584,15 +556,13 @@ def _(plt, splined_traces): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" ##Curvature Once we have the smooth traces for each molecule, we can calculate the curvature at each point along the molecular backbone. We can then look at how curvature changes at different points along the molecule, determine whether certain features (such as protein binding or damage) affect DNA curvature, and calculate an average curvature value for the entire molecule. We make use of the `calculate_curvature_stats_image` function to calculate curvature for all grains identified within our image. - """ - ) + """) return @@ -606,11 +576,9 @@ def _(calculate_curvature_stats_image, grains, splined_traces): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" We can print the resulting `curvature_stats` which provides a dictionary of curvature values for each grain, with one value per coordinate along the grain's trace. - """ - ) + """) return @@ -622,11 +590,9 @@ def _(curvature_stats): @app.cell(hide_code=True) def _(mo): - mo.md( - r""" + mo.md(r""" The following code block allows us to visualise curvature values at different points along the grain traces, with light reds indicating regions with lower curvature, and darker reds indicating high curvature regions. - """ - ) + """) return diff --git a/tests/test_grains.py b/tests/test_grains.py index 282e22ecd8e..e9b0b2a5e7a 100644 --- a/tests/test_grains.py +++ b/tests/test_grains.py @@ -3133,7 +3133,7 @@ def test_calculate_region_connection_regions( expected_intersection_points: list[tuple[int, int]], ) -> None: """Test the calculate_region_connection_regions method of the Grains class.""" - (result_num_connection_regions, result_intersection_labels, result_intersection_points) = ( + result_num_connection_regions, result_intersection_labels, result_intersection_points = ( Grains.calculate_region_connection_regions(grain_mask_tensor, classes) ) diff --git a/topostats/filters.py b/topostats/filters.py index 9a4249964b6..ff3da38bfc9 100644 --- a/topostats/filters.py +++ b/topostats/filters.py @@ -194,10 +194,8 @@ def median_flatten( if not np.isnan(m): image[row, :] -= m else: - LOGGER.warning( - """f[{self.filename}] Large grain detected image can not be -processed, please refer to https://github.com/AFM-SPM/TopoStats/discussions for more information.""" - ) + LOGGER.warning("""f[{self.filename}] Large grain detected image can not be +processed, please refer to https://github.com/AFM-SPM/TopoStats/discussions for more information.""") return image diff --git a/topostats/io.py b/topostats/io.py index 579042f0f5c..fc9a325ec96 100644 --- a/topostats/io.py +++ b/topostats/io.py @@ -854,11 +854,9 @@ def get_data(self) -> None: # noqa: C901 # pylint: disable=too-many-branches raise self._check_image_size_and_add_to_dict(image=self.image, filename=self.filename) else: - raise ValueError( - f"File type {suffix} not yet supported. Please make an issue at \ + raise ValueError(f"File type {suffix} not yet supported. Please make an issue at \ https://github.com/AFM-SPM/TopoStats/issues, or email topostats@sheffield.ac.uk to request support for \ - this file type." - ) + this file type.") def _check_image_size_and_add_to_dict(self, image: npt.NDArray, filename: str) -> None: """ @@ -1104,10 +1102,8 @@ def save_topostats_file( dict_to_hdf5(open_hdf5_file=f, group_path="/", dictionary=topostats_object.image_grain_crops_to_dict()) else: - raise ValueError( - "TopoStats object dictionary does not contain an 'image'. \ - TopoStats objects must be saved with a flattened image." - ) + raise ValueError("TopoStats object dictionary does not contain an 'image'. \ + TopoStats objects must be saved with a flattened image.") def save_pkl(outfile: Path, to_pkl: dict) -> None: