Skip to content
Closed
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
8 changes: 8 additions & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ convention = "google"
"S101", # Use of assert detected.
"PLR2004", # Use of magic values detected.
]
# WIPP-style CLI names and legacy class name; keep lint noise low until refactor.
"transforms/images/remove-border-objects-plugin/src/*.py" = [
"N801",
"N803",
"N816",
"ANN001",
"ANN201",
]

[isort]
force-single-line = true
23 changes: 16 additions & 7 deletions transforms/images/remove-border-objects-plugin/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
FROM labshare/polus-bfio-util:2.1.9-tensorflow
FROM polusai/bfio:2.5.0
ENV EXEC_DIR="/opt/executables"
RUN mkdir -p ${EXEC_DIR}
COPY VERSION ${EXEC_DIR}
COPY src ${EXEC_DIR}/
RUN pip3 install -r ${EXEC_DIR}/requirements.txt --no-cache-dir && \
pip3 install "bfio[all]"
ENTRYPOINT ["python3", "main.py"]
ENV POLUS_IMG_EXT=".ome.tif"
ENV POLUS_TAB_EXT=".csv"
ENV POLUS_LOG="INFO"

WORKDIR ${EXEC_DIR}
ENV TOOL_DIR="transforms/images/remove-border-objects-plugin"
RUN mkdir -p image-tools
COPY . ${EXEC_DIR}/image-tools
RUN pip3 install -U pip setuptools wheel \
&& python3 -c 'import sys; assert sys.version_info>=(3,11)' \
&& R="${EXEC_DIR}/image-tools" && M="$R/$TOOL_DIR" \
&& if [ -f "$M/pyproject.toml" ]; then pip3 install --no-cache-dir "$M"; \
else pip3 install --no-cache-dir "$R"; fi
ENTRYPOINT ["python3", "-m", "main"]
CMD ["--help"]
6 changes: 1 addition & 5 deletions transforms/images/remove-border-objects-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ At the moment this plugin supports label images with two dimensions only. We wil
<img src="./image.png">


**a -** Original image contains 67 unique label objects
**a -** Original image contains 67 unique label objects
**b -** Image with 16 detected border objects
**c -** Removing Border objects and sequential relabelling

Expand All @@ -41,7 +41,3 @@ This plugin takes two input arguments and
| `--inpDir` | Input image directory | Input | collection |
| `--pattern` | Filepattern to parse image files | Input | string |
| `--outDir` | Output collection | Output | collection |




2 changes: 1 addition & 1 deletion transforms/images/remove-border-objects-plugin/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.1
0.1.1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash

version=$(<VERSION)
docker build . -t polusai/remove-border-objects-plugin:${version}
docker build . -t polusai/remove-border-objects-plugin:${version}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ bump2version --config-file bumpversion.cfg --new-version ${version} --allow-dirt
docker push polusai/remove-border-objects-plugin:${version}

# Run unittests
python -m unittest
python -m unittest
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@
}
],
"validators": null
}
}
46 changes: 46 additions & 0 deletions transforms/images/remove-border-objects-plugin/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[project]
name = "polus-remove-border-objects-plugin"
version = "0.1.1"
requires-python = ">=3.11,<3.14"
dependencies = [
"bfio>=2.5.0",
"filepattern>=2.2.4",
"typer>=0.24.1,<0.25.0",
"numpy>=2.0.0",
"tqdm==4.67.3",
"basicpy @ git+https://github.com/ndonyapour/BaSiCPy.git@chore/update-supported-python",
]

[project.optional-dependencies]
dev = [
"bump2version>=1.0.1",
"pre-commit>=4.5.1",
"black>=26.3.1",
"flake8>=6.0.0",
"mypy>=1.0.0",
"pytest>=7.2.1",
"pytest-cov>=7.0.0",
"pytest-sugar>=1.1.1,<2.0.0",
"pytest-xdist>=3.8.0,<4.0.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.metadata]
allow-direct-references = true

[tool.hatch.build.targets.wheel]
packages = ["src/polus"]

[tool.pytest.ini_options]
pythonpath = [
"."
]

[tool.setuptools.package-dir]
"" = "src"

[tool.setuptools]
py-modules = ["main", "functions"]
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,4 @@ docker run --mount type=bind,source=${datapath},target=/data/ \
polusai/remove-border-objects-plugin:${version} \
--inpDir ${inpDir} \
--pattern ${pattern} \
--outDir ${outDir}

--outDir ${outDir}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Remove border objects plugin package."""
58 changes: 31 additions & 27 deletions transforms/images/remove-border-objects-plugin/src/functions.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,43 @@
import os
from bfio import BioReader, BioWriter
"""Label image border cleanup and sequential relabelling."""

from pathlib import Path

import numpy as np
from bfio import BioReader
from bfio import BioWriter
from skimage.segmentation import relabel_sequential


class Discard_borderobjects:

"""Discard objects which touches image borders and relabelling of objects.

Args:
inpDir (Path) : Path to label image directory
outDir (Path) : Path to relabel image directory
filename (str): Name of a label image
Returns:
label_image : ndarray of dtype int
label_image, with discarded objects touching border
"""
def __init__(self, inpDir, outDir, filename):
label_image, with discarded objects touching border.
"""

def __init__(self, inpDir, outDir, filename) -> None:
"""Load label image from ``inpDir`` / ``filename``."""
self.inpDir = inpDir
self.outDir= outDir
self.outDir = outDir
self.filename = filename
self.imagepath = os.path.join(self.inpDir, self.filename)
self.br_image = BioReader(self.imagepath)
self.imagepath = Path(self.inpDir) / self.filename
self.br_image = BioReader(str(self.imagepath))
self.label_img = self.br_image.read().squeeze()

def discard_borderobjects(self):
""" This functions identifies which label pixels touches image borders and
setting the values of those label pixels to background pixels values which is 0
"""Clear labels that touch the image border.

Sets border-touching label pixels to background (0).
"""
borderobj = list(self.label_img[0, :])
borderobj.extend(self.label_img[:, 0])
borderobj.extend(self.label_img[- 1, :])
borderobj.extend(self.label_img[:, - 1])
borderobj.extend(self.label_img[-1, :])
borderobj.extend(self.label_img[:, -1])
borderobj = np.unique(borderobj).tolist()

for obj in borderobj:
Expand All @@ -40,21 +46,19 @@ def discard_borderobjects(self):
return self.label_img

def relabel_sequential(self):
""" Sequential relabelling of objects in a label image
"""
relabel_img, _, inverse_map = relabel_sequential(self.label_img)
"""Sequential relabelling of objects in a label image."""
relabel_img, _, inverse_map = relabel_sequential(self.label_img)
return relabel_img, inverse_map


def save_relabel_image(self, x):
""" Writing images with relabelled and cleared border touching objects
"""
with BioWriter(file_path = Path(self.outDir, self.filename),
backend='python',
metadata = self.br_image.metadata,
X=self.label_img.shape[0],
Y=self.label_img.shape[0],
dtype=self.label_img.dtype) as bw:
"""Writing images with relabelled and cleared border touching objects."""
with BioWriter(
file_path=Path(self.outDir, self.filename),
backend="python",
metadata=self.br_image.metadata,
X=self.label_img.shape[0],
Y=self.label_img.shape[0],
dtype=self.label_img.dtype,
) as bw:
bw[:] = x
bw.close()
return
bw.close()
138 changes: 77 additions & 61 deletions transforms/images/remove-border-objects-plugin/src/main.py
Original file line number Diff line number Diff line change
@@ -1,89 +1,105 @@
import argparse, logging, os, time, filepattern
"""CLI entrypoint for the remove-border-objects plugin."""

import argparse
import logging
import os
import sys
import time
from pathlib import Path
from functions import *

import filepattern
from functions import Discard_borderobjects

#Import environment variables
POLUS_LOG = getattr(logging,os.environ.get('POLUS_LOG','INFO'))
POLUS_EXT = os.environ.get('POLUS_EXT','.ome.tif')
# Import environment variables
POLUS_LOG = getattr(logging, os.environ.get("POLUS_LOG", "INFO"))
POLUS_EXT = os.environ.get("POLUS_EXT", ".ome.tif")

# Initialize the logger
logging.basicConfig(format='%(asctime)s - %(name)-8s - %(levelname)-8s - %(message)s',
datefmt='%d-%b-%y %H:%M:%S')
logging.basicConfig(
format="%(asctime)s - %(name)-8s - %(levelname)-8s - %(message)s",
datefmt="%d-%b-%y %H:%M:%S",
)
logger = logging.getLogger("main")
logger.setLevel(POLUS_LOG)


def main(inpDir:Path,
pattern:str,
outDir:Path,
):
starttime= time.time()
if pattern is None:
logger.info(
"No filepattern was provided so filepattern uses all input files"
)
def main(
inpDir: Path,
pattern: str,
outDir: Path,
) -> None:
"""Run border discard and relabel for all matching images in ``inpDir``."""
starttime = time.time()
if pattern is None:
logger.info("No filepattern was provided so filepattern uses all input files")

assert inpDir.exists(), logger.info("Input directory does not exist")
count=0
fp = filepattern.FilePattern(inpDir,pattern)
imagelist = len([f for f in fp])
if not inpDir.exists():
logger.error("Input directory does not exist")
sys.exit(1)
count = 0
fp = filepattern.FilePattern(inpDir, pattern)
imagelist = len(list(fp))

for f in fp():
count += 1
file = f[0]['file'].name
logger.info(f'Label image: {file}')
db = Discard_borderobjects(inpDir, outDir, file)
db.discard_borderobjects()
relabel_img, _ = db.relabel_sequential()
db.save_relabel_image(relabel_img)
logger.info(f'Saving {count}/{imagelist} Relabelled image with discarded objects: {file}')
logger.info('Finished all processes')
endtime = (time.time() - starttime)/60
logger.info(f'Total time taken to process all images: {endtime}')
for f in fp():
count += 1
file = f[0]["file"].name
logger.info(f"Label image: {file}")
db = Discard_borderobjects(inpDir, outDir, file)
db.discard_borderobjects()
relabel_img, _ = db.relabel_sequential()
db.save_relabel_image(relabel_img)
msg = (
f"Saving {count}/{imagelist} Relabelled image "
f"with discarded objects: {file}"
)
logger.info(msg)
logger.info("Finished all processes")
endtime = (time.time() - starttime) / 60
logger.info(f"Total time taken to process all images: {endtime}")


# ''' Argument parsing '''
logger.info("Parsing arguments...")
parser = argparse.ArgumentParser(prog='main', description='Discard Border Objects Plugin')
parser = argparse.ArgumentParser(
prog="main",
description="Discard Border Objects Plugin",
)
# # Input arguments

parser.add_argument(
"--inpDir",
dest="inpDir",
type=str,
help="Input image collection to be processed by this plugin",
required=True
)
"--inpDir",
dest="inpDir",
type=str,
help="Input image collection to be processed by this plugin",
required=True,
)
parser.add_argument(
"--pattern",
dest="pattern",
type=str,
default=".+",
help="Filepattern regex used to parse image files",
required=False
)
"--pattern",
dest="pattern",
type=str,
default=".+",
help="Filepattern regex used to parse image files",
required=False,
)
# # Output arguments
parser.add_argument('--outDir',
dest='outDir',
parser.add_argument(
"--outDir",
dest="outDir",
type=str,
help='Output directory',
required=True
)
help="Output directory",
required=True,
)
# # Parse the arguments
args = parser.parse_args()
inpDir = Path(args.inpDir)

if (inpDir.joinpath('images').is_dir()):
inputDir = inpDir.joinpath('images').absolute()
logger.info('inpDir = {}'.format(inpDir))
if inpDir.joinpath("images").is_dir():
inputDir = inpDir.joinpath("images").absolute()
logger.info(f"inpDir = {inpDir}")
pattern = args.pattern
logger.info("pattern = {}".format(pattern))
logger.info(f"pattern = {pattern}")
outDir = Path(args.outDir)
logger.info('outDir = {}'.format(outDir))
logger.info(f"outDir = {outDir}")

if __name__=="__main__":
main(inpDir=inpDir,
pattern=pattern,
outDir=outDir
)
if __name__ == "__main__":
main(inpDir=inpDir, pattern=pattern, outDir=outDir)
Loading
Loading