Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f7d776e
Align level weighted with matlab.
derollins Jan 5, 2026
627c148
Big update: Optomise the alignment with MATLAB. Manyb of the funciton…
derollins Jan 5, 2026
f279f43
Correct polarity of masks to match MATLAB. Now generates boolean mask…
derollins Jan 8, 2026
08d3ce8
Align level with MATLAB Nanolocz level. Aligned funcitons so outputs …
derollins Jan 9, 2026
aa91555
Docstring and comment corrections to thresholder and level
derollins Jan 12, 2026
239a0c4
Update level_weighted docstrings.
derollins Jan 12, 2026
f314b6c
Aligned level_auto with MATLAB version. Added helpers for precodition…
derollins Jan 12, 2026
0cd4a78
Update level_auto docstrings. Also turned off preconditioning debug.
derollins Jan 12, 2026
d1c772d
Update README and add citatio.cff file.
derollins Jan 12, 2026
9e39f8c
Add scikit-image dependancy to pyproject.toml
derollins Jan 12, 2026
d61500d
Add and update thresholder tests (although these were for old version).
derollins Jan 12, 2026
a978fd7
Merge branch 'main' into align_with_matlab
derollins Jan 12, 2026
4b72b6d
Pre-commit fixes.
derollins Jan 12, 2026
67bffec
Spelling and whitespaace fixes.
derollins Jan 12, 2026
a7285f6
Merge branch 'align_with_matlab' of github.com:derollins/Pyhton-Nanol…
derollins Jan 12, 2026
c32f8b8
Show pre-commit diffs.
derollins Jan 12, 2026
410d83a
More precommit fixes
derollins Jan 12, 2026
558ab82
In line level, ensured pos was a boolean array so that floats were no…
derollins Jan 12, 2026
84b06d1
Remove depreciated test from test_level_weieghted
derollins Jan 12, 2026
9711798
Stop supporting python 3.10 and start testing python 3.13
derollins Jan 12, 2026
84adae8
Update tests
derollins Jan 12, 2026
1af1c08
FIx depreciation errors in remove_small_holes and add a conftest.py f…
derollins Jan 12, 2026
b2402b2
Add docstring to conftest funciton.
derollins Jan 12, 2026
5b4ddb0
Tests: Add tests to test_level_auto to cover new helper funcitons.
derollins Jan 13, 2026
c1f0b03
protect against runtime warning for all NaN masks
derollins Jan 14, 2026
d3227c8
Tidy up notebook and run nbstripout.
derollins Jan 14, 2026
f4ec53e
Add tests with real data. Introduced sample data in tests/resources a…
derollins Jan 14, 2026
16ff961
Add |tests/resources/ to various excludes in teh pre commit config.
derollins Jan 14, 2026
622df14
update pre commit excludes.
derollins Jan 14, 2026
43109f7
Add real data tests to tests for level and level_auto.
derollins Jan 14, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/pre-commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ jobs:
pip install pre-commit

- name: Run pre-commit hooks
run: pre-commit run --all-files
run: pre-commit run --all-files --show-diff-on-failure
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.10", "3.11", "3.12"]
python-version: ["3.11", "3.12", "3.13"]

steps:
- name: Checkout repository
Expand Down
18 changes: 9 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ repos:
rev: v4.5.0
hooks:
- id: trailing-whitespace
exclude: ^(docs/|notebooks/|LICENSE)$
exclude: (^docs/|^tests/resources/|^notebooks/|^LICENSE$)
- id: end-of-file-fixer
exclude: ^(docs/|notebooks/|LICENSE)$
exclude: (^docs/|^tests/resources/|^notebooks/|^LICENSE$)
- id: check-yaml
exclude: ^(docs/|notebooks/|LICENSE)$
exclude: (^docs/|^notebooks/|^LICENSE$)
- id: check-added-large-files
args: ["--maxkb=500"]
exclude: ^(docs/|notebooks/|LICENSE)$
exclude: (^docs/|^tests/resources/|^notebooks/|^LICENSE$)
- id: check-merge-conflict
- id: check-toml

Expand All @@ -22,23 +22,23 @@ repos:
- id: isort
args: ["--profile", "black"]
language_version: python3
exclude: ^(docs/|notebooks/|LICENSE)$
exclude: (^docs/|^notebooks/|^LICENSE$)

# --- Ruff Lint & Format ---
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.7
hooks:
- id: ruff
args: ["--fix"]
exclude: ^(docs/|notebooks/|LICENSE)$
exclude: (^docs/|^notebooks/|^LICENSE$)

# --- Black Code Formatter ---
- repo: https://github.com/psf/black
rev: 23.7.0
hooks:
- id: black
language_version: python3
exclude: ^(docs/|notebooks/|LICENSE)$
exclude: (^docs/|^notebooks/|^LICENSE$)

# --- Mypy (type checking) ---
- repo: https://github.com/pre-commit/mirrors-mypy
Expand All @@ -59,7 +59,7 @@ repos:
hooks:
- id: markdownlint-cli2
args: ['--fix','--config','.markdownlint.yaml']
exclude: ^(docs/|notebooks/|LICENSE)$
exclude: (^docs/|^notebooks/|^LICENSE$)

# --- Remove Notebook Metadata ---
- repo: https://github.com/kynan/nbstripout
Expand All @@ -74,4 +74,4 @@ repos:
hooks:
- id: codespell
types: [text]
exclude: ^(docs/_build/|build/|dist/|notebooks/|LICENSE)$
exclude: (^docs/_build/|^tests/resources/|^build/|^dist/|^notebooks/|^LICENSE$)
31 changes: 31 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!

cff-version: 1.2.0
title: Python-Nanolocz-Library
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- given-names: Daniel E
family-names: Rollins
email: d.e.rollins@leed.ac.uk
affiliation: University of Leeds
- given-names: George R
family-names: Heath
affiliation: University of Leeds
email: G.R.Heath@leeds.ac.uk
orcid: 'https://orcid.org/0000-0001-6431-2191'
repository-code: 'https://github.com/derollins/Python-Nanolocz-Library'
abstract: >-
A Python implementation of the NanoLocz library
(https://github.com/George-R-Heath/NanoLocz-Matlab-Library).
keywords:
- AFM
- Atomic Force MIcroscopy
- High Speed AFM
- Levelling
- image processing
- image analysis
license: GPL-3.0
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pip install .

### Requirements

This library requires **Python 3.10 or newer** and uses modern scientific Python packages to replace MATLAB functionality
This library requires **Python 3.11 or newer** and uses modern scientific Python packages to replace MATLAB functionality
from the original NanoLocz platform:

- **NumPy** – Core numerical operations and array handling (replaces MATLAB’s matrix operations).
Expand All @@ -61,7 +61,7 @@ easily extensible for AFM workflows.
import numpy as np
from pnanolocz_lib.level import apply_level
from pnanolocz_lib.level_auto import apply_level_auto
from pnanolocz_lib.thresholder import thresholder
from pnanolocz_lib.thresholder import apply_thresholder
from pnanolocz_lib.level_weighted import apply_weighted_level

# 1) Polynomial plane leveling
Expand All @@ -78,7 +78,7 @@ stack = np.load("stack.npy") # (N,H,W)
out = apply_level_auto(stack, routine="multi-plane-otsu")

# 4) Otsu mask
otsu_mask = thresholder(img, method="otsu", limits=None)
mask = apply_thresholder(img, method="otsu", limits=None)
```

---
Expand Down Expand Up @@ -124,7 +124,11 @@ otsu_mask = thresholder(img, method="otsu", limits=None)
- **`pnanolocz_lib.thresholder`**
Intensity / edge detection: histogram, Otsu, auto edges, skeleton, step detection.

Available thresholds:
Typical usage involves calling the `apply_thresholder()` function with an image (2D)
or image stack (3D) and specifying the desired method and polynomial orders.
(see Quickstart above for an example)

Available thresholder functions:

| Method | Description |
|--------------|-------------|
Expand Down Expand Up @@ -168,8 +172,13 @@ otsu_mask = thresholder(img, method="otsu", limits=None)
## 📝 Citation

If you use this library, please cite:
Heath, G.R. et al. *NanoLocz: Image analysis platform for AFM, high‑speed AFM and localization AFM.*
Small Methods 2024, 2301766. <https://doi.org/10.1002/smtd.202301766>
> Heath, G.R. et al. *NanoLocz: Image analysis platform for AFM, high‑speed AFM and localization AFM.*
> Small Methods 2024, 2301766. <https://doi.org/10.1002/smtd.202301766>

and

> Rollins, D. E., & Heath, G. R. (2025). *Python-NanoLocz-Library: A Python implementation of the NanoLocz AFM leveling and
> analysis tools*. University of Leeds. <https://github.com/derollins/Python-Nanolocz-Library>

---

Expand Down
118 changes: 28 additions & 90 deletions notebooks/Test pnanolocz-lib.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"outputs": [],
"source": [
"data_path = r\"C:/Users/ggjh246/OneDrive - University of Leeds/Code/playNano_testdata/save-2025.05.20-12.57.06.187.h5-jpk\"\n",
"# Un- comment the following line if you do not have playnano installed in your enviroment. \n",
"#%pip install numpy pandas matplotlib playnano\n",
"from playnano.io.loader import load_afm_stack\n",
"afm_stack = load_afm_stack(data_path, channel = \"height_trace\")"
]
Expand All @@ -40,24 +42,18 @@
"metadata": {},
"outputs": [],
"source": [
"from pnanolocz_lib import level_auto, level, level_weighted\n",
"from pnanolocz_lib import level_auto, level, level_weighted, thresholder\n",
"\n",
"frames = afm_stack.n_frames\n",
"frame_ind = range(0, frames)\n",
"\n",
"plane_levelled = level.apply_level(afm_stack.data, 2, 2, \"plane\")\n",
"plt.imshow(plane_levelled[5], cmap=\"afmhot\")\n"
"frame_ind = range(0, frames)"
]
},
{
"cell_type": "code",
"execution_count": null,
"cell_type": "markdown",
"id": "4",
"metadata": {},
"outputs": [],
"source": [
"levelled = level.apply_level(plane_levelled, 1, 0, \"med_line\",)\n",
"plt.imshow(levelled[5], cmap=\"afmhot\")\n"
"### Apply a quadratic plane fit"
]
},
{
Expand All @@ -67,17 +63,16 @@
"metadata": {},
"outputs": [],
"source": [
"auto_leveled = level_auto.apply_level_auto(afm_stack.data, \"multi-plane-otsu\")"
"plane_levelled = level.apply_level(afm_stack.data, 2, 2, \"plane\")\n",
"plt.imshow(plane_levelled[5], cmap=\"afmhot\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"cell_type": "markdown",
"id": "6",
"metadata": {},
"outputs": [],
"source": [
"plt.imshow(auto_leveled[5], cmap=\"afmhot\")"
"### Mask all the pixels above 0"
]
},
{
Expand All @@ -87,18 +82,16 @@
"metadata": {},
"outputs": [],
"source": [
"from pnanolocz_lib.thresholder import thresholder\n",
"mask_hist = thresholder(plane_levelled, 'histogram', limits = (0.2, 100),invert = False)"
"mask_hist = thresholder.apply_thresholder(plane_levelled, 'histogram', limits = (float('-inf'), 0.2),invert = False)\n",
"plt.imshow(mask_hist[5], interpolation= 'none')"
]
},
{
"cell_type": "code",
"execution_count": null,
"cell_type": "markdown",
"id": "8",
"metadata": {},
"outputs": [],
"source": [
"plt.imshow(mask_hist[5], interpolation= 'none')"
"### Apply a line fit from the background (not masked) to the image"
]
},
{
Expand All @@ -108,20 +101,16 @@
"metadata": {},
"outputs": [],
"source": [
"from pnanolocz_lib.level_weighted import apply_level_weighted\n",
"lev_weight = apply_level_weighted(plane_levelled, 1, 1, \"smed_line\", mask=mask_hist)\n",
"plt.imshow(lev_weight[5], cmap=\"afmhot\")"
"masked_line_levelled = level.apply_level(plane_levelled, 1, 0, \"line\", mask_hist)\n",
"plt.imshow(masked_line_levelled[5], cmap=\"afmhot\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"cell_type": "markdown",
"id": "10",
"metadata": {},
"outputs": [],
"source": [
"masked_line_levelled = level.apply_level(plane_levelled, 1, 0, \"line\", mask_hist)\n",
"plt.imshow(masked_line_levelled[5], cmap=\"afmhot\")"
"### Rather than manually applying steps, use the \"multi-plane-otsu\" auto level routine"
]
},
{
Expand All @@ -130,17 +119,17 @@
"id": "11",
"metadata": {},
"outputs": [],
"source": []
"source": [
"auto_leveled = level_auto.apply_level_auto(afm_stack.data, \"multi-plane-otsu\")\n",
"plt.imshow(auto_leveled[5], cmap=\"afmhot\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"cell_type": "markdown",
"id": "12",
"metadata": {},
"outputs": [],
"source": [
"hist2 = thresholder(masked_line_levelled, \"histogram\", limits=(0.4,100), invert=False)\n",
"plt.imshow(hist2[5], interpolation= 'none')"
"### Use `get_background` to see the values subtracted from a leveled image."
]
},
{
Expand All @@ -150,8 +139,7 @@
"metadata": {},
"outputs": [],
"source": [
"maksed_linemed_levelled = level.apply_level(masked_line_levelled, 1, 0, \"med_line\", mask_hist)\n",
"plt.imshow(maksed_linemed_levelled[1], cmap=\"afmhot\")"
"from pnanolocz_lib.level import get_background"
]
},
{
Expand All @@ -161,7 +149,7 @@
"metadata": {},
"outputs": [],
"source": [
"mask_hist2 =thresholder(maksed_linemed_levelled, 'histogram', limits= (0.5,100), invert=False)"
"bg = get_background(afm_stack.data, 1, 1, 'plane')"
]
},
{
Expand All @@ -171,8 +159,7 @@
"metadata": {},
"outputs": [],
"source": [
"mask_hist2 = thresholder(maksed_linemed_levelled, 'histogram', limits = (0.5,100),invert = False)\n",
"plt.imshow(mask_hist2[0])"
"plt.imshow(bg[5])"
]
},
{
Expand All @@ -181,55 +168,6 @@
"id": "16",
"metadata": {},
"outputs": [],
"source": [
"manual_levelled = level.apply_level(masked_plane_levelled, 1, 0, \"line\", mask_hist2)\n",
"plt.imshow(manual_levelled[0], cmap=\"afmhot\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "17",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "18",
"metadata": {},
"outputs": [],
"source": [
"from pnanolocz_lib.level import get_background"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "19",
"metadata": {},
"outputs": [],
"source": [
"bg = get_background(afm_stack.data, 1, 0, 'med_line')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "20",
"metadata": {},
"outputs": [],
"source": [
"plt.imshow(bg[0])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "21",
"metadata": {},
"outputs": [],
"source": []
}
],
Expand All @@ -249,7 +187,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.13"
"version": "3.12.12"
}
},
"nbformat": 4,
Expand Down
Loading