Switch to unified view

a b/pathaia/patches/object_api.py
1
# coding: utf8
2
"""
3
A module to extract patches in a slide.
4
5
Enable filtering on tissue surface ratio.
6
Draft for hierarchical patch extraction and representation is proposed.
7
"""
8
9
import os
10
from typing import Optional, Sequence
11
from ..util.basic import ifnone
12
from ..util.types import PathLike, Filter, FilterList, Coord
13
from .functional_api import patchify_slide
14
from .functional_api import patchify_folder
15
from .functional_api import patchify_slide_hierarchically
16
from .functional_api import patchify_folder_hierarchically
17
from .filters import standardize_filters
18
from .errors import UnknownLevelError
19
from .compat import convert_coords
20
21
22
class Patchifier(object):
23
    """
24
    A class to handle patchification tasks.
25
26
    Args:
27
        outdir: path to an output directory.
28
        level: pyramid level to extract.
29
        psize: size of the side of the patches (in pixels).
30
        interval: {"x", "y"} interval between 2 neighboring patches.
31
        offset: {"x", "y"} offset in px on x and y axis for patch start.
32
        filters: filters to accept patches.
33
        extensions: list of file extensions to consider. Defaults to '.mrxs'.
34
        verbose: 0 => nada, 1 => patchifying parameters, 2 => start-end of processes,
35
            thumbnail export.
36
    """
37
38
    def __init__(
39
        self,
40
        outdir: PathLike,
41
        level: int,
42
        psize: int,
43
        interval: Coord,
44
        offset: Coord = (0, 0),
45
        filters: Optional[Sequence[Filter]] = None,
46
        extensions: Optional[Sequence[str]] = None,
47
        verbose: int = 2,
48
    ):
49
        self.outdir = outdir
50
        self.level = level
51
        self.psize = psize
52
        self.interval = convert_coords(interval)
53
        self.offset = convert_coords(offset)
54
        self.filters = ifnone(filters, [])
55
        self.verbose = verbose
56
        self.extensions = ifnone(extensions, (".mrxs",))
57
58
    def patchify(self, path: PathLike):
59
        """
60
        Patchify a slide or an entire folder of slides.
61
62
        Args:
63
            path: absolute path to a slide or a folder of slides.
64
65
        """
66
        if os.path.isdir(path):
67
            patchify_folder(
68
                path,
69
                self.outdir,
70
                self.level,
71
                self.psize,
72
                self.interval,
73
                offset=self.offset,
74
                filters=self.filters,
75
                verbose=self.verbose,
76
                extensions=self.extensions,
77
            )
78
        else:
79
            patchify_slide(
80
                path,
81
                self.outdir,
82
                self.level,
83
                self.psize,
84
                self.interval,
85
                offset=self.offset,
86
                filters=self.filters,
87
                verbose=self.verbose,
88
            )
89
90
    def add_filter(self, filter_func: Filter):
91
        """
92
        Add a filter function to this patch extractor.
93
94
        Args:
95
            filter_func: a function that take an image as argument and output a bool.
96
97
        """
98
        self.filters.append(filter_func)
99
100
101
class HierarchicalPatchifier(object):
102
    """
103
    A class to handle hierachical patchification tasks.
104
105
    Args:
106
        outdir: path to an output directory.
107
        top_level: top pyramid level to consider.
108
        low_level: lowest pyramid level to consider.
109
        psize: size of the side of the patches (in pixels).
110
        interval: {"x", "y"} interval between 2 neighboring patches.
111
        offset: {"x", "y"} offset in px on x and y axis for patch start.
112
        filters: filters to accept patches.
113
        silent: pyramid level not to output.
114
        extensions: list of file extensions to consider. Defaults to '.mrxs'.
115
        verbose: 0 => nada, 1 => patchifying parameters, 2 => start-end of processes, thumbnail export.
116
117
    """
118
119
    def __init__(
120
        self,
121
        outdir: PathLike,
122
        top_level: int,
123
        low_level: int,
124
        psize: int,
125
        interval: Coord,
126
        offset: Coord = (0, 0),
127
        filters: Optional[FilterList] = None,
128
        silent: Optional[Sequence[int]] = None,
129
        extensions: Optional[Sequence[str]] = None,
130
        verbose: int = 2,
131
    ):
132
        self.outdir = outdir
133
        self.top_level = top_level
134
        self.low_level = low_level
135
        self.psize = psize
136
        self.interval = convert_coords(interval)
137
        self.offset = convert_coords(offset)
138
        self.filters = standardize_filters(ifnone(filters, {}), top_level, low_level)
139
        self.verbose = verbose
140
        self.silent = ifnone(silent, [])
141
        self.extensions = ifnone(extensions, (".mrxs",))
142
143
    def patchify(self, path: PathLike):
144
        """
145
        Patchify hierarchically a slide or an entire folder of slides.
146
147
        Args:
148
            path: absolute path to a slide or a folder of slides.
149
150
        """
151
        if os.path.isdir(path):
152
            patchify_folder_hierarchically(
153
                path,
154
                self.outdir,
155
                self.top_level,
156
                self.low_level,
157
                self.psize,
158
                self.interval,
159
                offset=self.offset,
160
                filters=self.filters,
161
                silent=self.silent,
162
                extensions=self.extensions,
163
                verbose=self.verbose,
164
            )
165
        else:
166
            patchify_slide_hierarchically(
167
                path,
168
                self.outdir,
169
                self.top_level,
170
                self.low_level,
171
                self.psize,
172
                self.interval,
173
                offset=self.offset,
174
                filters=self.filters,
175
                silent=self.silent,
176
                verbose=self.verbose,
177
            )
178
179
    def add_filter(self, filter_func: Filter, level: Optional[int] = None):
180
        """
181
        Add a filter function to the hierarchical patch extractor.
182
183
        Args:
184
            filter_func: a function that take an image as argument and output a bool.
185
186
        """
187
        # if level is None, append filter func to all levels
188
        if level is None:
189
            for lev in self.filters:
190
                self.filters[lev].append(filter_func)
191
        # if level is int, append filter func to this level
192
        elif type(level) == int:
193
            if level not in self.filters:
194
                raise UnknownLevelError(
195
                    "Level {} is not in {} !!!".format(level, list(self.filters.keys()))
196
                )
197
            self.filters[level].append(filter_func)
198
        elif type(level) == list:
199
            for lev in level:
200
                self.add_filter(filter_func, lev)
201
        else:
202
            raise UnknownLevelError("{} is not a valid type of level !!!".format(level))