Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
28711fb
NEW: Add feature read_region
Jul 2, 2020
e3b8562
MAINT: Configure setup.py
Jul 2, 2020
cd0b83d
STY: Rearrange examples in doc to be within 88 characters
Jul 2, 2020
e26971b
TST: Add test read_region
Jul 2, 2020
e9bba78
BUG: Fix travis bug
Jul 2, 2020
fde23d6
DEV: Add command line for read regions
Jul 2, 2020
9212432
BUG: Fix travis bug for read region
Jul 2, 2020
5cc5a21
BUG: Fix travis bug for read region
Jul 2, 2020
9c33d9e
BUG: Fix Travis bug
Jul 2, 2020
8a817b2
BUG: Fix Travis bug
Jul 2, 2020
1b77fc3
BUG: Fix Travis bug
Jul 2, 2020
8f44f6e
BUG: Fix Travis bug can't find WSI files
Jul 2, 2020
6e4e14a
TST: Add command line test for read region
Jul 2, 2020
6694609
DOC: Fix docstring for test_wsireader_read_region
Jul 2, 2020
3703ce4
BUG: Fix Travis error for command line read region
Jul 2, 2020
93cbc9e
DOC: Update docstring
Jul 2, 2020
3841658
DOC: Update docstring to fix issues with Sphinx build
Jul 2, 2020
ad3aba7
DOC: Create Read The Docs Configuration
John-P Jul 3, 2020
8a26683
NEW: Add feature read_region
Jul 2, 2020
7e533d9
STY: Rearrange examples in doc to be within 88 characters
Jul 2, 2020
5d66eb1
TST: Add test read_region
Jul 2, 2020
de1a6fa
BUG: Fix travis bug
Jul 2, 2020
faac800
DEV: Add command line for read regions
Jul 2, 2020
9257c94
BUG: Fix travis bug for read region
Jul 2, 2020
defc227
BUG: Fix travis bug for read region
Jul 2, 2020
012f426
BUG: Fix Travis bug
Jul 2, 2020
a5a9848
BUG: Fix Travis bug
Jul 2, 2020
6f76b61
BUG: Fix Travis bug
Jul 2, 2020
e97a256
BUG: Fix Travis bug can't find WSI files
Jul 2, 2020
3e559df
TST: Add command line test for read region
Jul 2, 2020
00af859
DOC: Fix docstring for test_wsireader_read_region
Jul 2, 2020
1a9ae8f
BUG: Fix Travis error for command line read region
Jul 2, 2020
9122236
DOC: Update docstring
Jul 2, 2020
6b1c7b1
DOC: Update docstring to fix issues with Sphinx build
Jul 2, 2020
44e008e
Merge remote-tracking branch 'origin/feature-read-region' into featur…
Jul 3, 2020
16ad9f9
MAINT: Replace cv2_imwrite with imwrite
Jul 3, 2020
5129549
DOC: Update usage.rst to include imwrite in doc
Jul 3, 2020
e7718a2
DOC: Update README.rst
Jul 6, 2020
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 docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ Utils
utils.misc
^^^^^^^^^^
.. automodule:: tiatoolbox.utils.misc
:members: save_yaml, split_path_name_ext, grab_files_from_dir
:members: save_yaml, split_path_name_ext, grab_files_from_dir, cv2_imwrite
8 changes: 8 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@

requirements = [
"Click>=7.0",
"numpy",
"pillow",
"matplotlib",
"setuptools<=45.1.0",
"opencv-python>=4.0",
"pathos==0.2.5",
"openslide-python==1.1.1",
"pyyaml",
]

setup_requirements = [
Expand Down
61 changes: 59 additions & 2 deletions tests/test_tiatoolbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pytest

from tiatoolbox.dataloader.slide_info import slide_info
from tiatoolbox.dataloader import wsireader
from tiatoolbox import utils
from tiatoolbox import cli
from tiatoolbox import __version__
Expand All @@ -12,6 +13,7 @@
import requests
import os
import pathlib
import numpy as np


@pytest.fixture
Expand Down Expand Up @@ -47,7 +49,7 @@ def _response_svs(request):
if not pathlib.Path.is_file(svs_file_path):
r = requests.get(
"http://openslide.cs.cmu.edu/download/openslide-testdata"
"/Hamamatsu/CMU-1.ndpi"
"/Aperio/CMU-1.svs"
)
with open(svs_file_path, "wb") as f:
f.write(r.content)
Expand All @@ -64,14 +66,43 @@ def test_slide_info(_response_ndpi, _response_svs):
"""pytest for slide_info as a python function"""
file_types = ("*.ndpi", "*.svs", "*.mrxs")
files_all = utils.misc.grab_files_from_dir(
input_path=str(pathlib.Path(r".")), file_types=file_types,
input_path=str(pathlib.Path(__file__).parent), file_types=file_types,
)
slide_params = slide_info(input_path=files_all, workers=2)

for slide_param in slide_params:
utils.misc.save_yaml(slide_param, slide_param["file_name"] + ".yaml")


def test_wsireader_slide_info(_response_svs):
"""pytest for slide_info in WSIReader class as a python function"""
file_types = ("*.svs",)
files_all = utils.misc.grab_files_from_dir(
input_path=str(pathlib.Path(__file__).parent), file_types=file_types,
)
input_dir, file_name, ext = utils.misc.split_path_name_ext(str(files_all[0]))
wsi_obj = wsireader.WSIReader(input_dir, file_name + ext)
slide_param = wsi_obj.slide_info()
utils.misc.save_yaml(slide_param, slide_param["file_name"] + ".yaml")


def test_wsireader_read_region(_response_svs):
"""pytest for read region as a python function"""
file_types = ("*.svs",)
files_all = utils.misc.grab_files_from_dir(
input_path=str(pathlib.Path(__file__).parent), file_types=file_types,
)
input_dir, file_name, ext = utils.misc.split_path_name_ext(str(files_all[0]))
wsi_obj = wsireader.WSIReader(input_dir, file_name + ext)
level = 0
region = [13000, 17000, 15000, 19000]
im_region = wsi_obj.read_region(region[0], region[1], region[2], region[3], level)
im_region = im_region[:, :, 0:3]
assert isinstance(im_region, np.ndarray)
assert im_region.dtype == "uint8"
assert im_region.shape == (2000, 2000, 3)


def test_command_line_help_interface():
"""Test the CLI help"""
runner = CliRunner()
Expand Down Expand Up @@ -106,3 +137,29 @@ def test_command_line_slide_info(_response_ndpi, _response_svs):
)

assert slide_info_result.exit_code == 0


def test_command_line_read_region(_response_ndpi):
"""Test the Read Region CLI."""
runner = CliRunner()
read_region_result = runner.invoke(
cli.main,
[
"read-region",
"--wsi_input",
str(pathlib.Path(__file__).parent.joinpath("CMU-1.ndpi")),
"--level",
"0",
"--mode",
"save",
"--region",
"0", "0", "2000", "2000",
"--output_path",
str(pathlib.Path(__file__).parent.joinpath("im_region.jpg")),
],
)

assert read_region_result.exit_code == 0
assert os.path.isfile(
str(pathlib.Path(__file__).parent.joinpath("im_region.jpg"))
)
48 changes: 48 additions & 0 deletions tiatoolbox/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
from tiatoolbox import __version__
from tiatoolbox import dataloader
from tiatoolbox import utils

import sys
import click
import os
import pathlib
from PIL import Image


def version_msg():
Expand Down Expand Up @@ -67,5 +70,50 @@ def slide_info(wsi_input, output_dir, file_types, mode, workers=None):
)


@main.command()
@click.option("--wsi_input", help="Path to WSI file")
@click.option(
"--output_path",
help="Path to output file to save the image region in save mode,"
" default=wsi_input_dir/../im_region",
)
@click.option(
"--region",
type=int,
nargs=4,
help="image region in the whole slide image to read" "default=0 0 2000 2000",
)
@click.option(
"--level",
type=int,
default=0,
help="pyramid level to read the image, " "default=0",
)
@click.option(
"--mode",
default="show",
help="'show' to display image region or 'save' to save at the output path"
", default=show",
)
def read_region(wsi_input, region, level, output_path, mode):
"""Reads a region in an whole slide image as specified"""
if all(region):
region = [0, 0, 2000, 2000]

input_dir, file_name, ext = utils.misc.split_path_name_ext(full_path=wsi_input)
if output_path is None and mode == "save":
output_path = str(pathlib.Path(input_dir).joinpath("../im_region.jpg"))
wsi_obj = dataloader.wsireader.WSIReader(
input_dir=input_dir, file_name=file_name + ext
)
im_region = wsi_obj.read_region(region[0], region[1], region[2], region[3], level)
if mode == "show":
im_region = Image.fromarray(im_region)
im_region.show()

if mode == "save":
utils.misc.cv2_imwrite(output_path, im_region)


if __name__ == "__main__":
sys.exit(main()) # pragma: no cover
6 changes: 3 additions & 3 deletions tiatoolbox/dataloader/slide_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ def slide_info(input_path, output_dir=None):
to run slide_info in parallel

Args:
input_path: Path to whole slide image
output_dir: Path to output directory to save the output
workers: num of cpu cores to use for multiprocessing
input_path (str): Path to whole slide image
output_dir (str): Path to output directory to save the output
workers (int): num of cpu cores to use for multiprocessing
Returns:
list: list of dictionary Whole Slide meta information

Expand Down
34 changes: 34 additions & 0 deletions tiatoolbox/dataloader/wsireader.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ def __init__(

def slide_info(self):
"""WSI meta data reader

Args:
self (WSIReader):

Returns:
dict: dictionary containing meta information
Expand Down Expand Up @@ -105,3 +107,35 @@ def slide_info(self):
}

return param

def read_region(self, start_w, start_h, end_w, end_h, level=0):
"""Read a region in whole slide image

Args:
start_w (int): starting point in x-direction (along width)
start_h (int): starting point in y-direction (along height)
end_w (int): end point in x-direction (along width)
end_h (int): end point in y-direction (along height)
level (int): pyramid level to read the image

Returns:
img_array : ndarray of size MxNx3
M=end_h-start_h, N=end_w-start_w

Examples:
>>> from tiatoolbox.dataloader import wsireader
>>> from matplotlib import pyplot as plt
>>> wsi_obj = wsireader.WSIReader(input_dir="./", file_name="CMU-1.ndpi")
>>> level = 0
>>> region = [13000, 17000, 15000, 19000]
>>> im_region = wsi_obj.read_region(
... region[0], region[1], region[2], region[3], level)
>>> plt.imshow(im_region)

"""
openslide_obj = self.openslide_obj
im_region = openslide_obj.read_region(
[start_w, start_h], level, [end_w - start_w, end_h - start_h]
)
im_region = np.asarray(im_region)
return im_region
20 changes: 20 additions & 0 deletions tiatoolbox/utils/misc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Miscellaneous small functions repeatedly used in tiatoolbox"""
import os
import cv2
import pathlib
import yaml

Expand Down Expand Up @@ -75,3 +76,22 @@ def save_yaml(input_dict, output_path="output.yaml"):
"""
with open(pathlib.Path(output_path), "w") as yaml_file:
yaml.dump(input_dict, yaml_file)


def cv2_imwrite(image_path, cv_im):
"""Write a numpy array to an image

Args:
image_path (str): file path (including extension) to save image
cv_im (ndarray): image array of dtype uint8, MxNx3

Returns:

Examples:
>>> from tiatoolbox import utils
>>> import numpy as np
>>> utils.misc.cv2_imwrite('BlankImage.jpg',
... np.ones([100, 100, 3]).astype('uint8')*255)

"""
cv2.imwrite(image_path, cv2.cvtColor(cv_im, cv2.COLOR_RGB2BGR))