biomedisa 24.8.11__tar.gz → 25.7.1__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.
- {biomedisa-24.8.11 → biomedisa-25.7.1}/PKG-INFO +6 -4
- {biomedisa-24.8.11 → biomedisa-25.7.1}/README.md +3 -2
- {biomedisa-24.8.11 → biomedisa-25.7.1}/pyproject.toml +1 -1
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/deeplearning.py +27 -8
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/DataGenerator.py +93 -144
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/PredictDataGenerator.py +7 -5
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/biomedisa_helper.py +67 -25
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/crop_helper.py +7 -7
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/keras_helper.py +276 -151
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/random_walk/pyopencl_large.py +1 -1
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/random_walk/pyopencl_small.py +3 -3
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/random_walk/rw_large.py +6 -2
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/random_walk/rw_small.py +7 -3
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/interpolation.py +3 -1
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa.egg-info/PKG-INFO +6 -4
- {biomedisa-24.8.11 → biomedisa-25.7.1}/LICENSE +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/setup.cfg +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/__init__.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/__main__.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/DataGeneratorCrop.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/PredictDataGeneratorCrop.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/__init__.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/active_contour.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/amira_to_np/__init__.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/amira_to_np/amira_data_stream.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/amira_to_np/amira_grammar.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/amira_to_np/amira_header.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/amira_to_np/amira_helper.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/assd.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/create_slices.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/curvop_numba.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/django_env.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/nc_reader.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/pid.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/process_image.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/pycuda_test.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/random_walk/__init__.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/random_walk/gpu_kernels.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/random_walk/pycuda_large.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/random_walk/pycuda_large_allx.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/random_walk/pycuda_small.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/random_walk/pycuda_small_allx.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/remove_outlier.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/features/split_volume.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa/mesh.py +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa.egg-info/SOURCES.txt +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa.egg-info/dependency_links.txt +0 -0
- {biomedisa-24.8.11 → biomedisa-25.7.1}/src/biomedisa.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: biomedisa
|
3
|
-
Version:
|
3
|
+
Version: 25.7.1
|
4
4
|
Summary: Segmentation of 3D volumetric image data
|
5
5
|
Author: Philipp Lösel
|
6
6
|
Author-email: philipp.loesel@anu.edu.au
|
@@ -13,6 +13,7 @@ Classifier: Operating System :: OS Independent
|
|
13
13
|
Requires-Python: >=3.9
|
14
14
|
Description-Content-Type: text/markdown
|
15
15
|
License-File: LICENSE
|
16
|
+
Dynamic: license-file
|
16
17
|
|
17
18
|
[](https://biomedisa.info)
|
18
19
|
-----------
|
@@ -36,13 +37,14 @@ License-File: LICENSE
|
|
36
37
|
Biomedisa (https://biomedisa.info) is a free and easy-to-use open-source application for segmenting large 3D volumetric images such as CT and MRI scans, developed at [The Australian National University CTLab](https://ctlab.anu.edu.au/). Biomedisa's smart interpolation of sparsely pre-segmented slices enables accurate semi-automated segmentation by considering the complete underlying image data. Additionally, Biomedisa enables deep learning for fully automated segmentation across similar samples and structures. It is compatible with segmentation tools like Amira/Avizo, ImageJ/Fiji, and 3D Slicer. If you are using Biomedisa or the data for your research please cite: Lösel, P.D. et al. [Introducing Biomedisa as an open-source online platform for biomedical image segmentation.](https://www.nature.com/articles/s41467-020-19303-w) *Nat. Commun.* **11**, 5577 (2020).
|
37
38
|
|
38
39
|
## Hardware Requirements
|
39
|
-
+ One or more NVIDIA GPUs
|
40
|
+
+ One or more NVIDIA, AMD, or Intel GPUs
|
40
41
|
|
41
42
|
## Installation (command-line based)
|
42
43
|
+ [Ubuntu 22/24 + Smart Interpolation](https://github.com/biomedisa/biomedisa/blob/master/README/ubuntu_interpolation_cli.md)
|
43
44
|
+ [Ubuntu 22/24 + Deep Learning](https://github.com/biomedisa/biomedisa/blob/master/README/ubuntu_deeplearning_cli.md)
|
44
45
|
+ [Ubuntu 22/24 + Smart Interpolation + Deep Learning](https://github.com/biomedisa/biomedisa/blob/master/README/ubuntu_cli.md)
|
45
|
-
+ [Windows
|
46
|
+
+ [Windows (WSL) + Smart Interpolation + Deep Learning ("advanced")](https://github.com/biomedisa/biomedisa/blob/master/README/windows_wsl.md)
|
47
|
+
+ [Windows 10/11 + Smart Interpolation (NVIDIA, AMD, Intel) ("simple")](https://github.com/biomedisa/biomedisa/blob/master/README/windows_interpolation.md)
|
46
48
|
|
47
49
|
## Installation (3D Slicer extension)
|
48
50
|
+ [Ubuntu 22/24](https://github.com/biomedisa/biomedisa/blob/master/README/ubuntu_slicer.md)
|
@@ -20,13 +20,14 @@
|
|
20
20
|
Biomedisa (https://biomedisa.info) is a free and easy-to-use open-source application for segmenting large 3D volumetric images such as CT and MRI scans, developed at [The Australian National University CTLab](https://ctlab.anu.edu.au/). Biomedisa's smart interpolation of sparsely pre-segmented slices enables accurate semi-automated segmentation by considering the complete underlying image data. Additionally, Biomedisa enables deep learning for fully automated segmentation across similar samples and structures. It is compatible with segmentation tools like Amira/Avizo, ImageJ/Fiji, and 3D Slicer. If you are using Biomedisa or the data for your research please cite: Lösel, P.D. et al. [Introducing Biomedisa as an open-source online platform for biomedical image segmentation.](https://www.nature.com/articles/s41467-020-19303-w) *Nat. Commun.* **11**, 5577 (2020).
|
21
21
|
|
22
22
|
## Hardware Requirements
|
23
|
-
+ One or more NVIDIA GPUs
|
23
|
+
+ One or more NVIDIA, AMD, or Intel GPUs
|
24
24
|
|
25
25
|
## Installation (command-line based)
|
26
26
|
+ [Ubuntu 22/24 + Smart Interpolation](https://github.com/biomedisa/biomedisa/blob/master/README/ubuntu_interpolation_cli.md)
|
27
27
|
+ [Ubuntu 22/24 + Deep Learning](https://github.com/biomedisa/biomedisa/blob/master/README/ubuntu_deeplearning_cli.md)
|
28
28
|
+ [Ubuntu 22/24 + Smart Interpolation + Deep Learning](https://github.com/biomedisa/biomedisa/blob/master/README/ubuntu_cli.md)
|
29
|
-
+ [Windows
|
29
|
+
+ [Windows (WSL) + Smart Interpolation + Deep Learning ("advanced")](https://github.com/biomedisa/biomedisa/blob/master/README/windows_wsl.md)
|
30
|
+
+ [Windows 10/11 + Smart Interpolation (NVIDIA, AMD, Intel) ("simple")](https://github.com/biomedisa/biomedisa/blob/master/README/windows_interpolation.md)
|
30
31
|
|
31
32
|
## Installation (3D Slicer extension)
|
32
33
|
+ [Ubuntu 22/24](https://github.com/biomedisa/biomedisa/blob/master/README/ubuntu_slicer.md)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/python3
|
2
2
|
##########################################################################
|
3
3
|
## ##
|
4
|
-
## Copyright (c) 2019-
|
4
|
+
## Copyright (c) 2019-2025 Philipp Lösel. All rights reserved. ##
|
5
5
|
## ##
|
6
6
|
## This file is part of the open source project biomedisa. ##
|
7
7
|
## ##
|
@@ -77,7 +77,8 @@ def deep_learning(img_data, label_data=None, val_img_data=None, val_label_data=N
|
|
77
77
|
z_patch=64, y_patch=64, x_patch=64, path_to_logfile=None, img_id=None, label_id=None,
|
78
78
|
remote=False, queue=0, username=None, shortfilename=None, dice_loss=False,
|
79
79
|
acwe=False, acwe_alpha=1.0, acwe_smooth=1, acwe_steps=3, clean=None, fill=None,
|
80
|
-
separation=False, mask=None, refinement=False
|
80
|
+
separation=False, mask=None, refinement=False, ignore_mask=False, mixed_precision=False,
|
81
|
+
slicer=False, path_to_data=None):
|
81
82
|
|
82
83
|
# create biomedisa
|
83
84
|
bm = Biomedisa()
|
@@ -91,6 +92,7 @@ def deep_learning(img_data, label_data=None, val_img_data=None, val_label_data=N
|
|
91
92
|
key_copy = tuple(locals().keys())
|
92
93
|
for arg in key_copy:
|
93
94
|
bm.__dict__[arg] = locals()[arg]
|
95
|
+
bm.path_to_data = bm.path_to_images
|
94
96
|
|
95
97
|
# normalization
|
96
98
|
bm.normalize = 1 if bm.normalization else 0
|
@@ -214,10 +216,16 @@ def deep_learning(img_data, label_data=None, val_img_data=None, val_label_data=N
|
|
214
216
|
|
215
217
|
if bm.predict:
|
216
218
|
|
217
|
-
#
|
218
|
-
|
219
|
-
|
220
|
-
|
219
|
+
# load model
|
220
|
+
try:
|
221
|
+
hf = h5py.File(bm.path_to_model, 'r')
|
222
|
+
meta = hf.get('meta')
|
223
|
+
configuration = meta.get('configuration')
|
224
|
+
bm.allLabels = np.array(meta.get('labels'))
|
225
|
+
except:
|
226
|
+
raise RuntimeError("Invalid model.")
|
227
|
+
|
228
|
+
# get configuration
|
221
229
|
channels, bm.x_scale, bm.y_scale, bm.z_scale, bm.normalize, mu, sig = np.array(configuration)[:]
|
222
230
|
channels, bm.x_scale, bm.y_scale, bm.z_scale, bm.normalize, mu, sig = int(channels), int(bm.x_scale), \
|
223
231
|
int(bm.y_scale), int(bm.z_scale), int(bm.normalize), float(mu), float(sig)
|
@@ -225,7 +233,6 @@ def deep_learning(img_data, label_data=None, val_img_data=None, val_label_data=N
|
|
225
233
|
normalization_parameters = np.array(meta['normalization'], dtype=float)
|
226
234
|
else:
|
227
235
|
normalization_parameters = np.array([[mu],[sig]])
|
228
|
-
bm.allLabels = np.array(meta.get('labels'))
|
229
236
|
if 'patch_normalization' in meta:
|
230
237
|
bm.patch_normalization = bool(meta['patch_normalization'][()])
|
231
238
|
if 'scaling' in meta:
|
@@ -506,6 +513,12 @@ if __name__ == '__main__':
|
|
506
513
|
help='Save data in formats like NRRD or TIFF using --extension=".nrrd"')
|
507
514
|
parser.add_argument('-ptm','--path_to_model', type=str, metavar='PATH', default=None,
|
508
515
|
help='Specify the model location for training')
|
516
|
+
parser.add_argument('-im','--ignore_mask', action='store_true', default=False,
|
517
|
+
help='Use a binary mask in the second channel of the label file to define ignored (0) and considered (1) areas during training')
|
518
|
+
parser.add_argument('-mp','--mixed_precision', action='store_true', default=False,
|
519
|
+
help='Use mixed precision in model')
|
520
|
+
parser.add_argument('--slicer', action='store_true', default=False,
|
521
|
+
help='Required for starting Biomedisa from 3D Slicer')
|
509
522
|
bm = parser.parse_args()
|
510
523
|
bm.success = True
|
511
524
|
|
@@ -543,6 +556,12 @@ if __name__ == '__main__':
|
|
543
556
|
bm.django_env = False
|
544
557
|
|
545
558
|
kwargs = vars(bm)
|
559
|
+
bm.path_to_data = bm.path_to_images
|
560
|
+
|
561
|
+
# verify model
|
562
|
+
if bm.predict and os.path.splitext(bm.path)[1] != '.h5':
|
563
|
+
bm = _error_(bm, "Invalid model.")
|
564
|
+
raise RuntimeError("Invalid model.")
|
546
565
|
|
547
566
|
# train or predict segmentation
|
548
567
|
try:
|
@@ -561,5 +580,5 @@ if __name__ == '__main__':
|
|
561
580
|
bm = _error_(bm, 'GPU out of memory. Reduce your batch size')
|
562
581
|
except Exception as e:
|
563
582
|
print(traceback.format_exc())
|
564
|
-
bm = _error_(bm, e)
|
583
|
+
bm = _error_(bm, str(e))
|
565
584
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
##########################################################################
|
2
2
|
## ##
|
3
|
-
## Copyright (c) 2019-
|
3
|
+
## Copyright (c) 2019-2025 Philipp Lösel. All rights reserved. ##
|
4
4
|
## ##
|
5
5
|
## This file is part of the open source project biomedisa. ##
|
6
6
|
## ##
|
@@ -26,11 +26,11 @@
|
|
26
26
|
## ##
|
27
27
|
##########################################################################
|
28
28
|
|
29
|
+
from biomedisa.features.biomedisa_helper import welford_mean_std
|
29
30
|
import numpy as np
|
30
31
|
import tensorflow as tf
|
31
32
|
import numba
|
32
33
|
import random
|
33
|
-
import scipy
|
34
34
|
|
35
35
|
def get_random_rotation_matrix(batch_size):
|
36
36
|
angle_xy = np.random.uniform(0, 2. * np.pi, batch_size)
|
@@ -80,7 +80,6 @@ def rotate_img_patch(src,trg,k,l,m,cos_a,sin_a,z_patch,y_patch,x_patch,imageHeig
|
|
80
80
|
trg[z-k,y-l,x-m] = val
|
81
81
|
return trg
|
82
82
|
|
83
|
-
|
84
83
|
@numba.jit(nopython=True)#parallel=True
|
85
84
|
def rotate_img_patch_3d(src,trg,k,l,m,rm_xx,rm_xy,rm_xz,rm_yx,rm_yy,rm_yz,rm_zx,rm_zy,rm_zz,z_patch,y_patch,x_patch,imageVertStride,imageDepth,imageHeight,imageWidth):
|
86
85
|
#return rotate_label_patch_3d(src,trg,k,l,m,rm_xx,rm_xy,rm_xz,rm_yx,rm_yy,rm_yz,rm_zx,rm_zy,rm_zz,z_patch,y_patch,x_patch,imageVertStride,imageDepth,imageHeight,imageWidth)
|
@@ -175,34 +174,11 @@ def rotate_label_patch_3d(src,trg,k,l,m,rm_xx,rm_xy,rm_xz,rm_yx,rm_yy,rm_yz,rm_z
|
|
175
174
|
trg[z-k,y-l,x-m] = src[idx_src_z,idx_src_y,idx_src_x]
|
176
175
|
return trg
|
177
176
|
|
178
|
-
def random_rotation_3d(image, max_angle=180):
|
179
|
-
""" Randomly rotate an image by a random angle (-max_angle, max_angle).
|
180
|
-
|
181
|
-
Arguments:
|
182
|
-
max_angle: `float`. The maximum rotation angle.
|
183
|
-
|
184
|
-
Returns:
|
185
|
-
batch of rotated 3D images
|
186
|
-
"""
|
187
|
-
|
188
|
-
# rotate along x-axis
|
189
|
-
angle = random.uniform(-max_angle, max_angle)
|
190
|
-
image2 = scipy.ndimage.rotate(image, angle, mode='nearest', axes=(0, 1), reshape=False)
|
191
|
-
|
192
|
-
# rotate along y-axis
|
193
|
-
angle = random.uniform(-max_angle, max_angle)
|
194
|
-
image3 = scipy.ndimage.rotate(image2, angle, mode='nearest', axes=(0, 2), reshape=False)
|
195
|
-
|
196
|
-
# rotate along z-axis
|
197
|
-
angle = random.uniform(-max_angle, max_angle)
|
198
|
-
image_rot = scipy.ndimage.rotate(image3, angle, mode='nearest', axes=(1, 2), reshape=False)
|
199
|
-
|
200
|
-
return image_rot
|
201
|
-
|
202
177
|
class DataGenerator(tf.keras.utils.Sequence):
|
203
178
|
'Generates data for Keras'
|
204
|
-
def __init__(self, img, label, list_IDs_fg, list_IDs_bg, shuffle, train,
|
205
|
-
dim_img=(32,32,32), n_classes=10, n_channels=1, augment=(False,False,False,False,0,False),
|
179
|
+
def __init__(self, img, label, list_IDs_fg, list_IDs_bg, shuffle, train, batch_size=32, dim=(32,32,32),
|
180
|
+
dim_img=(32,32,32), n_classes=10, n_channels=1, augment=(False,False,False,False,0,False),
|
181
|
+
patch_normalization=False, separation=False, ignore_mask=False):
|
206
182
|
'Initialization'
|
207
183
|
self.dim = dim
|
208
184
|
self.dim_img = dim_img
|
@@ -216,10 +192,10 @@ class DataGenerator(tf.keras.utils.Sequence):
|
|
216
192
|
self.shuffle = shuffle
|
217
193
|
self.augment = augment
|
218
194
|
self.train = train
|
219
|
-
self.classification = classification
|
220
195
|
self.on_epoch_end()
|
221
196
|
self.patch_normalization = patch_normalization
|
222
197
|
self.separation = separation
|
198
|
+
self.ignore_mask = ignore_mask
|
223
199
|
|
224
200
|
def __len__(self):
|
225
201
|
'Denotes the number of batches per epoch'
|
@@ -276,12 +252,12 @@ class DataGenerator(tf.keras.utils.Sequence):
|
|
276
252
|
def __data_generation(self, list_IDs_temp):
|
277
253
|
'Generates data containing batch_size samples' # X : (n_samples, *dim, n_channels)
|
278
254
|
|
279
|
-
#
|
255
|
+
# number of label channels
|
256
|
+
label_channels = 2 if self.ignore_mask else 1
|
257
|
+
|
258
|
+
# allocate memory
|
280
259
|
X = np.empty((self.batch_size, *self.dim, self.n_channels), dtype=np.float32)
|
281
|
-
|
282
|
-
y = np.empty((self.batch_size, 1), dtype=np.int32)
|
283
|
-
else:
|
284
|
-
y = np.empty((self.batch_size, *self.dim, 1), dtype=np.int32)
|
260
|
+
y = np.empty((self.batch_size, *self.dim, label_channels), dtype=np.int32)
|
285
261
|
|
286
262
|
# get augmentation parameter
|
287
263
|
flip_x, flip_y, flip_z, swapaxes, rotate, rotate3d = self.augment
|
@@ -306,119 +282,92 @@ class DataGenerator(tf.keras.utils.Sequence):
|
|
306
282
|
m = rest % self.dim_img[2]
|
307
283
|
|
308
284
|
# get patch
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
tmp_X = np.swapaxes(tmp_X,1,2)
|
335
|
-
|
336
|
-
# assign to batch
|
337
|
-
X[i,:,:,:,0] = tmp_X
|
338
|
-
y[i,0] = tmp_y
|
339
|
-
|
340
|
-
else:
|
341
|
-
# get patch
|
342
|
-
tmp_X = self.img[k:k+self.dim[0],l:l+self.dim[1],m:m+self.dim[2]]
|
343
|
-
tmp_y = self.label[k:k+self.dim[0],l:l+self.dim[1],m:m+self.dim[2]]
|
344
|
-
|
345
|
-
# center label gets value 1
|
346
|
-
if self.separation:
|
347
|
-
centerLabel = tmp_y[self.dim[0]//2,self.dim[1]//2,self.dim[2]//2]
|
348
|
-
tmp_y = tmp_y.copy()
|
349
|
-
tmp_y[tmp_y!=centerLabel]=0
|
350
|
-
tmp_y[tmp_y==centerLabel]=1
|
351
|
-
|
352
|
-
# augmentation
|
353
|
-
if self.train:
|
354
|
-
|
355
|
-
# rotate in xy plane
|
356
|
-
if rotate:
|
357
|
-
tmp_X = np.empty((*self.dim, self.n_channels), dtype=np.float32)
|
358
|
-
tmp_y = np.empty(self.dim, dtype=np.int32)
|
359
|
-
cos_a = cos_angle[i]
|
360
|
-
sin_a = sin_angle[i]
|
361
|
-
for c in range(self.n_channels):
|
362
|
-
tmp_X[:,:,:,c] = rotate_img_patch(self.img[:,:,:,c],tmp_X[:,:,:,c],k,l,m,cos_a,sin_a,
|
363
|
-
self.dim[0],self.dim[1],self.dim[2],
|
364
|
-
self.dim_img[1],self.dim_img[2])
|
365
|
-
tmp_y = rotate_label_patch(self.label,tmp_y,k,l,m,cos_a,sin_a,
|
285
|
+
tmp_X = self.img[k:k+self.dim[0],l:l+self.dim[1],m:m+self.dim[2]]
|
286
|
+
tmp_y = self.label[k:k+self.dim[0],l:l+self.dim[1],m:m+self.dim[2]]
|
287
|
+
|
288
|
+
# center label gets value 1
|
289
|
+
if self.separation:
|
290
|
+
centerLabel = tmp_y[self.dim[0]//2,self.dim[1]//2,self.dim[2]//2]
|
291
|
+
tmp_y = tmp_y.copy()
|
292
|
+
tmp_y[tmp_y!=centerLabel]=0
|
293
|
+
tmp_y[tmp_y==centerLabel]=1
|
294
|
+
|
295
|
+
# augmentation
|
296
|
+
if self.train:
|
297
|
+
|
298
|
+
# rotate in xy plane
|
299
|
+
if rotate:
|
300
|
+
tmp_X = np.empty((*self.dim, self.n_channels), dtype=np.float32)
|
301
|
+
tmp_y = np.empty((*self.dim, label_channels), dtype=np.int32)
|
302
|
+
cos_a = cos_angle[i]
|
303
|
+
sin_a = sin_angle[i]
|
304
|
+
for ch in range(self.n_channels):
|
305
|
+
tmp_X[...,ch] = rotate_img_patch(self.img[...,ch],tmp_X[...,ch],k,l,m,cos_a,sin_a,
|
306
|
+
self.dim[0],self.dim[1],self.dim[2],
|
307
|
+
self.dim_img[1],self.dim_img[2])
|
308
|
+
for ch in range(label_channels):
|
309
|
+
tmp_y[...,ch] = rotate_label_patch(self.label[...,ch],tmp_y[...,ch],k,l,m,cos_a,sin_a,
|
366
310
|
self.dim[0],self.dim[1],self.dim[2],
|
367
311
|
self.dim_img[1],self.dim_img[2])
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
312
|
+
|
313
|
+
# rotate through a random 3d angle, uniformly distributed on a sphere.
|
314
|
+
if rotate3d:
|
315
|
+
tmp_X = np.empty((*self.dim, self.n_channels), dtype=np.float32)
|
316
|
+
tmp_y = np.empty((*self.dim, label_channels), dtype=np.int32)
|
317
|
+
rm_xx = rot_mtx[i, 0, 0]
|
318
|
+
rm_xy = rot_mtx[i, 0, 1]
|
319
|
+
rm_xz = rot_mtx[i, 0, 2]
|
320
|
+
rm_yx = rot_mtx[i, 1, 0]
|
321
|
+
rm_yy = rot_mtx[i, 1, 1]
|
322
|
+
rm_yz = rot_mtx[i, 1, 2]
|
323
|
+
rm_zx = rot_mtx[i, 2, 0]
|
324
|
+
rm_zy = rot_mtx[i, 2, 1]
|
325
|
+
rm_zz = rot_mtx[i, 2, 2]
|
326
|
+
for ch in range(self.n_channels):
|
327
|
+
tmp_X[...,ch] = rotate_img_patch_3d(self.img[...,ch],tmp_X[...,ch],k,l,m,
|
328
|
+
rm_xx,rm_xy,rm_xz,rm_yx,rm_yy,rm_yz,rm_zx,rm_zy,rm_zz,
|
329
|
+
self.dim[0],self.dim[1],self.dim[2],
|
330
|
+
256, self.dim_img[0],self.dim_img[1],self.dim_img[2])
|
331
|
+
for ch in range(label_channels):
|
332
|
+
tmp_y[...,ch] = rotate_label_patch_3d(self.label[...,ch],tmp_y[...,ch],k,l,m,
|
388
333
|
rm_xx,rm_xy,rm_xz,rm_yx,rm_yy,rm_yz,rm_zx,rm_zy,rm_zz,
|
389
334
|
self.dim[0],self.dim[1],self.dim[2],
|
390
335
|
256, self.dim_img[0],self.dim_img[1],self.dim_img[2])
|
391
336
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
337
|
+
# flip patch along axes
|
338
|
+
v = np.random.randint(n_aug+1)
|
339
|
+
if np.any([flip_x, flip_y, flip_z]) and v>0:
|
340
|
+
flip = flips[v-1]
|
341
|
+
tmp_X = np.flip(tmp_X, flip)
|
342
|
+
tmp_y = np.flip(tmp_y, flip)
|
343
|
+
|
344
|
+
# swap axes
|
345
|
+
if swapaxes:
|
346
|
+
v = np.random.randint(4)
|
347
|
+
if v==1:
|
348
|
+
tmp_X = np.swapaxes(tmp_X,0,1)
|
349
|
+
tmp_y = np.swapaxes(tmp_y,0,1)
|
350
|
+
elif v==2:
|
351
|
+
tmp_X = np.swapaxes(tmp_X,0,2)
|
352
|
+
tmp_y = np.swapaxes(tmp_y,0,2)
|
353
|
+
elif v==3:
|
354
|
+
tmp_X = np.swapaxes(tmp_X,1,2)
|
355
|
+
tmp_y = np.swapaxes(tmp_y,1,2)
|
356
|
+
|
357
|
+
# patch normalization
|
358
|
+
if self.patch_normalization:
|
359
|
+
tmp_X = tmp_X.copy().astype(np.float32)
|
360
|
+
for ch in range(self.n_channels):
|
361
|
+
mean, std = welford_mean_std(tmp_X[...,ch])
|
362
|
+
tmp_X[...,ch] -= mean
|
363
|
+
tmp_X[...,ch] /= max(std, 1e-6)
|
364
|
+
|
365
|
+
# assign to batch
|
366
|
+
X[i] = tmp_X
|
367
|
+
y[i] = tmp_y
|
368
|
+
|
369
|
+
if self.ignore_mask:
|
370
|
+
return X, y
|
371
|
+
else:
|
372
|
+
return X, tf.keras.utils.to_categorical(y, num_classes=self.n_classes)
|
424
373
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
##########################################################################
|
2
2
|
## ##
|
3
|
-
## Copyright (c) 2019-
|
3
|
+
## Copyright (c) 2019-2025 Philipp Lösel. All rights reserved. ##
|
4
4
|
## ##
|
5
5
|
## This file is part of the open source project biomedisa. ##
|
6
6
|
## ##
|
@@ -26,6 +26,7 @@
|
|
26
26
|
## ##
|
27
27
|
##########################################################################
|
28
28
|
|
29
|
+
from biomedisa.features.biomedisa_helper import welford_mean_std
|
29
30
|
import numpy as np
|
30
31
|
import tensorflow as tf
|
31
32
|
|
@@ -77,10 +78,11 @@ class PredictDataGenerator(tf.keras.utils.Sequence):
|
|
77
78
|
# get patch
|
78
79
|
tmp_X = self.img[k:k+self.dim[0],l:l+self.dim[1],m:m+self.dim[2]]
|
79
80
|
if self.patch_normalization:
|
80
|
-
tmp_X =
|
81
|
-
for
|
82
|
-
|
83
|
-
tmp_X[
|
81
|
+
tmp_X = tmp_X.copy().astype(np.float32)
|
82
|
+
for ch in range(self.n_channels):
|
83
|
+
mean, std = welford_mean_std(tmp_X[...,ch])
|
84
|
+
tmp_X[...,ch] -= mean
|
85
|
+
tmp_X[...,ch] /= max(std, 1e-6)
|
84
86
|
X[i] = tmp_X
|
85
87
|
|
86
88
|
return X
|
@@ -32,6 +32,7 @@ from biomedisa.features.amira_to_np.amira_helper import amira_to_np, np_to_amira
|
|
32
32
|
from biomedisa.features.nc_reader import nc_to_np, np_to_nc
|
33
33
|
from tifffile import imread, imwrite
|
34
34
|
from medpy.io import load, save
|
35
|
+
from skimage import io
|
35
36
|
import SimpleITK as sitk
|
36
37
|
from PIL import Image
|
37
38
|
import numpy as np
|
@@ -52,25 +53,41 @@ def silent_remove(filename):
|
|
52
53
|
except OSError:
|
53
54
|
pass
|
54
55
|
|
56
|
+
# calculate mean and standard deviation using Welford's algorithm
|
57
|
+
@numba.jit(nopython=True)
|
58
|
+
def welford_mean_std(arr):
|
59
|
+
mean, m2, count = 0.0, 0.0, 0
|
60
|
+
for x in arr.ravel():
|
61
|
+
count += 1
|
62
|
+
delta = x - mean
|
63
|
+
mean += delta / count
|
64
|
+
delta2 = x - mean
|
65
|
+
m2 += delta * delta2 # Running sum of squared differences
|
66
|
+
std_dev = np.sqrt(m2 / count) if count > 1 else 0.0 # Population std deviation
|
67
|
+
return mean, std_dev
|
68
|
+
|
55
69
|
# determine all values and their count from an array
|
56
70
|
def unique(arr, return_counts=False):
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
if np.issubdtype(arr.dtype, np.integer) and np.all(arr >= 0):
|
72
|
+
try:
|
73
|
+
arr = arr.ravel()
|
74
|
+
counts = np.zeros(np.amax(arr)+1, dtype=int)
|
75
|
+
@numba.jit(nopython=True)
|
76
|
+
def __unique__(arr, size, counts):
|
77
|
+
for k in range(size):
|
78
|
+
counts[arr[k]] += 1
|
79
|
+
return counts
|
80
|
+
counts = __unique__(arr, arr.size, counts)
|
81
|
+
labels = np.where(counts)[0]
|
82
|
+
if return_counts:
|
83
|
+
return labels, counts[labels]
|
84
|
+
else:
|
85
|
+
return labels
|
86
|
+
except Exception as e:
|
87
|
+
print(f"Error: {e}")
|
88
|
+
return None
|
89
|
+
else:
|
90
|
+
return np.unique(arr, return_counts=return_counts)
|
74
91
|
|
75
92
|
# create a unique filename
|
76
93
|
def unique_file_path(path, dir_path=biomedisa.BASE_DIR+'/private_storage/'):
|
@@ -260,6 +277,10 @@ def recursive_file_permissions(path_to_dir):
|
|
260
277
|
except:
|
261
278
|
pass
|
262
279
|
|
280
|
+
def natural_key(string):
|
281
|
+
# Split the string into parts of digits and non-digits
|
282
|
+
return [int(s) if s.isdigit() else s.lower() for s in re.split(r'(\d+)', string)]
|
283
|
+
|
263
284
|
def load_data(path_to_data, process='None', return_extension=False):
|
264
285
|
|
265
286
|
if not os.path.exists(path_to_data):
|
@@ -341,7 +362,7 @@ def load_data(path_to_data, process='None', return_extension=False):
|
|
341
362
|
file_names = []
|
342
363
|
img_slices = []
|
343
364
|
header = []
|
344
|
-
files.sort()
|
365
|
+
files.sort(key=natural_key)
|
345
366
|
for file_name in files:
|
346
367
|
if os.path.isfile(file_name):
|
347
368
|
try:
|
@@ -349,8 +370,16 @@ def load_data(path_to_data, process='None', return_extension=False):
|
|
349
370
|
file_names.append(file_name)
|
350
371
|
img_slices.append(img)
|
351
372
|
header.append(img_header)
|
352
|
-
except:
|
353
|
-
|
373
|
+
except RuntimeError as e:
|
374
|
+
# Check for 64-bit TIFF error
|
375
|
+
if "Unable to read tiff file" in str(e) and "64-bit samples" in str(e):
|
376
|
+
try:
|
377
|
+
img = io.imread(file_name)
|
378
|
+
file_names.append(file_name)
|
379
|
+
img_slices.append(img)
|
380
|
+
header.append(None)
|
381
|
+
except:
|
382
|
+
pass
|
354
383
|
|
355
384
|
# get data size
|
356
385
|
img = img_slices[0]
|
@@ -408,14 +437,21 @@ def load_data(path_to_data, process='None', return_extension=False):
|
|
408
437
|
else:
|
409
438
|
return data, header
|
410
439
|
|
411
|
-
def _error_(bm, message):
|
440
|
+
def _error_(bm, message, level=1):
|
412
441
|
if bm.django_env:
|
413
442
|
from biomedisa.features.django_env import create_error_object
|
414
443
|
create_error_object(message, bm.remote, bm.queue, bm.img_id)
|
415
444
|
with open(bm.path_to_logfile, 'a') as logfile:
|
416
445
|
print('%s %s %s %s' %(time.ctime(), bm.username, bm.shortfilename, message), file=logfile)
|
417
|
-
|
418
|
-
|
446
|
+
elif bm.slicer and bm.path_to_data:
|
447
|
+
error_path = os.path.dirname(bm.path_to_data) + '/biomedisa-error.txt'
|
448
|
+
with open(error_path, 'w') as file:
|
449
|
+
file.write(message)
|
450
|
+
if level==0:
|
451
|
+
print('Warning:', message)
|
452
|
+
elif level==1:
|
453
|
+
print('Error:', message)
|
454
|
+
bm.success = False
|
419
455
|
return bm
|
420
456
|
|
421
457
|
def pre_processing(bm):
|
@@ -613,7 +649,10 @@ def _get_platform(bm):
|
|
613
649
|
if cl and bm.platform is None:
|
614
650
|
for vendor in ['NVIDIA', 'Intel', 'AMD', 'Apple']:
|
615
651
|
for dev, device_type in [('GPU',cl.device_type.GPU),('CPU',cl.device_type.CPU)]:
|
616
|
-
|
652
|
+
try:
|
653
|
+
all_platforms = cl.get_platforms()
|
654
|
+
except:
|
655
|
+
all_platforms = []
|
617
656
|
my_devices = []
|
618
657
|
for p in all_platforms:
|
619
658
|
if p.get_devices(device_type=device_type) and vendor in p.name:
|
@@ -634,7 +673,10 @@ def _get_platform(bm):
|
|
634
673
|
elif cl and len(bm.platform.split('_')) == 3:
|
635
674
|
plat, vendor, dev = bm.platform.split('_')
|
636
675
|
device_type=cl.device_type.GPU if dev=='GPU' else cl.device_type.CPU
|
637
|
-
|
676
|
+
try:
|
677
|
+
all_platforms = cl.get_platforms()
|
678
|
+
except:
|
679
|
+
all_platforms = []
|
638
680
|
my_devices = []
|
639
681
|
for p in all_platforms:
|
640
682
|
if p.get_devices(device_type=device_type) and vendor in p.name:
|