pdnl-process 0.5__tar.gz → 0.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pdnl_process-0.5 → pdnl_process-0.7}/PKG-INFO +2 -2
- pdnl_process-0.7/pdnl_process/__init__.py +1 -0
- pdnl_process-0.7/pdnl_process/main.py +124 -0
- {pdnl_process-0.5 → pdnl_process-0.7}/pyproject.toml +2 -2
- pdnl_process-0.5/pdnl_process/__init__.py +0 -1
- pdnl_process-0.5/pdnl_process/main.py +0 -124
- {pdnl_process-0.5 → pdnl_process-0.7}/.github/workflows/publish.yml +0 -0
- {pdnl_process-0.5 → pdnl_process-0.7}/.gitignore +0 -0
- {pdnl_process-0.5 → pdnl_process-0.7}/README.md +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import main, process_roi
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from tqdm import tqdm
|
|
10
|
+
import pdnl_sana.geo
|
|
11
|
+
import pdnl_sana.image
|
|
12
|
+
import pdnl_sana.process
|
|
13
|
+
import pdnl_sana.logging
|
|
14
|
+
import pdnl_sana.filter
|
|
15
|
+
import pdnl_sana.threshold
|
|
16
|
+
|
|
17
|
+
from matplotlib import pyplot as plt
|
|
18
|
+
|
|
19
|
+
def process_roi(frame, mask, subroi_masks, frame_loc, roi_name, output_path,
|
|
20
|
+
apply_smoothing=False, normalize_background=False,
|
|
21
|
+
background_radius=200, background_overlap=0.5,
|
|
22
|
+
triangular_strictness=0.0, minimum_threshold=0,
|
|
23
|
+
morphology_filters=[],
|
|
24
|
+
staining_code='H-DAB', stain_vector=None, debug_level='normal'):
|
|
25
|
+
|
|
26
|
+
# TODO: move all pdnl_sana.process code into here, then other scripts import these functions
|
|
27
|
+
out_d = os.path.join(output_path, roi_name)
|
|
28
|
+
os.makedirs(out_d, exist_ok=True)
|
|
29
|
+
logger = pdnl_sana.logging.Logger(debug_level, os.path.join(out_d, 'log.pkl'))
|
|
30
|
+
|
|
31
|
+
# TODO: shouldn't be omitting the mask here, but leaving it here so all data is generated in the same way for now.
|
|
32
|
+
# this was a fix for the background code, but that code is solved now
|
|
33
|
+
if staining_code == 'H-DAB':
|
|
34
|
+
processor = pdnl_sana.process.HDABProcessor(
|
|
35
|
+
logger, frame,
|
|
36
|
+
#main_mask=mask,
|
|
37
|
+
apply_smoothing=apply_smoothing,
|
|
38
|
+
normalize_background=normalize_background,
|
|
39
|
+
stain_vector=stain_vector,
|
|
40
|
+
radius=background_radius, overlap=background_overlap,
|
|
41
|
+
)
|
|
42
|
+
if staining_code == 'CV-DAB':
|
|
43
|
+
processor = pdnl_sana.process.CVDABProcessor(
|
|
44
|
+
logger, frame,
|
|
45
|
+
#main_mask=mask,
|
|
46
|
+
apply_smoothing=apply_smoothing,
|
|
47
|
+
normalize_background=normalize_background,
|
|
48
|
+
stain_vector=stain_vector,
|
|
49
|
+
)
|
|
50
|
+
hist = processor.dab.get_histogram(mask=processor.main_mask)
|
|
51
|
+
stain = processor.dab.copy()
|
|
52
|
+
stain.save(os.path.join(out_d, 'stain.npy'))
|
|
53
|
+
|
|
54
|
+
# calculate the threshold
|
|
55
|
+
triangular_strictness = triangular_strictness
|
|
56
|
+
threshold = pdnl_sana.threshold.triangular_method(
|
|
57
|
+
hist,
|
|
58
|
+
strictness=triangular_strictness,
|
|
59
|
+
)
|
|
60
|
+
threshold = np.clip(threshold, minimum_threshold, None)
|
|
61
|
+
|
|
62
|
+
stain.mask(mask)
|
|
63
|
+
stain.threshold(threshold)
|
|
64
|
+
morphology_filters = [pdnl_sana.filter.MorphologyFilter(**x) \
|
|
65
|
+
for x in morphology_filters]
|
|
66
|
+
for filt in morphology_filters:
|
|
67
|
+
stain.apply_morphology_filter(filt)
|
|
68
|
+
|
|
69
|
+
# TODO: don't write anything out if output_path=None
|
|
70
|
+
if debug_level in ['debug', 'full']:
|
|
71
|
+
frame.save(os.path.join(out_d, 'frame.png'))
|
|
72
|
+
processor.bckg.save(os.path.join(out_d, 'bckg.png'))
|
|
73
|
+
|
|
74
|
+
if debug_level == 'full':
|
|
75
|
+
plt.show()
|
|
76
|
+
stain.save(os.path.join(out_d, 'pos.png'))
|
|
77
|
+
mask.save(os.path.join(out_d, 'mask.png'))
|
|
78
|
+
for subroi_name in subroi_masks:
|
|
79
|
+
subroi_masks[subroi_name].save(os.path.join(out_d, f'submask_{subroi_name}.png'))
|
|
80
|
+
logger.data['triangular_strictness'] = triangular_strictness
|
|
81
|
+
logger.data['minimum_threshold'] = minimum_threshold
|
|
82
|
+
logger.data['morphology_filter'] = morphology_filters
|
|
83
|
+
logger.data['threshold'] = threshold
|
|
84
|
+
logger.data['frame_loc'] = frame_loc
|
|
85
|
+
logger.write_data()
|
|
86
|
+
|
|
87
|
+
return frame, stain, logger
|
|
88
|
+
|
|
89
|
+
def main():
|
|
90
|
+
|
|
91
|
+
parser = argparse.ArgumentParser()
|
|
92
|
+
parser.add_argument('-i', '--input_path', help="path to image data to process", required=True)
|
|
93
|
+
parser.add_argument('-o', '--output_path', help="path to write outputs to", required=True)
|
|
94
|
+
parser.add_argument('-s', '--staining_code', help="stain protocol used to stain the input image", default='H-DAB')
|
|
95
|
+
parser.add_argument('-p', '--parameters_file', help=".json file containing processing parameters", default=None)
|
|
96
|
+
parser.add_argument('--debug_level', help="outputs extra files", default='normal')
|
|
97
|
+
args = parser.parse_args()
|
|
98
|
+
|
|
99
|
+
if args.parameters_file:
|
|
100
|
+
params = json.load(open(args.parameters_file))
|
|
101
|
+
else:
|
|
102
|
+
params = {}
|
|
103
|
+
|
|
104
|
+
os.makedirs(args.output_path, exist_ok=True)
|
|
105
|
+
|
|
106
|
+
# get the background image
|
|
107
|
+
# TODO: test how big the concatenated frame will be, and move to big roi mode if too big
|
|
108
|
+
rois = pdnl_sana.slide.concatenate_chunks(args.input_path)
|
|
109
|
+
for roi_name in rois:
|
|
110
|
+
frame, mask, subroi_masks, frame_loc = rois[roi_name]
|
|
111
|
+
|
|
112
|
+
process_roi(frame, mask, subroi_masks, frame_loc, roi_name, args.output_path,
|
|
113
|
+
apply_smoothing=params.get('apply_smoothing', False),
|
|
114
|
+
normalize_background=params.get('normalize_background', False),
|
|
115
|
+
triangular_strictness=params.get('triangular_strictness', 0.0),
|
|
116
|
+
minimum_threshold=params.get('minimum_threshold', 0),
|
|
117
|
+
morphology_filters=[pdnl_sana.filter.MorphologyFilter(**x) for x in params.get('morphology_filters')],
|
|
118
|
+
staining_code=args.staining_code,
|
|
119
|
+
stain_vector=params.get('stain_vector', None),
|
|
120
|
+
debug_level=args.debug_level,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
main()
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pdnl_process"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Noah Capp", email="noahmcapp@gmail.com" },
|
|
10
10
|
]
|
|
@@ -28,4 +28,4 @@ classifiers = [
|
|
|
28
28
|
Homepage = "https://github.com/PennCompPathology/pdnl_process"
|
|
29
29
|
|
|
30
30
|
[project.scripts]
|
|
31
|
-
pdnl_process = "pdnl_process:main"
|
|
31
|
+
pdnl_process = "pdnl_process:main"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .main import main
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import sys
|
|
5
|
-
import argparse
|
|
6
|
-
import json
|
|
7
|
-
|
|
8
|
-
import numpy as np
|
|
9
|
-
from tqdm import tqdm
|
|
10
|
-
import pdnl_sana.geo
|
|
11
|
-
import pdnl_sana.image
|
|
12
|
-
import pdnl_sana.process
|
|
13
|
-
import pdnl_sana.logging
|
|
14
|
-
import pdnl_sana.filter
|
|
15
|
-
import pdnl_sana.threshold
|
|
16
|
-
|
|
17
|
-
from matplotlib import pyplot as plt
|
|
18
|
-
|
|
19
|
-
def main():
|
|
20
|
-
|
|
21
|
-
parser = argparse.ArgumentParser()
|
|
22
|
-
parser.add_argument('-i', '--input_path', help="path to image data to process", required=True)
|
|
23
|
-
parser.add_argument('-o', '--output_path', help="path to write outputs to", required=True)
|
|
24
|
-
parser.add_argument('-s', '--staining_code', help="stain protocol used to stain the input image", default='H-DAB')
|
|
25
|
-
parser.add_argument('-p', '--parameters_file', help=".json file containing processing parameters", default=None)
|
|
26
|
-
args = parser.parse_args()
|
|
27
|
-
|
|
28
|
-
if args.parameters_file:
|
|
29
|
-
params = json.load(open(args.parameters_file))
|
|
30
|
-
else:
|
|
31
|
-
params = {}
|
|
32
|
-
|
|
33
|
-
os.makedirs(args.output_path, exist_ok=True)
|
|
34
|
-
|
|
35
|
-
# preprocess the chunks
|
|
36
|
-
# TODO: move all pdnl_sana.process code into here, then other scripts import these functions
|
|
37
|
-
print('Preprocessing chunks...', flush=True)
|
|
38
|
-
histogram = None
|
|
39
|
-
for d in tqdm(os.listdir(args.input_path)):
|
|
40
|
-
if not os.path.isdir(os.path.join(args.input_path, d)):
|
|
41
|
-
continue
|
|
42
|
-
|
|
43
|
-
i, j = list(map(int, d.split('_')))
|
|
44
|
-
|
|
45
|
-
in_d = os.path.join(args.input_path, f'{i}_{j}')
|
|
46
|
-
out_d = os.path.join(args.output_path, f'{i}_{j}')
|
|
47
|
-
os.makedirs(out_d, exist_ok=True)
|
|
48
|
-
|
|
49
|
-
logger = pdnl_sana.logging.Logger('normal', os.path.join(in_d, 'log.pkl'))
|
|
50
|
-
level = logger.data['level']
|
|
51
|
-
mpp = logger.data['mpp']
|
|
52
|
-
ds = logger.data['ds']
|
|
53
|
-
converter = pdnl_sana.geo.Converter(mpp, ds)
|
|
54
|
-
frame = pdnl_sana.image.Frame(os.path.join(in_d, 'frame.png'), level=level, converter=converter)
|
|
55
|
-
mask = pdnl_sana.image.Frame(os.path.join(in_d, 'mask.png'), level=level, converter=converter)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if args.staining_code == 'H-DAB':
|
|
59
|
-
processor = pdnl_sana.process.HDABProcessor(
|
|
60
|
-
logger, frame, main_mask=mask,
|
|
61
|
-
apply_smoothing=params.get('apply_smoothing', False),
|
|
62
|
-
normalize_background=params.get('normalize_background', False),
|
|
63
|
-
stain_vector=params.get('stain_vector', None)
|
|
64
|
-
)
|
|
65
|
-
if args.staining_code == 'CV-DAB':
|
|
66
|
-
processor = pdnl_sana.process.CVDABProcessor(
|
|
67
|
-
logger, frame, main_mask=mask,
|
|
68
|
-
apply_smoothing=params.get('apply_smoothing', False),
|
|
69
|
-
normalize_background=params.get('normalize_background', False),
|
|
70
|
-
stain_vector=params.get('stain_vector', None)
|
|
71
|
-
)
|
|
72
|
-
hist = processor.dab.get_histogram(mask=processor.main_mask)
|
|
73
|
-
if histogram is None:
|
|
74
|
-
histogram = hist
|
|
75
|
-
else:
|
|
76
|
-
histogram = histogram + hist
|
|
77
|
-
processor.dab.save(os.path.join(out_d, 'stain.npy'))
|
|
78
|
-
|
|
79
|
-
# calculate the threshold
|
|
80
|
-
if not histogram is None:
|
|
81
|
-
triangular_strictness = params.get('triangular_strictness', 0.0)
|
|
82
|
-
threshold = pdnl_sana.threshold.triangular_method(
|
|
83
|
-
histogram,
|
|
84
|
-
strictness=triangular_strictness,
|
|
85
|
-
debug=True
|
|
86
|
-
)
|
|
87
|
-
else:
|
|
88
|
-
threshold = 0
|
|
89
|
-
|
|
90
|
-
minimum_threshold = params.get('minimum_threshold', 0)
|
|
91
|
-
if threshold < minimum_threshold:
|
|
92
|
-
threshold = minimum_threshold
|
|
93
|
-
|
|
94
|
-
# apply the threshold to the pre-processed chunks
|
|
95
|
-
print('Processing chunks...', flush=True)
|
|
96
|
-
for d in tqdm(os.listdir(args.output_path)):
|
|
97
|
-
if not os.path.isdir(os.path.join(args.input_path, d)):
|
|
98
|
-
continue
|
|
99
|
-
|
|
100
|
-
i, j = list(map(int, d.split('_')))
|
|
101
|
-
|
|
102
|
-
in_d = os.path.join(args.input_path, f'{i}_{j}')
|
|
103
|
-
out_d = os.path.join(args.output_path, f'{i}_{j}')
|
|
104
|
-
|
|
105
|
-
mask = pdnl_sana.image.Frame(os.path.join(in_d, 'mask.png'))
|
|
106
|
-
stain = pdnl_sana.image.Frame(os.path.join(out_d, 'stain.npy'))
|
|
107
|
-
|
|
108
|
-
stain.mask(mask)
|
|
109
|
-
stain.threshold(threshold)
|
|
110
|
-
morphology_filters = [pdnl_sana.filter.MorphologyFilter(**x) \
|
|
111
|
-
for x in params.get('morphology_filters', [])]
|
|
112
|
-
for filt in morphology_filters:
|
|
113
|
-
stain.apply_morphology_filter(filt)
|
|
114
|
-
|
|
115
|
-
stain.save(os.path.join(out_d, 'pos.png'))
|
|
116
|
-
logger = pdnl_sana.logging.Logger('normal', os.path.join(out_d, 'log.pkl'))
|
|
117
|
-
logger.data['triangular_strictness'] = triangular_strictness
|
|
118
|
-
logger.data['minimum_threshold'] = minimum_threshold
|
|
119
|
-
logger.data['morphology_filter'] = morphology_filters
|
|
120
|
-
logger.data['threshold'] = threshold
|
|
121
|
-
logger.write_data()
|
|
122
|
-
|
|
123
|
-
if __name__ == "__main__":
|
|
124
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|