pdnl-process 0.2__tar.gz → 0.4__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.2 → pdnl_process-0.4}/PKG-INFO +3 -2
- pdnl_process-0.4/pdnl_process/main.py +117 -0
- {pdnl_process-0.2 → pdnl_process-0.4}/pyproject.toml +4 -3
- pdnl_process-0.2/pdnl_process/main.py +0 -62
- {pdnl_process-0.2 → pdnl_process-0.4}/.github/workflows/publish.yml +0 -0
- {pdnl_process-0.2 → pdnl_process-0.4}/.gitignore +0 -0
- {pdnl_process-0.2 → pdnl_process-0.4}/README.md +0 -0
- {pdnl_process-0.2 → pdnl_process-0.4}/pdnl_process/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pdnl_process
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4
|
|
4
4
|
Summary: PDNL Process
|
|
5
5
|
Project-URL: Homepage, https://github.com/PennCompPathology/pdnl_process
|
|
6
6
|
Author-email: Noah Capp <noahmcapp@gmail.com>
|
|
@@ -10,5 +10,6 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
13
|
-
Requires-Python: >=3.
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
14
|
Requires-Dist: pdnl-sana
|
|
15
|
+
Requires-Dist: tdqm
|
|
@@ -0,0 +1,117 @@
|
|
|
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
|
+
hist = processor.dab.get_histogram(mask=processor.main_mask)
|
|
66
|
+
if histogram is None:
|
|
67
|
+
histogram = hist
|
|
68
|
+
else:
|
|
69
|
+
histogram = histogram + hist
|
|
70
|
+
processor.dab.save(os.path.join(out_d, 'stain.npy'))
|
|
71
|
+
|
|
72
|
+
# calculate the threshold
|
|
73
|
+
if not histogram is None:
|
|
74
|
+
triangular_strictness = params.get('triangular_strictness', 0.0)
|
|
75
|
+
threshold = pdnl_sana.threshold.triangular_method(
|
|
76
|
+
histogram,
|
|
77
|
+
strictness=triangular_strictness,
|
|
78
|
+
debug=True
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
threshold = 0
|
|
82
|
+
|
|
83
|
+
minimum_threshold = params.get('minimum_threshold', 0)
|
|
84
|
+
if threshold < minimum_threshold:
|
|
85
|
+
threshold = minimum_threshold
|
|
86
|
+
|
|
87
|
+
# apply the threshold to the pre-processed chunks
|
|
88
|
+
print('Processing chunks...', flush=True)
|
|
89
|
+
for d in tqdm(os.listdir(args.output_path)):
|
|
90
|
+
if not os.path.isdir(os.path.join(args.input_path, d)):
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
i, j = list(map(int, d.split('_')))
|
|
94
|
+
|
|
95
|
+
in_d = os.path.join(args.input_path, f'{i}_{j}')
|
|
96
|
+
out_d = os.path.join(args.output_path, f'{i}_{j}')
|
|
97
|
+
|
|
98
|
+
mask = pdnl_sana.image.Frame(os.path.join(in_d, 'mask.png'))
|
|
99
|
+
stain = pdnl_sana.image.Frame(os.path.join(out_d, 'stain.npy'))
|
|
100
|
+
|
|
101
|
+
stain.mask(mask)
|
|
102
|
+
stain.threshold(threshold)
|
|
103
|
+
morphology_filters = [pdnl_sana.filter.MorphologyFilter(**x) \
|
|
104
|
+
for x in params.get('morphology_filters', [])]
|
|
105
|
+
for filt in morphology_filters:
|
|
106
|
+
stain.apply_morphology_filter(filt)
|
|
107
|
+
|
|
108
|
+
stain.save(os.path.join(out_d, 'pos.png'))
|
|
109
|
+
logger = pdnl_sana.logging.Logger('normal', os.path.join(out_d, 'log.pkl'))
|
|
110
|
+
logger.data['triangular_strictness'] = triangular_strictness
|
|
111
|
+
logger.data['minimum_threshold'] = minimum_threshold
|
|
112
|
+
logger.data['morphology_filter'] = morphology_filters
|
|
113
|
+
logger.data['threshold'] = threshold
|
|
114
|
+
logger.write_data()
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
main()
|
|
@@ -4,16 +4,17 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pdnl_process"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Noah Capp", email="noahmcapp@gmail.com" },
|
|
10
10
|
]
|
|
11
11
|
description = "PDNL Process"
|
|
12
12
|
dependencies = [
|
|
13
|
-
"pdnl_sana"
|
|
13
|
+
"pdnl_sana",
|
|
14
|
+
"tdqm",
|
|
14
15
|
]
|
|
15
16
|
readme = "README.md"
|
|
16
|
-
requires-python = ">=3.
|
|
17
|
+
requires-python = ">=3.12"
|
|
17
18
|
classifiers = [
|
|
18
19
|
"Programming Language :: Python :: 3",
|
|
19
20
|
"License :: OSI Approved :: MIT License",
|
|
@@ -1,62 +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
|
-
import pdnl_sana.image
|
|
10
|
-
import pdnl_sana.process
|
|
11
|
-
import pdnl_sana.logging
|
|
12
|
-
import pdnl_sana.filter
|
|
13
|
-
|
|
14
|
-
from matplotlib import pyplot as plt
|
|
15
|
-
|
|
16
|
-
def main():
|
|
17
|
-
|
|
18
|
-
parser = argparse.ArgumentParser()
|
|
19
|
-
parser.add_argument('-i', '--input_file', help="path to image file to process", required=True)
|
|
20
|
-
parser.add_argument('-o', '--output_file', help="path to write outputs to", required=True)
|
|
21
|
-
parser.add_argument('-m', '--mask_file', help="path to mask file to apply to the image", default=None)
|
|
22
|
-
parser.add_argument('-s', '--staining_code', help="stain protocol used to stain the input image", default='H-DAB')
|
|
23
|
-
parser.add_argument('-p', '--parameters_file', help=".json file containing processing parameters", default=None)
|
|
24
|
-
parser.add_argument('--debug', action='store_true', help="plots an output image to analyze")
|
|
25
|
-
args = parser.parse_args()
|
|
26
|
-
|
|
27
|
-
frame = pdnl_sana.image.Frame(args.input_file)
|
|
28
|
-
if args.mask_file:
|
|
29
|
-
mask = pdnl_sana.image.Frame(args.mask_file)
|
|
30
|
-
else:
|
|
31
|
-
mask = None
|
|
32
|
-
if args.parameters_file:
|
|
33
|
-
params = json.load(open(args.parameters_file))
|
|
34
|
-
else:
|
|
35
|
-
params = {}
|
|
36
|
-
|
|
37
|
-
file_name = os.path.splitext(os.path.basename(args.input_file))[0]
|
|
38
|
-
debug_level = 'full' if args.debug else 'normal'
|
|
39
|
-
logger = pdnl_sana.logging.Logger(debug_level, os.path.splitext(args.output_file)[0]+'.pkl')
|
|
40
|
-
|
|
41
|
-
if args.staining_code == 'H-DAB':
|
|
42
|
-
processor = pdnl_sana.process.HDABProcessor(
|
|
43
|
-
logger, frame, main_mask=mask,
|
|
44
|
-
apply_smoothing=params.get('apply_smoothing', False),
|
|
45
|
-
normalize_background=params.get('normalize_background', False),
|
|
46
|
-
stain_vector=params.get('stain_vector', None)
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
out = processor.run(
|
|
50
|
-
triangular_strictness=params.get('triangular_strictness', 0.0),
|
|
51
|
-
minimum_threshold=params.get('minimum_threshold', 0),
|
|
52
|
-
od_threshold=params.get('od_threshold', None),
|
|
53
|
-
morphology_filters=[pdnl_sana.filter.MorphologyFilter(**filter_params) for filter_params in params.get('morphology_filters', [])],
|
|
54
|
-
)['positive_dab']
|
|
55
|
-
|
|
56
|
-
if args.debug:
|
|
57
|
-
plt.show()
|
|
58
|
-
|
|
59
|
-
out.save(args.output_file)
|
|
60
|
-
|
|
61
|
-
if __name__ == "__main__":
|
|
62
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|