Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/pypi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
name: Build Package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Python
Expand Down Expand Up @@ -46,7 +46,7 @@ jobs:
url: https://pypi.org/p/topostats
steps:
- name: Download all the dists
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: python-package-distributions
path: dist/
Expand All @@ -72,7 +72,7 @@ jobs:

# steps:
# - name: Download all the dists
# uses: actions/download-artifact@v4
# uses: actions/download-artifact@v5
# with:
# name: python-package-distributions
# path: dist/
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sphinx_docs_to_gh_pages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
name: Sphinx docs to gh-pages
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Python
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
python-version: ["3.10", "3.11"]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v5
with:
Expand Down
10 changes: 10 additions & 0 deletions topostats/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
__version__ = version("topostats")
__release__ = ".".join(__version__.split(".")[:-2])

TOPOSTATS_DETAILS = version("topostats").split("+g")
TOPOSTATS_VERSION = TOPOSTATS_DETAILS[0]
TOPOSTATS_COMMIT = TOPOSTATS_DETAILS[1].split(".d")[0]

colormaps.register(cmap=Colormap("nanoscope").get_cmap())
colormaps.register(cmap=Colormap("gwyddion").get_cmap())

Expand Down Expand Up @@ -269,3 +273,9 @@ def topostats_to_dict(self) -> dict[str, str | ImageGrainCrops | npt.NDArray]:
Dictionary of ''TopoStats'' object.
"""
return {re.sub(r"^_", "", key): value for key, value in self.__dict__.items()}


def log_topostats_version() -> None:
"""Log the TopoStats version, commit and date to system logger."""
LOGGER.info(f"TopoStats version : {TOPOSTATS_VERSION}")
LOGGER.info(f"Commit : {TOPOSTATS_COMMIT}")
5 changes: 4 additions & 1 deletion topostats/entry_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import sys
from pathlib import Path

from topostats import __version__, run_modules
from topostats import __version__, log_topostats_version, run_modules
from topostats.io import write_config_with_comments
from topostats.plotting import run_toposum

Expand Down Expand Up @@ -1268,6 +1268,9 @@ def entry_point(manually_provided_args=None, testing=False) -> None:
None
Does not return anything.
"""
# Log topostats version and the commit id
log_topostats_version()

# Parse command line options, load config (or default) and update with command line options
parser = create_parser()
args = parser.parse_args() if manually_provided_args is None else parser.parse_args(manually_provided_args)
Expand Down
3 changes: 2 additions & 1 deletion topostats/grainstats.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# pylint: disable=line-too-long
# pylint: disable=fixme
# FIXME : The calculate_stats() and calculate_aspect_ratio() raise this error when linting, could consider putting
# variables into dictionar, see example of breaking code out to staticmethod extremes() and returning a
# variables into dictionary, see example of breaking code out to staticmethod extremes() and returning a
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

# dictionary of x_min/x_max/y_min/y_max
# pylint: disable=too-many-locals
# FIXME : calculate_aspect_ratio raises this error when linting it has 65 statements, recommended not to exceed 50.
Expand Down Expand Up @@ -238,6 +238,7 @@ def calculate_stats(self) -> tuple[pd.DataFrame, dict]:
f"[{self.image_name}] : Skipping subgrain due to being too small "
f"(size: {subgrain_tight_shape}) to calculate stats for."
)
continue

# Calculate all the stats
points = self.calculate_points(subgrain_only_mask)
Expand Down
7 changes: 6 additions & 1 deletion topostats/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from numpyencoder import NumpyEncoder
from ruamel.yaml import YAML, YAMLError

from topostats import __release__, grains
from topostats import TOPOSTATS_COMMIT, TOPOSTATS_VERSION, __release__, grains
from topostats.logs.logs import LOGGER_NAME

LOGGER = logging.getLogger(LOGGER_NAME)
Expand Down Expand Up @@ -237,6 +237,11 @@ def write_yaml(
header = f"# {header_message} : {get_date_time()}\n" + CONFIG_DOCUMENTATION_REFERENCE
else:
header = f"# Configuration from TopoStats run completed : {get_date_time()}\n" + CONFIG_DOCUMENTATION_REFERENCE

# Add comment to config with topostats version + commit
header += f"# TopoStats version: {TOPOSTATS_VERSION}\n"
header += f"# Commit: {TOPOSTATS_COMMIT}\n"

output_config.write_text(header, encoding="utf-8")

yaml = YAML(typ="safe")
Expand Down
98 changes: 55 additions & 43 deletions topostats/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pandas as pd
from art import tprint

from topostats import __version__
from topostats import TOPOSTATS_COMMIT, TOPOSTATS_VERSION
from topostats.array_manipulation import re_crop_grain_image_and_mask_to_set_size_nm
from topostats.filters import Filters
from topostats.grains import GrainCrop, GrainCropsDirection, Grains, ImageGrainCrops
Expand Down Expand Up @@ -338,62 +338,73 @@ def run_grainstats(
grainstats_config.pop("run")
class_names = {index + 1: class_name for index, class_name in enumerate(grainstats_config.pop("class_names"))}
# Grain Statistics :
LOGGER.info(f"[{filename}] : *** Grain Statistics ***")
grainstats_dict = {}
height_profiles_dict = {}
try:
LOGGER.info(f"[{filename}] : *** Grain Statistics ***")
grain_plot_dict = {
key: value
for key, value in plotting_config["plot_dict"].items()
if key in ["grain_image", "grain_mask", "grain_mask_image"]
}
grainstats_dict = {}
height_profiles_dict = {}
except Exception as e:
LOGGER.error(
f"[{filename}] : An error occurred whilst creating a grain plots dictionary: {e}\nReturning empty dataframe."
)
return create_empty_dataframe(column_set="grainstats"), height_profiles_dict, {}

# There are two layers to process those above the given threshold and those below
grain_crops_direction: GrainCropsDirection
# There are two layers to process those above the given threshold and those below
grain_crops_direction: GrainCropsDirection
try:
for direction, grain_crops_direction in image_grain_crops.__dict__.items():
if grain_crops_direction is None:
LOGGER.warning(
f"No grains exist for the {direction} direction. Skipping grainstats for {direction}."
)
continue
grainstats_calculator = GrainStats(
grain_crops=grain_crops_direction.crops,
direction=direction,
base_output_dir=grain_out_path,
image_name=filename,
plot_opts=grain_plot_dict,
**grainstats_config,
)
grainstats_dict[direction], height_profiles_dict[direction] = grainstats_calculator.calculate_stats()
grainstats_dict[direction]["threshold"] = direction
# Create results dataframe from above and below results
# Appease pylint and ensure that grainstats_df is always created
grainstats_df = create_empty_dataframe(column_set="grainstats")
if "above" in grainstats_dict and "below" in grainstats_dict:
grainstats_df = pd.concat([grainstats_dict["below"], grainstats_dict["above"]])
elif "above" in grainstats_dict:
grainstats_df = grainstats_dict["above"]
elif "below" in grainstats_dict:
grainstats_df = grainstats_dict["below"]
else:
raise ValueError(
"grainstats dictionary has neither 'above' nor 'below' keys. This should be impossible."
)
grainstats_df["basename"] = basename.parent
grainstats_df["class_name"] = grainstats_df["class_number"].map(class_names)
LOGGER.info(f"[{filename}] : Calculated grainstats for {len(grainstats_df)} grains.")
LOGGER.info(f"[{filename}] : Grainstats stage completed successfully.")
return grainstats_df, height_profiles_dict, grainstats_calculator.grain_crops
except Exception:
LOGGER.info(
f"[{filename}] : Errors occurred whilst calculating grain statistics. Returning empty dataframe."
try:
grainstats_calculator = GrainStats(
grain_crops=grain_crops_direction.crops,
direction=direction,
base_output_dir=grain_out_path,
image_name=filename,
plot_opts=grain_plot_dict,
**grainstats_config,
)
grainstats_dict[direction], height_profiles_dict[direction] = (
grainstats_calculator.calculate_stats()
)
grainstats_dict[direction]["threshold"] = direction
except Exception as e:
LOGGER.error(
f"[{filename}] : An error occurred whilst calculating grain statistics: {e}\nReturning empty dataframe."
)
return create_empty_dataframe(column_set="grainstats"), height_profiles_dict, {}
except Exception as e:
LOGGER.error(
f"[{filename}] : An error occurred whilst trying to iterate over directions: {e}\nReturning empty dataframe."
)
return create_empty_dataframe(column_set="grainstats"), height_profiles_dict, {}
else:
LOGGER.info(
f"[{filename}] : Calculation of grainstats disabled, returning empty dataframe and empty height_profiles."
)
return create_empty_dataframe(column_set="grainstats"), {}, {}
# Create results dataframe from above and below results
# Appease pylint and ensure that grainstats_df is always created
grainstats_df = create_empty_dataframe(column_set="grainstats")
if "above" in grainstats_dict and "below" in grainstats_dict:
grainstats_df = pd.concat([grainstats_dict["below"], grainstats_dict["above"]])
elif "above" in grainstats_dict:
grainstats_df = grainstats_dict["above"]
elif "below" in grainstats_dict:
grainstats_df = grainstats_dict["below"]
else:
raise ValueError("grainstats dictionary has neither 'above' nor 'below' keys. This should be impossible.")
grainstats_df["basename"] = basename.parent
grainstats_df["class_name"] = grainstats_df["class_number"].map(class_names)
LOGGER.info(f"[{filename}] : Calculated grainstats for {len(grainstats_df)} grains.")
LOGGER.info(f"[{filename}] : Grainstats stage completed successfully.")
return grainstats_df, height_profiles_dict, grainstats_calculator.grain_crops
LOGGER.info(
f"[{filename}] : Calculation of grainstats disabled, returning empty dataframe and empty height_profiles."
)
return create_empty_dataframe(column_set="grainstats"), {}, {}


def run_disordered_tracing(
Expand Down Expand Up @@ -1626,7 +1637,8 @@ def completion_message(config: dict, img_files: list, summary_config: dict, imag
tprint("TopoStats", font="twisted")
LOGGER.info(
f"\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ COMPLETE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n"
f" TopoStats Version : {__version__}\n"
f" TopoStats Version : {TOPOSTATS_VERSION}\n"
f" TopoStats Commit : {TOPOSTATS_COMMIT}\n"
f" Base Directory : {config['base_dir']}\n"
f" File Extension : {config['file_ext']}\n"
f" Files Found : {len(img_files)}\n"
Expand Down
Loading