Skip to content

Commit

Permalink
enabled no_patches arg and fixed some directory creations
Browse files Browse the repository at this point in the history
  • Loading branch information
takumiando committed Jul 17, 2020
1 parent c08e300 commit 5d719ad
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 79 deletions.
38 changes: 0 additions & 38 deletions .github/workflows/pypipublish.yml

This file was deleted.

22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ Convert Helper for Histopathological / Cytopathological Machine Learning Tasks
3. Then wsiprocess helps converting WSI + Annotation data into patches and easy-to-use annotation data.

[WSIPatcher](https://github.com/tand826/WSIPatcher) will give you GUI.
[Command Helper](https://tand826.github.io/wsiprocess_command_line_helper) will recommend commands to run.

# Installation

Expand Down Expand Up @@ -109,21 +108,21 @@ patcher.get_patch_parallel(target_classes)
```


### As a command line tool
### As a command line tool (recommended)

#### basic flow

```bash
wsiprocess xxx.tiff method --annotation xxx.xml
wsiprocess [your method] xxx.tiff --annotation xxx.xml
```

#### Extract patches with mask of foreground area. The mask has pixels with 1 as foreground which are originally from 10 to 230 in the scale of 0-255, and pixels with 0 as background which are originally from 0 to 10 and from 230 to 255.

```bash
wsiprocess xxx.tif none -oa 0.01 -mm 10-230 -of 0.01 -ep
wsiprocess none xxx.tif -oa 0.01 -mm 10-230 -of 0.01 -ep
```

- Need recommendation for choice of arguments? -> [Command Helper](https://tand826.github.io/wsiprocess_command_line_helper). This is for v0.1. Now wsiprocess has more options. Type `wsiprocess` to see options.
- Need recommendation for choice of arguments? Type `wsiprocess -h` or `wsiprocess [your method] -h` to see options.

### As a docker command line tool (not working on)

Expand Down Expand Up @@ -274,10 +273,13 @@ pytest tests.py
# Citation

```
@article{wsiprocess,
Author = {Takumi Ando},
Title = {WSIPROCESS - Whole Slide Image Processing Library for Histopathological / Cytopathological Machine Learning Tasks},
Url = {https://github.com/tand826/wsiprocess},
Year = {2019}
@misc{https://doi.org/10.5281/zenodo.3887428,
doi = {10.5281/ZENODO.3887428},
url = {https://zenodo.org/record/3887428},
author = {{Takumi Ando}},
title = {tand826/wsiprocess: version 0.4},
publisher = {Zenodo},
year = {2020},
copyright = {Open Access}
}
```
4 changes: 2 additions & 2 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Params:
offset_x = OFFSET_X[0]
offset_y = OFFSET_Y[0]
magnification = MAGNIFICATIONS[0]
extract_patches = True
no_patches = False
voc_style = VOC_STYLE[0]
coco_style = COCO_STYLE[0]
yolo_style = YOLO_STYLE[0]
Expand Down Expand Up @@ -77,7 +77,7 @@ def cli(params):
offset_y=params.offset_y,
on_foreground=params.on_foreground,
on_annotation=params.on_annotation,
extract_patches=params.extract_patches)
no_patches=params.no_patches)
patcher.get_patch_parallel(annotation.classes)
if params.voc_style or params.coco_style or params.yolo_style:
converter = wp.converter(params.save_to/slide.filestem, params.save_to, params.ratio)
Expand Down
2 changes: 1 addition & 1 deletion wsiprocess/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
from .rule import Rule as rule
from .converter import Converter as converter

__version__ = "0.3.1"
__version__ = "0.4"
25 changes: 16 additions & 9 deletions wsiprocess/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ def set_base_parser(self):
self.base_parser = argparse.ArgumentParser(
description="wsiprocess command line tool")

def set_wsi_arg(self):
self.base_parser.add_argument(
"wsi", type=str,
help="Path to the target wsi.")

def set_common_args(self, parser):
""" Common Arguments """
parser.add_argument(
Expand Down Expand Up @@ -45,9 +40,17 @@ def set_common_args(self, parser):
parser.add_argument(
"-fs", "--finished_sample", action="store_true",
help="Generate samples at the end of the process.")
parser.add_argument(
"-np", "--no_patches", action="store_true",
help="Patcher run without extracting patches.")
parser.add_argument(
"-ep", "--extract_patches", action="store_true",
help="Extract the patches and save them as images.")
help="[Not Available]Extract the patches and save them as images.")

def set_wsi_arg(self, parser):
parser.add_argument(
"wsi", type=str,
help="Path to the target wsi.")

def add_annotation_args(self, parser, slide_is_sparse=False):
parser.add_argument(
Expand Down Expand Up @@ -94,6 +97,7 @@ def set_none_args(self):
parser_none = self.method_args.add_parser(
"none",
help="Arguments for not specific method.")
self.set_wsi_arg(parser_none)
self.add_on_foreground(parser_none)
self.add_binarization_method(parser_none)
self.set_common_args(parser_none)
Expand All @@ -103,6 +107,7 @@ def set_classification_args(self):
parser_cls = self.method_args.add_parser(
"classification",
help="Arguments for classification tasks.")
self.set_wsi_arg(parser_cls)
self.add_annotation_args(parser_cls)
self.set_common_args(parser_cls)

Expand All @@ -111,6 +116,8 @@ def set_detection_args(self):
parser_det = self.method_args.add_parser(
"detection",
help="Arguments for detection tasks.")
self.set_wsi_arg(parser_det)

parser_det.add_argument(
"-vo", "--voc_style", action="store_true",
help="Output as VOC style.")
Expand All @@ -132,14 +139,14 @@ def set_segmentation_args(self):
"segmentation",
help="Arguments for segmentation tasksk."
)
self.set_wsi_arg(parser_seg)
self.add_annotation_args(parser_seg)
self.set_common_args(parser_seg)

def build_args(self, command):
""" Base Parser """
self.set_base_parser()

self.set_wsi_arg()
self.set_method_args()
self.set_none_args()
self.set_classification_args()
Expand Down Expand Up @@ -179,7 +186,7 @@ def main(command=None):
rule = wp.rule(args.rule) if hasattr(args, "rule") else False
annotation = process_annotation(args, slide, rule)

if hasattr(args, "export_thumbs"):
if hasattr(args, "export_thumbs") and args.export_thumbs:
thumbs_dir = args.save_to/slide.filestem/"thumbs"
if not thumbs_dir.exists():
thumbs_dir.mkdir(parents=True)
Expand All @@ -201,7 +208,7 @@ def main(command=None):
offset_y=args.offset_y,
start_sample=args.start_sample,
finished_sample=args.finished_sample,
extract_patches=args.extract_patches)
no_patches=args.no_patches)
patcher.get_patch_parallel(annotation.classes)

if args.method == "detection":
Expand Down
28 changes: 16 additions & 12 deletions wsiprocess/patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ class Patcher:
Patcher starting.
finished_sample (bool, optional): Whether to save sample patches on
Patcher finished its work.
extract_patches (bool, optional): Whether to save patches when Patcher
runs.
extract_patches (bool, optional): This is deprecated because unless
"no_patches" is set, Patcher extracts patches.
no_patches (bool, optional): If set, Patcher runs without extracting
patches and saves them to disk.
Attributes:
slide (wsiprocess.slide.Slide): Slide object.
Expand Down Expand Up @@ -70,6 +72,7 @@ class Patcher:
finished_sample (bool): Whether to save sample patches on Patcher
finish.
extract_patches (bool): Whether to save patches when Patcher runs.
no_patches (bool): Whether to save patches when Patcher runs.
x_lefttop (list): Offsets of patches to the x-axis direction except for
the right edge.
Expand All @@ -87,7 +90,7 @@ def __init__(
patch_width=256, patch_height=256, overlap_width=0,
overlap_height=0, offset_x=0, offset_y=0, on_foreground=0.5,
on_annotation=1., start_sample=True, finished_sample=True,
extract_patches=True):
no_patches=False):
Verify.verify_sizes(
slide.wsi_width, slide.wsi_height, patch_width, patch_height,
overlap_width, overlap_height)
Expand All @@ -114,7 +117,7 @@ def __init__(

self.start_sample = start_sample
self.finished_sample = finished_sample
self.extract_patches = extract_patches
self.no_patches = no_patches

self.on_foreground = on_foreground
self.annotation = annotation
Expand All @@ -131,7 +134,7 @@ def __init__(

self.result = {"result": []}
self.verify = Verify(save_to, self.filestem, method,
start_sample, finished_sample, extract_patches)
start_sample, finished_sample, no_patches)
self.verify.verify_dirs()

def __str__(self):
Expand Down Expand Up @@ -335,7 +338,7 @@ def save_results(self):
self.result["offset_y"] = self.offset_y
self.result["start_sample"] = self.start_sample
self.result["finished_sample"] = self.finished_sample
self.result["extract_patches"] = self.extract_patches
self.result["no_patches"] = self.no_patches
self.result["on_foreground"] = self.on_foreground
self.result["on_annotation"] = self.on_annotation
self.result["save_to"] = str(Path(self.save_to).absolute())
Expand Down Expand Up @@ -365,12 +368,13 @@ def get_patch(self, x, y, classes=False):
on_annotation_classes.append(cls)
else:
on_annotation_classes = ["foreground"]
if self.extract_patches:
patch = self.slide.slide.crop(x, y, self.p_width, self.p_height)
for cls in on_annotation_classes:
patch = self.slide.slide.crop(x, y, self.p_width, self.p_height)
for cls in on_annotation_classes:
if not self.no_patches:
patch.jpegsave(
"{}/{}/patches/{}/{:06}_{:06}.jpg".format(self.save_to, self.filestem, cls, x, y))
self.save_patch_result(x, y, cls)
"{}/{}/patches/{}/{:06}_{:06}.jpg".format(
self.save_to, self.filestem, cls, x, y))
self.save_patch_result(x, y, cls)

def get_patch_parallel(self, classes=False, cores=-1):
"""Run get_patch() in parallel.
Expand All @@ -380,7 +384,7 @@ def get_patch_parallel(self, classes=False, cores=-1):
cores (int): Threads to run. -1 means same as the number of cores.
"""
for cls in classes:
if self.extract_patches:
if not self.no_patches:
self.verify.verify_dir(
"{}/{}/patches/{}".format(self.save_to, self.filestem, cls))
if self.method == "segmentation":
Expand Down
16 changes: 9 additions & 7 deletions wsiprocess/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ class Verify:
start_sample (bool): Whether to save sample patches on Patcher start.
finished_sample (bool): Whether to save sample patches on Patcher
finish.
extract_patches (bool): Whether to save patches when Patcher runs.
extract_patches (bool): [Deleted]Whether to save patches when Patcher
runs.
no_patches (bool): Whether to save patches when Patcher runs.
Attributes:
save_to (str): The root of the output directory.
Expand All @@ -29,34 +31,34 @@ class Verify:
start_sample (bool): Whether to save sample patches on Patcher start.
finished_sample (bool): Whether to save sample patches on Patcher
finish.
extract_patches (bool): Whether to save patches when Patcher runs.
extract_patches (bool): [Deleted]Whether to save patches when Patcher
runs.
no_patches (bool): Whether to save patches when Patcher runs.
"""

def __init__(
self, save_to, filestem, method, start_sample,
finished_sample, extract_patches):
finished_sample, no_patches):
self.save_to = save_to
self.filestem = filestem
self.method = method
self.start_sample = start_sample
self.finished_sample = finished_sample
self.extract_patches = extract_patches
self.no_patches = no_patches

def verify_dirs(self):
"""Ensure the output directories exists for each tasks.
"""
base_dir = Path(self.save_to)/self.filestem
self.verify_dir(base_dir)
if self.method == "none":
if self.method == "none" and not self.no_patches:
self.verify_dir(base_dir/"patches"/"foreground")
if self.method == "segmentation":
self.verify_dir(base_dir/"masks")
if self.start_sample:
self.verify_dir(base_dir/"start_sample")
if self.finished_sample:
self.verify_dir(base_dir/"finished_sample")
if self.extract_patches:
self.verify_dir(base_dir/"patches")

@staticmethod
def verify_dir(path):
Expand Down

0 comments on commit 5d719ad

Please sign in to comment.