biomedisa 24.5.23__py3-none-any.whl → 24.7.1__py3-none-any.whl
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/deeplearning.py +35 -32
- biomedisa/features/DataGenerator.py +1 -1
- biomedisa/features/active_contour.py +3 -10
- biomedisa/features/biomedisa_helper.py +17 -16
- biomedisa/features/create_slices.py +4 -3
- biomedisa/features/keras_helper.py +280 -112
- biomedisa/features/remove_outlier.py +3 -9
- biomedisa/interpolation.py +8 -15
- biomedisa/mesh.py +12 -11
- {biomedisa-24.5.23.dist-info → biomedisa-24.7.1.dist-info}/METADATA +12 -7
- {biomedisa-24.5.23.dist-info → biomedisa-24.7.1.dist-info}/RECORD +14 -14
- {biomedisa-24.5.23.dist-info → biomedisa-24.7.1.dist-info}/WHEEL +1 -1
- {biomedisa-24.5.23.dist-info → biomedisa-24.7.1.dist-info}/LICENSE +0 -0
- {biomedisa-24.5.23.dist-info → biomedisa-24.7.1.dist-info}/top_level.txt +0 -0
biomedisa/deeplearning.py
CHANGED
@@ -65,11 +65,11 @@ def deep_learning(img_data, label_data=None, val_img_data=None, val_label_data=N
|
|
65
65
|
path_to_images=None, path_to_labels=None, val_images=None, val_labels=None,
|
66
66
|
path_to_model=None, predict=False, train=False, header_file=None,
|
67
67
|
balance=False, crop_data=False, flip_x=False, flip_y=False, flip_z=False,
|
68
|
-
swapaxes=False, train_dice=False, val_dice=True,
|
68
|
+
swapaxes=False, train_dice=False, val_dice=True, compression=True, ignore='none', only='all',
|
69
69
|
network_filters='32-64-128-256-512', resnet=False, debug_cropping=False,
|
70
|
-
save_cropped=False, epochs=100,
|
70
|
+
save_cropped=False, epochs=100, normalization=True, rotate=0.0, validation_split=0.0,
|
71
71
|
learning_rate=0.01, stride_size=32, validation_stride_size=32, validation_freq=1,
|
72
|
-
batch_size=None, x_scale=256, y_scale=256, z_scale=256,
|
72
|
+
batch_size=None, x_scale=256, y_scale=256, z_scale=256, scaling=True, early_stopping=0,
|
73
73
|
pretrained_model=None, fine_tune=False, workers=1, cropping_epochs=50,
|
74
74
|
x_range=None, y_range=None, z_range=None, header=None, extension='.tif',
|
75
75
|
img_header=None, img_extension='.tif', average_dice=False, django_env=False,
|
@@ -91,17 +91,13 @@ def deep_learning(img_data, label_data=None, val_img_data=None, val_label_data=N
|
|
91
91
|
for arg in key_copy:
|
92
92
|
bm.__dict__[arg] = locals()[arg]
|
93
93
|
|
94
|
-
# compression
|
95
|
-
if bm.no_compression:
|
96
|
-
bm.compression = False
|
97
|
-
else:
|
98
|
-
bm.compression = True
|
99
|
-
|
100
94
|
# normalization
|
101
|
-
if bm.
|
95
|
+
bm.normalize = 1 if bm.normalization else 0
|
96
|
+
|
97
|
+
# use patch normalization instead of normalizing the entire volume
|
98
|
+
if not bm.scaling:
|
102
99
|
bm.normalize = 0
|
103
|
-
|
104
|
-
bm.normalize = 1
|
100
|
+
bm.patch_normalization = True
|
105
101
|
|
106
102
|
# django environment
|
107
103
|
if bm.django_env:
|
@@ -217,14 +213,19 @@ def deep_learning(img_data, label_data=None, val_img_data=None, val_label_data=N
|
|
217
213
|
hf = h5py.File(bm.path_to_model, 'r')
|
218
214
|
meta = hf.get('meta')
|
219
215
|
configuration = meta.get('configuration')
|
220
|
-
channels, bm.x_scale, bm.y_scale, bm.z_scale, normalize, mu, sig = np.array(configuration)[:]
|
221
|
-
channels, bm.x_scale, bm.y_scale, bm.z_scale, normalize, mu, sig = int(channels), int(bm.x_scale), \
|
222
|
-
int(bm.y_scale), int(bm.z_scale), int(normalize), float(mu), float(sig)
|
223
|
-
if '
|
224
|
-
normalization_parameters = np.array(meta
|
216
|
+
channels, bm.x_scale, bm.y_scale, bm.z_scale, bm.normalize, mu, sig = np.array(configuration)[:]
|
217
|
+
channels, bm.x_scale, bm.y_scale, bm.z_scale, bm.normalize, mu, sig = int(channels), int(bm.x_scale), \
|
218
|
+
int(bm.y_scale), int(bm.z_scale), int(bm.normalize), float(mu), float(sig)
|
219
|
+
if 'normalization' in meta:
|
220
|
+
normalization_parameters = np.array(meta['normalization'], dtype=float)
|
225
221
|
else:
|
226
222
|
normalization_parameters = np.array([[mu],[sig]])
|
227
223
|
allLabels = np.array(meta.get('labels'))
|
224
|
+
if 'patch_normalization' in meta:
|
225
|
+
bm.patch_normalization = bool(meta['patch_normalization'][()])
|
226
|
+
if 'scaling' in meta:
|
227
|
+
bm.scaling = bool(meta['scaling'][()])
|
228
|
+
|
228
229
|
# check if amira header is available in the network
|
229
230
|
if header is None and meta.get('header') is not None:
|
230
231
|
header = [np.array(meta.get('header'))]
|
@@ -290,16 +291,11 @@ def deep_learning(img_data, label_data=None, val_img_data=None, val_label_data=N
|
|
290
291
|
region_of_interest, cropped_volume = ch.crop_data(bm.path_to_image, bm.path_to_model, bm.path_to_cropped_image,
|
291
292
|
bm.batch_size, bm.debug_cropping, bm.save_cropped, img_data, bm.x_range, bm.y_range, bm.z_range)
|
292
293
|
|
293
|
-
# load prediction data
|
294
|
-
img, img_header, z_shape, y_shape, x_shape, region_of_interest, img_data = load_prediction_data(bm.path_to_image,
|
295
|
-
channels, bm.x_scale, bm.y_scale, bm.z_scale, bm.no_scaling, normalize, normalization_parameters,
|
296
|
-
region_of_interest, img_data, img_header)
|
297
|
-
|
298
294
|
# make prediction
|
299
|
-
results, bm = predict_semantic_segmentation(bm,
|
300
|
-
|
301
|
-
|
302
|
-
|
295
|
+
results, bm = predict_semantic_segmentation(bm,
|
296
|
+
header, img_header, allLabels,
|
297
|
+
region_of_interest, extension, img_data,
|
298
|
+
channels, normalization_parameters)
|
303
299
|
|
304
300
|
# results
|
305
301
|
if cropped_volume is not None:
|
@@ -403,7 +399,7 @@ if __name__ == '__main__':
|
|
403
399
|
help='Dice loss function')
|
404
400
|
parser.add_argument('-ad','--average_dice', action='store_true', default=False,
|
405
401
|
help='Use averaged dice score of each label')
|
406
|
-
parser.add_argument('-nc', '--
|
402
|
+
parser.add_argument('-nc', '--no-compression', dest='compression', action='store_false',
|
407
403
|
help='Disable compression of segmentation results')
|
408
404
|
parser.add_argument('-i', '--ignore', type=str, default='none',
|
409
405
|
help='Ignore specific label(s), e.g. 2,5,6')
|
@@ -421,12 +417,12 @@ if __name__ == '__main__':
|
|
421
417
|
help='Epochs the network is trained')
|
422
418
|
parser.add_argument('-ce','--cropping_epochs', type=int, default=50,
|
423
419
|
help='Epochs the network for auto-cropping is trained')
|
424
|
-
parser.add_argument('-nn','--
|
425
|
-
help='Disable image
|
420
|
+
parser.add_argument('-nn','--no-normalization', dest='normalization', action='store_false',
|
421
|
+
help='Disable normalization of 3D image volumes')
|
426
422
|
parser.add_argument('-r','--rotate', type=float, default=0.0,
|
427
423
|
help='Randomly rotate during training')
|
428
424
|
parser.add_argument('-vs','--validation_split', type=float, default=0.0,
|
429
|
-
help='Percentage of data used for
|
425
|
+
help='Percentage of data used for training')
|
430
426
|
parser.add_argument('-lr','--learning_rate', type=float, default=0.01,
|
431
427
|
help='Learning rate')
|
432
428
|
parser.add_argument('-ss','--stride_size', metavar="[1-64]", type=int, choices=range(1,65), default=32,
|
@@ -447,7 +443,7 @@ if __name__ == '__main__':
|
|
447
443
|
help='Images and labels are scaled at y-axis to this size before training')
|
448
444
|
parser.add_argument('-zs','--z_scale', type=int, default=256,
|
449
445
|
help='Images and labels are scaled at z-axis to this size before training')
|
450
|
-
parser.add_argument('-ns','--
|
446
|
+
parser.add_argument('-ns','--no-scaling', dest='scaling', action='store_false',
|
451
447
|
help='Do not resize image and label data')
|
452
448
|
parser.add_argument('-es','--early_stopping', type=int, default=0,
|
453
449
|
help='Training is terminated when the accuracy has not increased in the epochs defined by this')
|
@@ -484,8 +480,15 @@ if __name__ == '__main__':
|
|
484
480
|
parser.add_argument('-hf','--header_file', type=str, metavar='PATH', default=None,
|
485
481
|
help='Location of header file')
|
486
482
|
bm = parser.parse_args()
|
487
|
-
|
488
483
|
bm.success = True
|
484
|
+
|
485
|
+
# prediction or training
|
486
|
+
if not any([bm.train, bm.predict]):
|
487
|
+
bm.predict = False
|
488
|
+
bm.train = True
|
489
|
+
if os.path.splitext(bm.path)[1] == '.h5':
|
490
|
+
bm.predict = True
|
491
|
+
bm.train = False
|
489
492
|
if bm.predict:
|
490
493
|
bm.path_to_labels = None
|
491
494
|
bm.path_to_model = bm.path
|
@@ -286,7 +286,7 @@ class DataGenerator(tf.keras.utils.Sequence):
|
|
286
286
|
|
287
287
|
# patch normalization
|
288
288
|
if self.patch_normalization:
|
289
|
-
tmp_X =
|
289
|
+
tmp_X = tmp_X.copy()
|
290
290
|
for c in range(self.n_channels):
|
291
291
|
tmp_X[:,:,:,c] -= np.mean(tmp_X[:,:,:,c])
|
292
292
|
tmp_X[:,:,:,c] /= max(np.std(tmp_X[:,:,:,c]), 1e-6)
|
@@ -106,7 +106,7 @@ def reduce_blocksize(raw, slices):
|
|
106
106
|
return raw, slices, argmin_z, argmax_z, argmin_y, argmax_y, argmin_x, argmax_x
|
107
107
|
|
108
108
|
def activeContour(data, labelData, alpha=1.0, smooth=1, steps=3,
|
109
|
-
path_to_data=None, path_to_labels=None,
|
109
|
+
path_to_data=None, path_to_labels=None, compression=True,
|
110
110
|
ignore='none', only='all', simple=False,
|
111
111
|
img_id=None, friend_id=None, remote=False):
|
112
112
|
|
@@ -126,12 +126,6 @@ def activeContour(data, labelData, alpha=1.0, smooth=1, steps=3,
|
|
126
126
|
else:
|
127
127
|
bm.django_env = False
|
128
128
|
|
129
|
-
# compression
|
130
|
-
if bm.no_compression:
|
131
|
-
bm.compression = False
|
132
|
-
else:
|
133
|
-
bm.compression = True
|
134
|
-
|
135
129
|
# disable file saving when called as a function
|
136
130
|
if bm.data is not None:
|
137
131
|
bm.path_to_data = None
|
@@ -374,8 +368,7 @@ def init_active_contour(image_id, friend_id, label_id, simple=False):
|
|
374
368
|
else:
|
375
369
|
try:
|
376
370
|
activeContour(None, None, path_to_data=image.pic.path, path_to_labels=friend.pic.path,
|
377
|
-
alpha=label.ac_alpha, smooth=label.ac_smooth, steps=label.ac_steps,
|
378
|
-
no_compression=(False if label.compression else True),
|
371
|
+
alpha=label.ac_alpha, smooth=label.ac_smooth, steps=label.ac_steps, compression=label.compression,
|
379
372
|
simple=simple, img_id=image_id, friend_id=friend_id, remote=False)
|
380
373
|
except Exception as e:
|
381
374
|
print(traceback.format_exc())
|
@@ -407,7 +400,7 @@ if __name__ == '__main__':
|
|
407
400
|
help='Number of smoothing steps')
|
408
401
|
parser.add_argument('-st', '--steps', type=int, default=3,
|
409
402
|
help='Number of iterations')
|
410
|
-
parser.add_argument('-nc', '--
|
403
|
+
parser.add_argument('-nc', '--no-compression', dest='compression', action='store_false',
|
411
404
|
help='Disable compression of segmentation results')
|
412
405
|
parser.add_argument('-i', '--ignore', type=str, default='none',
|
413
406
|
help='Ignore specific label(s), e.g. 2,5,6')
|
@@ -317,19 +317,23 @@ def load_data(path_to_data, process='None', return_extension=False):
|
|
317
317
|
data, header = None, None
|
318
318
|
else:
|
319
319
|
try:
|
320
|
-
#
|
321
|
-
|
322
|
-
|
320
|
+
# load data slice by slice
|
321
|
+
file_names = []
|
322
|
+
img_slices = []
|
323
|
+
header = []
|
324
|
+
files.sort()
|
325
|
+
for file_name in files:
|
326
|
+
if os.path.isfile(file_name):
|
323
327
|
try:
|
324
|
-
img,
|
328
|
+
img, img_header = load(file_name)
|
329
|
+
file_names.append(file_name)
|
330
|
+
img_slices.append(img)
|
331
|
+
header.append(img_header)
|
325
332
|
except:
|
326
|
-
|
327
|
-
else:
|
328
|
-
files.remove(name)
|
329
|
-
files.sort()
|
333
|
+
pass
|
330
334
|
|
331
335
|
# get data size
|
332
|
-
img
|
336
|
+
img = img_slices[0]
|
333
337
|
if len(img.shape)==3:
|
334
338
|
ysh, xsh, csh = img.shape[0], img.shape[1], img.shape[2]
|
335
339
|
channel = 'last'
|
@@ -340,11 +344,9 @@ def load_data(path_to_data, process='None', return_extension=False):
|
|
340
344
|
ysh, xsh = img.shape[0], img.shape[1]
|
341
345
|
csh, channel = 0, None
|
342
346
|
|
343
|
-
#
|
344
|
-
data = np.empty((len(
|
345
|
-
|
346
|
-
for k, file_name in enumerate(files):
|
347
|
-
img, img_header = load(file_name)
|
347
|
+
# create 3D volume
|
348
|
+
data = np.empty((len(file_names), ysh, xsh), dtype=img.dtype)
|
349
|
+
for k, img in enumerate(img_slices):
|
348
350
|
if csh==3:
|
349
351
|
img = rgb2gray(img, channel)
|
350
352
|
elif csh==1 and channel=='last':
|
@@ -352,8 +354,7 @@ def load_data(path_to_data, process='None', return_extension=False):
|
|
352
354
|
elif csh==1 and channel=='first':
|
353
355
|
img = img[0,:,:]
|
354
356
|
data[k] = img
|
355
|
-
|
356
|
-
header = [header, files, data.dtype]
|
357
|
+
header = [header, file_names, data.dtype]
|
357
358
|
data = np.swapaxes(data, 1, 2)
|
358
359
|
data = np.copy(data, order='C')
|
359
360
|
except Exception as e:
|
@@ -151,6 +151,7 @@ def create_slices(path_to_data, path_to_label, on_site=False):
|
|
151
151
|
# increase contrast
|
152
152
|
raw = img_to_uint8(raw)
|
153
153
|
raw = contrast(raw)
|
154
|
+
zsh, ysh, xsh = raw.shape
|
154
155
|
|
155
156
|
# create slices for slice viewer
|
156
157
|
if not os.path.isdir(path_to_slices):
|
@@ -160,9 +161,9 @@ def create_slices(path_to_data, path_to_label, on_site=False):
|
|
160
161
|
os.chmod(path_to_slices, 0o770)
|
161
162
|
|
162
163
|
# save slices
|
163
|
-
for k in range(
|
164
|
+
for k in range(zsh):
|
164
165
|
im = Image.fromarray(raw[k])
|
165
|
-
im.save(path_to_slices +
|
166
|
+
im.save(path_to_slices + '/slice_' + str(k).zfill(len(str(zsh-1))) + '.png')
|
166
167
|
|
167
168
|
if path_to_label and not os.path.isdir(path_to_label_slices):
|
168
169
|
|
@@ -263,7 +264,7 @@ def create_slices(path_to_data, path_to_label, on_site=False):
|
|
263
264
|
|
264
265
|
# save slice
|
265
266
|
im = Image.fromarray(out)
|
266
|
-
im.save(path_to_label_slices +
|
267
|
+
im.save(path_to_label_slices + '/slice_' + str(k).zfill(len(str(zsh-1))) + '.png')
|
267
268
|
|
268
269
|
except Exception as e:
|
269
270
|
print(e)
|
@@ -45,6 +45,7 @@ from biomedisa.features.biomedisa_helper import (
|
|
45
45
|
img_resize, load_data, save_data, set_labels_to_zero, id_generator, unique_file_path)
|
46
46
|
from biomedisa.features.remove_outlier import clean, fill
|
47
47
|
from biomedisa.features.active_contour import activeContour
|
48
|
+
from tifffile import TiffFile, imread
|
48
49
|
import matplotlib.pyplot as plt
|
49
50
|
import SimpleITK as sitk
|
50
51
|
import tensorflow as tf
|
@@ -100,7 +101,7 @@ def save_history(history, path_to_model, val_dice, train_dice):
|
|
100
101
|
# save history dictonary
|
101
102
|
np.save(path_to_model.replace('.h5','.npy'), history)
|
102
103
|
|
103
|
-
def predict_blocksize(labelData, x_puffer, y_puffer, z_puffer):
|
104
|
+
def predict_blocksize(labelData, x_puffer=25, y_puffer=25, z_puffer=25):
|
104
105
|
zsh, ysh, xsh = labelData.shape
|
105
106
|
argmin_z, argmax_z, argmin_y, argmax_y, argmin_x, argmax_x = zsh, 0, ysh, 0, xsh, 0
|
106
107
|
for k in range(zsh):
|
@@ -121,7 +122,7 @@ def predict_blocksize(labelData, x_puffer, y_puffer, z_puffer):
|
|
121
122
|
argmax_z = argmax_z + z_puffer if argmax_z + z_puffer < zsh else zsh
|
122
123
|
return argmin_z,argmax_z,argmin_y,argmax_y,argmin_x,argmax_x
|
123
124
|
|
124
|
-
def
|
125
|
+
def set_image_dimensions(header, data):
|
125
126
|
|
126
127
|
# read header as string
|
127
128
|
b = header.tobytes()
|
@@ -148,7 +149,7 @@ def get_image_dimensions(header, data):
|
|
148
149
|
new_header = np.frombuffer(b2, dtype=header.dtype)
|
149
150
|
return new_header
|
150
151
|
|
151
|
-
def
|
152
|
+
def set_physical_size(header, img_header):
|
152
153
|
|
153
154
|
# read img_header as string
|
154
155
|
b = img_header.tobytes()
|
@@ -354,7 +355,7 @@ def read_img_list(img_list, label_list, temp_img_dir, temp_label_dir):
|
|
354
355
|
label_names.append(label_name)
|
355
356
|
return img_names, label_names
|
356
357
|
|
357
|
-
def load_training_data(normalize, img_list, label_list, channels, x_scale, y_scale, z_scale,
|
358
|
+
def load_training_data(normalize, img_list, label_list, channels, x_scale, y_scale, z_scale, scaling,
|
358
359
|
crop_data, labels_to_compute, labels_to_remove, img_in=None, label_in=None,
|
359
360
|
normalization_parameters=None, allLabels=None, header=None, extension='.tif',
|
360
361
|
x_puffer=25, y_puffer=25, z_puffer=25):
|
@@ -386,7 +387,7 @@ def load_training_data(normalize, img_list, label_list, channels, x_scale, y_sca
|
|
386
387
|
if crop_data:
|
387
388
|
argmin_z,argmax_z,argmin_y,argmax_y,argmin_x,argmax_x = predict_blocksize(label, x_puffer, y_puffer, z_puffer)
|
388
389
|
label = np.copy(label[argmin_z:argmax_z,argmin_y:argmax_y,argmin_x:argmax_x], order='C')
|
389
|
-
if
|
390
|
+
if scaling:
|
390
391
|
label = img_resize(label, z_scale, y_scale, x_scale, labels=True)
|
391
392
|
|
392
393
|
# if header is not single data stream Amira Mesh falling back to Multi-TIFF
|
@@ -412,7 +413,7 @@ def load_training_data(normalize, img_list, label_list, channels, x_scale, y_sca
|
|
412
413
|
else:
|
413
414
|
img = img_in
|
414
415
|
img_names = ['img_1']
|
415
|
-
if label_dim != img.shape:
|
416
|
+
if label_dim != img.shape[:3]:
|
416
417
|
InputError.message = f'Dimensions of "{os.path.basename(img_names[0])}" and "{os.path.basename(label_names[0])}" do not match'
|
417
418
|
raise InputError()
|
418
419
|
|
@@ -432,7 +433,7 @@ def load_training_data(normalize, img_list, label_list, channels, x_scale, y_sca
|
|
432
433
|
|
433
434
|
# scale/resize image data
|
434
435
|
img = img.astype(np.float32)
|
435
|
-
if
|
436
|
+
if scaling:
|
436
437
|
img = img_resize(img, z_scale, y_scale, x_scale)
|
437
438
|
|
438
439
|
# normalize image data
|
@@ -469,7 +470,7 @@ def load_training_data(normalize, img_list, label_list, channels, x_scale, y_sca
|
|
469
470
|
if crop_data:
|
470
471
|
argmin_z,argmax_z,argmin_y,argmax_y,argmin_x,argmax_x = predict_blocksize(a, x_puffer, y_puffer, z_puffer)
|
471
472
|
a = np.copy(a[argmin_z:argmax_z,argmin_y:argmax_y,argmin_x:argmax_x], order='C')
|
472
|
-
if
|
473
|
+
if scaling:
|
473
474
|
a = img_resize(a, z_scale, y_scale, x_scale, labels=True)
|
474
475
|
label = np.append(label, a, axis=0)
|
475
476
|
|
@@ -481,7 +482,7 @@ def load_training_data(normalize, img_list, label_list, channels, x_scale, y_sca
|
|
481
482
|
raise InputError()
|
482
483
|
else:
|
483
484
|
a = img_in[k]
|
484
|
-
if label_dim != a.shape:
|
485
|
+
if label_dim != a.shape[:3]:
|
485
486
|
InputError.message = f'Dimensions of "{os.path.basename(img_names[k])}" and "{os.path.basename(label_names[k])}" do not match'
|
486
487
|
raise InputError()
|
487
488
|
if len(a.shape)==3:
|
@@ -493,7 +494,7 @@ def load_training_data(normalize, img_list, label_list, channels, x_scale, y_sca
|
|
493
494
|
if crop_data:
|
494
495
|
a = np.copy(a[argmin_z:argmax_z,argmin_y:argmax_y,argmin_x:argmax_x], order='C')
|
495
496
|
a = a.astype(np.float32)
|
496
|
-
if
|
497
|
+
if scaling:
|
497
498
|
a = img_resize(a, z_scale, y_scale, x_scale)
|
498
499
|
for c in range(channels):
|
499
500
|
a[:,:,:,c] -= np.amin(a[:,:,:,c])
|
@@ -541,18 +542,17 @@ class CustomCallback(Callback):
|
|
541
542
|
time_remaining = str(t // 60) + 'min'
|
542
543
|
else:
|
543
544
|
time_remaining = str(t // 3600) + 'h ' + str((t % 3600) // 60) + 'min'
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
image.message = 'Progress {}%, {} remaining'.format(percentage,time_remaining)
|
545
|
+
image.message = 'Progress {}%, {} remaining'.format(percentage,time_remaining)
|
546
|
+
if 'best_val_dice' in logs:
|
547
|
+
best_val_dice = round(float(logs['best_val_dice'])*100,1)
|
548
|
+
image.message += f', {best_val_dice}% accuracy'
|
549
549
|
image.save()
|
550
550
|
print("Start epoch {} of training; got log keys: {}".format(epoch, keys))
|
551
551
|
|
552
552
|
class MetaData(Callback):
|
553
553
|
def __init__(self, path_to_model, configuration_data, allLabels,
|
554
554
|
extension, header, crop_data, cropping_weights, cropping_config,
|
555
|
-
normalization_parameters, cropping_norm):
|
555
|
+
normalization_parameters, cropping_norm, patch_normalization, scaling):
|
556
556
|
|
557
557
|
self.path_to_model = path_to_model
|
558
558
|
self.configuration_data = configuration_data
|
@@ -564,6 +564,8 @@ class MetaData(Callback):
|
|
564
564
|
self.cropping_weights = cropping_weights
|
565
565
|
self.cropping_config = cropping_config
|
566
566
|
self.cropping_norm = cropping_norm
|
567
|
+
self.patch_normalization = patch_normalization
|
568
|
+
self.scaling = scaling
|
567
569
|
|
568
570
|
def on_epoch_end(self, epoch, logs={}):
|
569
571
|
hf = h5py.File(self.path_to_model, 'r')
|
@@ -574,6 +576,8 @@ class MetaData(Callback):
|
|
574
576
|
group.create_dataset('configuration', data=self.configuration_data)
|
575
577
|
group.create_dataset('normalization', data=self.normalization_parameters)
|
576
578
|
group.create_dataset('labels', data=self.allLabels)
|
579
|
+
group.create_dataset('patch_normalization', data=int(self.patch_normalization))
|
580
|
+
group.create_dataset('scaling', data=int(self.scaling))
|
577
581
|
if self.extension == '.am':
|
578
582
|
group.create_dataset('extension', data=self.extension)
|
579
583
|
group.create_dataset('header', data=self.header)
|
@@ -758,8 +762,8 @@ def dice_coef_loss(nb_labels):
|
|
758
762
|
for index in range(1,nb_labels):
|
759
763
|
dice += dice_coef(y_true[:,:,:,:,index], y_pred[:,:,:,:,index])
|
760
764
|
dice = dice / (nb_labels-1)
|
761
|
-
loss = -K.log(dice)
|
762
|
-
|
765
|
+
#loss = -K.log(dice)
|
766
|
+
loss = 1 - dice
|
763
767
|
return loss
|
764
768
|
return loss_fn
|
765
769
|
|
@@ -772,7 +776,7 @@ def train_semantic_segmentation(bm,
|
|
772
776
|
|
773
777
|
# training data
|
774
778
|
img, label, allLabels, normalization_parameters, header, extension, bm.channels = load_training_data(bm.normalize,
|
775
|
-
img_list, label_list, None, bm.x_scale, bm.y_scale, bm.z_scale, bm.
|
779
|
+
img_list, label_list, None, bm.x_scale, bm.y_scale, bm.z_scale, bm.scaling, bm.crop_data,
|
776
780
|
bm.only, bm.ignore, img, label, None, None, header, extension)
|
777
781
|
|
778
782
|
# configuration data
|
@@ -784,7 +788,7 @@ def train_semantic_segmentation(bm,
|
|
784
788
|
# validation data
|
785
789
|
if any(val_img_list) or img_val is not None:
|
786
790
|
img_val, label_val, _, _, _, _, _ = load_training_data(bm.normalize,
|
787
|
-
val_img_list, val_label_list, bm.channels, bm.x_scale, bm.y_scale, bm.z_scale, bm.
|
791
|
+
val_img_list, val_label_list, bm.channels, bm.x_scale, bm.y_scale, bm.z_scale, bm.scaling, bm.crop_data,
|
788
792
|
bm.only, bm.ignore, img_val, label_val, normalization_parameters, allLabels)
|
789
793
|
|
790
794
|
elif bm.validation_split:
|
@@ -908,7 +912,7 @@ def train_semantic_segmentation(bm,
|
|
908
912
|
# save meta data
|
909
913
|
meta_data = MetaData(bm.path_to_model, configuration_data, allLabels,
|
910
914
|
extension, header, bm.crop_data, bm.cropping_weights, bm.cropping_config,
|
911
|
-
normalization_parameters, bm.cropping_norm)
|
915
|
+
normalization_parameters, bm.cropping_norm, bm.patch_normalization, bm.scaling)
|
912
916
|
|
913
917
|
# model checkpoint
|
914
918
|
if img_val is not None:
|
@@ -946,22 +950,29 @@ def train_semantic_segmentation(bm,
|
|
946
950
|
if img_val is not None and not bm.val_dice:
|
947
951
|
save_history(history.history, bm.path_to_model, False, bm.train_dice)
|
948
952
|
|
949
|
-
def load_prediction_data(
|
950
|
-
|
951
|
-
|
953
|
+
def load_prediction_data(bm, channels, normalize, normalization_parameters,
|
954
|
+
region_of_interest, img, img_header, load_blockwise=False, z=None):
|
955
|
+
|
952
956
|
# read image data
|
953
957
|
if img is None:
|
954
|
-
|
958
|
+
if load_blockwise:
|
959
|
+
img_header = None
|
960
|
+
tif = TiffFile(bm.path_to_image)
|
961
|
+
img = imread(bm.path_to_image, key=range(z,min(len(tif.pages),z+bm.z_patch)))
|
962
|
+
else:
|
963
|
+
img, img_header = load_data(bm.path_to_image, 'first_queue')
|
955
964
|
|
956
965
|
# verify validity
|
957
966
|
if img is None:
|
958
|
-
InputError.message = f'Invalid image data: {os.path.basename(
|
967
|
+
InputError.message = f'Invalid image data: {os.path.basename(bm.path_to_image)}.'
|
959
968
|
raise InputError()
|
960
969
|
|
961
|
-
# preserve original image data
|
962
|
-
img_data =
|
970
|
+
# preserve original image data for post-processing
|
971
|
+
img_data = None
|
972
|
+
if bm.acwe:
|
973
|
+
img_data = img.copy()
|
963
974
|
|
964
|
-
# handle all images
|
975
|
+
# handle all images using number of channels >=1
|
965
976
|
if len(img.shape)==3:
|
966
977
|
z_shape, y_shape, x_shape = img.shape
|
967
978
|
img = img.reshape(z_shape, y_shape, x_shape, 1)
|
@@ -969,7 +980,7 @@ def load_prediction_data(path_to_img, channels, x_scale, y_scale, z_scale,
|
|
969
980
|
InputError.message = f'Number of channels must be {channels}.'
|
970
981
|
raise InputError()
|
971
982
|
|
972
|
-
# image shape
|
983
|
+
# original image shape
|
973
984
|
z_shape, y_shape, x_shape, _ = img.shape
|
974
985
|
|
975
986
|
# automatic cropping of image to region of interest
|
@@ -981,8 +992,8 @@ def load_prediction_data(path_to_img, channels, x_scale, y_scale, z_scale,
|
|
981
992
|
|
982
993
|
# scale/resize image data
|
983
994
|
img = img.astype(np.float32)
|
984
|
-
if
|
985
|
-
img = img_resize(img, z_scale, y_scale, x_scale)
|
995
|
+
if bm.scaling:
|
996
|
+
img = img_resize(img, bm.z_scale, bm.y_scale, bm.x_scale)
|
986
997
|
|
987
998
|
# normalize image data
|
988
999
|
for c in range(channels):
|
@@ -1000,94 +1011,255 @@ def load_prediction_data(path_to_img, channels, x_scale, y_scale, z_scale,
|
|
1000
1011
|
|
1001
1012
|
return img, img_header, z_shape, y_shape, x_shape, region_of_interest, img_data
|
1002
1013
|
|
1003
|
-
def
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1014
|
+
def append_ghost_areas(bm, img):
|
1015
|
+
# append ghost areas to make image dimensions divisible by patch size (mirror edge areas)
|
1016
|
+
zsh, ysh, xsh, _ = img.shape
|
1017
|
+
z_rest = bm.z_patch - (zsh % bm.z_patch)
|
1018
|
+
if z_rest == bm.z_patch:
|
1019
|
+
z_rest = -zsh
|
1020
|
+
else:
|
1021
|
+
img = np.append(img, img[-z_rest:][::-1], axis=0)
|
1022
|
+
y_rest = bm.y_patch - (ysh % bm.y_patch)
|
1023
|
+
if y_rest == bm.y_patch:
|
1024
|
+
y_rest = -ysh
|
1025
|
+
else:
|
1026
|
+
img = np.append(img, img[:,-y_rest:][:,::-1], axis=1)
|
1027
|
+
x_rest = bm.x_patch - (xsh % bm.x_patch)
|
1028
|
+
if x_rest == bm.x_patch:
|
1029
|
+
x_rest = -xsh
|
1030
|
+
else:
|
1031
|
+
img = np.append(img, img[:,:,-x_rest:][:,:,::-1], axis=2)
|
1032
|
+
return img, z_rest, y_rest, x_rest
|
1007
1033
|
|
1008
|
-
|
1034
|
+
def predict_semantic_segmentation(bm,
|
1035
|
+
header, img_header, allLabels,
|
1036
|
+
region_of_interest, extension, img_data,
|
1037
|
+
channels, normalization_parameters):
|
1009
1038
|
|
1010
|
-
#
|
1011
|
-
|
1039
|
+
# initialize results
|
1040
|
+
results = {}
|
1012
1041
|
|
1013
1042
|
# number of labels
|
1014
1043
|
nb_labels = len(allLabels)
|
1015
1044
|
|
1016
|
-
#
|
1017
|
-
|
1045
|
+
# load model
|
1046
|
+
if bm.dice_loss:
|
1047
|
+
def loss_fn(y_true, y_pred):
|
1048
|
+
dice = 0
|
1049
|
+
for index in range(1, nb_labels):
|
1050
|
+
dice += dice_coef(y_true[:,:,:,:,index], y_pred[:,:,:,:,index])
|
1051
|
+
dice = dice / (nb_labels-1)
|
1052
|
+
#loss = -K.log(dice)
|
1053
|
+
loss = 1 - dice
|
1054
|
+
return loss
|
1055
|
+
custom_objects = {'dice_coef_loss': dice_coef_loss,'loss_fn': loss_fn}
|
1056
|
+
model = load_model(bm.path_to_model, custom_objects=custom_objects)
|
1057
|
+
else:
|
1058
|
+
model = load_model(bm.path_to_model)
|
1059
|
+
|
1060
|
+
# check if data can be loaded blockwise to save host memory
|
1061
|
+
load_blockwise = False
|
1062
|
+
if not bm.scaling and not bm.normalize and bm.path_to_image and not np.any(region_of_interest) and \
|
1063
|
+
os.path.splitext(bm.path_to_image)[1] in ['.tif', '.tiff'] and not bm.acwe:
|
1064
|
+
tif = TiffFile(bm.path_to_image)
|
1065
|
+
zsh = len(tif.pages)
|
1066
|
+
ysh, xsh = tif.pages[0].shape
|
1067
|
+
|
1068
|
+
# determine new image size after appending ghost areas to make image dimensions divisible by patch size
|
1069
|
+
z_rest = bm.z_patch - (zsh % bm.z_patch)
|
1070
|
+
if z_rest == bm.z_patch:
|
1071
|
+
z_rest = -zsh
|
1072
|
+
else:
|
1073
|
+
zsh += z_rest
|
1074
|
+
y_rest = bm.y_patch - (ysh % bm.y_patch)
|
1075
|
+
if y_rest == bm.y_patch:
|
1076
|
+
y_rest = -ysh
|
1077
|
+
else:
|
1078
|
+
ysh += y_rest
|
1079
|
+
x_rest = bm.x_patch - (xsh % bm.x_patch)
|
1080
|
+
if x_rest == bm.x_patch:
|
1081
|
+
x_rest = -xsh
|
1082
|
+
else:
|
1083
|
+
xsh += x_rest
|
1018
1084
|
|
1019
|
-
|
1020
|
-
|
1021
|
-
for
|
1022
|
-
for
|
1023
|
-
|
1085
|
+
# get Ids of patches
|
1086
|
+
list_IDs = []
|
1087
|
+
for k in range(0, zsh-bm.z_patch+1, bm.stride_size):
|
1088
|
+
for l in range(0, ysh-bm.y_patch+1, bm.stride_size):
|
1089
|
+
for m in range(0, xsh-bm.x_patch+1, bm.stride_size):
|
1090
|
+
list_IDs.append(k*ysh*xsh+l*xsh+m)
|
1024
1091
|
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1092
|
+
# make length of list divisible by batch size
|
1093
|
+
rest = bm.batch_size - (len(list_IDs) % bm.batch_size)
|
1094
|
+
list_IDs = list_IDs + list_IDs[:rest]
|
1028
1095
|
|
1029
|
-
|
1030
|
-
|
1096
|
+
# prediction
|
1097
|
+
if len(list_IDs) > 400:
|
1098
|
+
load_blockwise = True
|
1031
1099
|
|
1032
|
-
#
|
1033
|
-
|
1034
|
-
'dim_img': (zsh, ysh, xsh),
|
1035
|
-
'batch_size': batch_size,
|
1036
|
-
'n_channels': csh,
|
1037
|
-
'patch_normalization': bm.patch_normalization}
|
1100
|
+
# load image data and calculate patch IDs
|
1101
|
+
if not load_blockwise:
|
1038
1102
|
|
1039
|
-
|
1040
|
-
|
1103
|
+
# load prediction data
|
1104
|
+
img, img_header, z_shape, y_shape, x_shape, region_of_interest, img_data = load_prediction_data(
|
1105
|
+
bm, channels, bm.normalize, normalization_parameters, region_of_interest, img_data, img_header)
|
1041
1106
|
|
1042
|
-
|
1043
|
-
|
1107
|
+
# append ghost areas
|
1108
|
+
img, z_rest, y_rest, x_rest = append_ghost_areas(bm, img)
|
1109
|
+
|
1110
|
+
# img shape
|
1111
|
+
zsh, ysh, xsh, _ = img.shape
|
1112
|
+
|
1113
|
+
# list of IDs
|
1114
|
+
list_IDs = []
|
1115
|
+
|
1116
|
+
# get Ids of patches
|
1117
|
+
for k in range(0, zsh-bm.z_patch+1, bm.stride_size):
|
1118
|
+
for l in range(0, ysh-bm.y_patch+1, bm.stride_size):
|
1119
|
+
for m in range(0, xsh-bm.x_patch+1, bm.stride_size):
|
1120
|
+
list_IDs.append(k*ysh*xsh+l*xsh+m)
|
1121
|
+
|
1122
|
+
# make length of list divisible by batch size
|
1123
|
+
rest = bm.batch_size - (len(list_IDs) % bm.batch_size)
|
1124
|
+
list_IDs = list_IDs + list_IDs[:rest]
|
1125
|
+
|
1126
|
+
# number of patches
|
1127
|
+
nb_patches = len(list_IDs)
|
1128
|
+
|
1129
|
+
# load all patches on GPU memory
|
1130
|
+
if not load_blockwise and nb_patches < 400:
|
1131
|
+
|
1132
|
+
# parameters
|
1133
|
+
params = {'dim': (bm.z_patch, bm.y_patch, bm.x_patch),
|
1134
|
+
'dim_img': (zsh, ysh, xsh),
|
1135
|
+
'batch_size': bm.batch_size,
|
1136
|
+
'n_channels': channels,
|
1137
|
+
'patch_normalization': bm.patch_normalization}
|
1044
1138
|
|
1045
|
-
|
1046
|
-
|
1139
|
+
# data generator
|
1140
|
+
predict_generator = PredictDataGenerator(img, list_IDs, **params)
|
1141
|
+
|
1142
|
+
# predict probabilities
|
1047
1143
|
probabilities = model.predict(predict_generator, verbose=0, steps=None)
|
1144
|
+
|
1145
|
+
# create final
|
1146
|
+
final = np.zeros((zsh, ysh, xsh, nb_labels), dtype=np.float32)
|
1147
|
+
nb = 0
|
1148
|
+
for k in range(0, zsh-bm.z_patch+1, bm.stride_size):
|
1149
|
+
for l in range(0, ysh-bm.y_patch+1, bm.stride_size):
|
1150
|
+
for m in range(0, xsh-bm.x_patch+1, bm.stride_size):
|
1151
|
+
final[k:k+bm.z_patch, l:l+bm.y_patch, m:m+bm.x_patch] += probabilities[nb]
|
1152
|
+
nb += 1
|
1153
|
+
|
1154
|
+
# calculate result
|
1155
|
+
label = np.argmax(final, axis=-1).astype(np.uint8)
|
1156
|
+
|
1048
1157
|
else:
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
#
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1158
|
+
# stream data batchwise to GPU to reduce memory usage
|
1159
|
+
X = np.empty((bm.batch_size, bm.z_patch, bm.y_patch, bm.x_patch, channels), dtype=np.float32)
|
1160
|
+
|
1161
|
+
# allocate final array
|
1162
|
+
if bm.return_probs:
|
1163
|
+
final = np.zeros((zsh, ysh, xsh, nb_labels), dtype=np.float32)
|
1164
|
+
|
1165
|
+
# allocate result array
|
1166
|
+
label = np.zeros((zsh, ysh, xsh), dtype=np.uint8)
|
1167
|
+
|
1168
|
+
# predict segmentation block by block
|
1169
|
+
z_indices = range(0, zsh-bm.z_patch+1, bm.stride_size)
|
1170
|
+
for j, z in enumerate(z_indices):
|
1171
|
+
|
1172
|
+
# load blockwise
|
1173
|
+
if load_blockwise:
|
1174
|
+
img, _, _, _, _, _, _ = load_prediction_data(bm,
|
1175
|
+
channels, bm.normalize, normalization_parameters,
|
1176
|
+
region_of_interest, img_data, img_header, load_blockwise, z)
|
1177
|
+
img, _, _, _ = append_ghost_areas(bm, img)
|
1178
|
+
|
1179
|
+
# list of IDs
|
1180
|
+
list_IDs = []
|
1181
|
+
|
1182
|
+
# get Ids of patches
|
1183
|
+
for l in range(0, ysh-bm.y_patch+1, bm.stride_size):
|
1184
|
+
for m in range(0, xsh-bm.x_patch+1, bm.stride_size):
|
1185
|
+
list_IDs.append(z*ysh*xsh+l*xsh+m)
|
1186
|
+
|
1187
|
+
# make length of list divisible by batch size
|
1188
|
+
max_i = len(list_IDs)
|
1189
|
+
rest = bm.batch_size - (len(list_IDs) % bm.batch_size)
|
1190
|
+
list_IDs = list_IDs + list_IDs[:rest]
|
1191
|
+
|
1192
|
+
# number of patches
|
1193
|
+
nb_patches = len(list_IDs)
|
1194
|
+
|
1195
|
+
# allocate tmp probabilities array
|
1196
|
+
probs = np.zeros((bm.z_patch, ysh, xsh, nb_labels), dtype=np.float32)
|
1197
|
+
|
1198
|
+
# get one batch of image patches
|
1199
|
+
for step in range(nb_patches//bm.batch_size):
|
1200
|
+
for i, ID in enumerate(list_IDs[step*bm.batch_size:(step+1)*bm.batch_size]):
|
1201
|
+
|
1202
|
+
# get patch indices
|
1203
|
+
k=0 if load_blockwise else ID // (ysh*xsh)
|
1204
|
+
rest = ID % (ysh*xsh)
|
1205
|
+
l = rest // xsh
|
1206
|
+
m = rest % xsh
|
1207
|
+
|
1208
|
+
# get patch
|
1209
|
+
tmp_X = img[k:k+bm.z_patch,l:l+bm.y_patch,m:m+bm.x_patch]
|
1210
|
+
if bm.patch_normalization:
|
1211
|
+
tmp_X = np.copy(tmp_X, order='C')
|
1212
|
+
for c in range(channels):
|
1213
|
+
tmp_X[:,:,:,c] -= np.mean(tmp_X[:,:,:,c])
|
1214
|
+
tmp_X[:,:,:,c] /= max(np.std(tmp_X[:,:,:,c]), 1e-6)
|
1215
|
+
X[i] = tmp_X
|
1216
|
+
|
1217
|
+
# predict batch
|
1218
|
+
Y = model.predict(X, verbose=0, steps=None, batch_size=bm.batch_size)
|
1219
|
+
|
1220
|
+
# loop over result patches
|
1221
|
+
for i, ID in enumerate(list_IDs[step*bm.batch_size:(step+1)*bm.batch_size]):
|
1222
|
+
rest = ID % (ysh*xsh)
|
1223
|
+
l = rest // xsh
|
1224
|
+
m = rest % xsh
|
1225
|
+
if i < max_i:
|
1226
|
+
probs[:,l:l+bm.y_patch,m:m+bm.x_patch] += Y[i]
|
1227
|
+
|
1228
|
+
# overlap in z direction
|
1229
|
+
if bm.stride_size < bm.z_patch:
|
1230
|
+
if j>0:
|
1231
|
+
probs[:bm.stride_size] += overlap
|
1232
|
+
overlap = probs[bm.stride_size:].copy()
|
1233
|
+
|
1234
|
+
# calculate result
|
1235
|
+
if z==z_indices[-1]:
|
1236
|
+
label[z:z+bm.z_patch] = np.argmax(probs, axis=-1).astype(np.uint8)
|
1082
1237
|
if bm.return_probs:
|
1083
|
-
|
1084
|
-
|
1238
|
+
final[z:z+bm.z_patch] = probs
|
1239
|
+
else:
|
1240
|
+
block_zsh = min(bm.stride_size, bm.z_patch)
|
1241
|
+
label[z:z+block_zsh] = np.argmax(probs[:block_zsh], axis=-1).astype(np.uint8)
|
1242
|
+
if bm.return_probs:
|
1243
|
+
final[z:z+block_zsh] = probs[:block_zsh]
|
1244
|
+
|
1245
|
+
# remove appendix
|
1246
|
+
if bm.return_probs:
|
1247
|
+
final = final[:-z_rest,:-y_rest,:-x_rest]
|
1248
|
+
label = label[:-z_rest,:-y_rest,:-x_rest]
|
1249
|
+
zsh, ysh, xsh = label.shape
|
1085
1250
|
|
1086
1251
|
# return probabilities
|
1087
1252
|
if bm.return_probs:
|
1253
|
+
counter = np.zeros((zsh, ysh, xsh, nb_labels), dtype=np.float32)
|
1254
|
+
nb = 0
|
1255
|
+
for k in range(0, zsh-bm.z_patch+1, bm.stride_size):
|
1256
|
+
for l in range(0, ysh-bm.y_patch+1, bm.stride_size):
|
1257
|
+
for m in range(0, xsh-bm.x_patch+1, bm.stride_size):
|
1258
|
+
counter[k:k+bm.z_patch, l:l+bm.y_patch, m:m+bm.x_patch] += 1
|
1259
|
+
nb += 1
|
1088
1260
|
counter[counter==0] = 1
|
1089
1261
|
probabilities = final / counter
|
1090
|
-
if
|
1262
|
+
if bm.scaling:
|
1091
1263
|
probabilities = img_resize(probabilities, z_shape, y_shape, x_shape)
|
1092
1264
|
if np.any(region_of_interest):
|
1093
1265
|
min_z,max_z,min_y,max_y,min_x,max_x,original_zsh,original_ysh,original_xsh = region_of_interest[:]
|
@@ -1096,12 +1268,8 @@ def predict_semantic_segmentation(bm, img, path_to_model,
|
|
1096
1268
|
probabilities = np.copy(tmp, order='C')
|
1097
1269
|
results['probs'] = probabilities
|
1098
1270
|
|
1099
|
-
# get final
|
1100
|
-
label = np.argmax(final, axis=3)
|
1101
|
-
label = label.astype(np.uint8)
|
1102
|
-
|
1103
1271
|
# rescale final to input size
|
1104
|
-
if
|
1272
|
+
if bm.scaling:
|
1105
1273
|
label = img_resize(label, z_shape, y_shape, x_shape, labels=True)
|
1106
1274
|
|
1107
1275
|
# revert automatic cropping
|
@@ -1130,10 +1298,10 @@ def predict_semantic_segmentation(bm, img, path_to_model,
|
|
1130
1298
|
# handle amira header
|
1131
1299
|
if header is not None:
|
1132
1300
|
if extension == '.am':
|
1133
|
-
header =
|
1301
|
+
header = set_image_dimensions(header[0], label)
|
1134
1302
|
if img_header is not None:
|
1135
1303
|
try:
|
1136
|
-
header =
|
1304
|
+
header = set_physical_size(header, img_header[0])
|
1137
1305
|
except:
|
1138
1306
|
pass
|
1139
1307
|
header = [header]
|
@@ -1151,7 +1319,7 @@ def predict_semantic_segmentation(bm, img, path_to_model,
|
|
1151
1319
|
|
1152
1320
|
# save result
|
1153
1321
|
if bm.path_to_image:
|
1154
|
-
save_data(bm.path_to_final, label, header=header, compress=
|
1322
|
+
save_data(bm.path_to_final, label, header=header, compress=bm.compression)
|
1155
1323
|
|
1156
1324
|
# paths to optional results
|
1157
1325
|
filename, extension = os.path.splitext(bm.path_to_final)
|
@@ -1169,17 +1337,17 @@ def predict_semantic_segmentation(bm, img, path_to_model,
|
|
1169
1337
|
cleaned_result = clean(label, bm.clean)
|
1170
1338
|
results['cleaned'] = cleaned_result
|
1171
1339
|
if bm.path_to_image:
|
1172
|
-
save_data(path_to_cleaned, cleaned_result, header=header, compress=
|
1340
|
+
save_data(path_to_cleaned, cleaned_result, header=header, compress=bm.compression)
|
1173
1341
|
if bm.fill:
|
1174
1342
|
filled_result = clean(label, bm.fill)
|
1175
1343
|
results['filled'] = filled_result
|
1176
1344
|
if bm.path_to_image:
|
1177
|
-
save_data(path_to_filled, filled_result, header=header, compress=
|
1345
|
+
save_data(path_to_filled, filled_result, header=header, compress=bm.compression)
|
1178
1346
|
if bm.clean and bm.fill:
|
1179
1347
|
cleaned_filled_result = cleaned_result + (filled_result - label)
|
1180
1348
|
results['cleaned_filled'] = cleaned_filled_result
|
1181
1349
|
if bm.path_to_image:
|
1182
|
-
save_data(path_to_cleaned_filled, cleaned_filled_result, header=header, compress=
|
1350
|
+
save_data(path_to_cleaned_filled, cleaned_filled_result, header=header, compress=bm.compression)
|
1183
1351
|
|
1184
1352
|
# post-processing with active contour
|
1185
1353
|
if bm.acwe:
|
@@ -1188,8 +1356,8 @@ def predict_semantic_segmentation(bm, img, path_to_model,
|
|
1188
1356
|
results['acwe'] = acwe_result
|
1189
1357
|
results['refined'] = refined_result
|
1190
1358
|
if bm.path_to_image:
|
1191
|
-
save_data(path_to_acwe, acwe_result, header=header, compress=
|
1192
|
-
save_data(path_to_refined, refined_result, header=header, compress=
|
1359
|
+
save_data(path_to_acwe, acwe_result, header=header, compress=bm.compression)
|
1360
|
+
save_data(path_to_refined, refined_result, header=header, compress=bm.compression)
|
1193
1361
|
|
1194
1362
|
return results, bm
|
1195
1363
|
|
@@ -143,7 +143,7 @@ def fill(image, threshold=0.9):
|
|
143
143
|
return image_i
|
144
144
|
|
145
145
|
def main_helper(path_to_labels, img_id=None, friend_id=None, fill_holes=True,
|
146
|
-
clean_threshold=0.1, fill_threshold=0.9, remote=False,
|
146
|
+
clean_threshold=0.1, fill_threshold=0.9, remote=False, compression=True):
|
147
147
|
|
148
148
|
# django environment
|
149
149
|
if img_id is not None:
|
@@ -151,12 +151,6 @@ def main_helper(path_to_labels, img_id=None, friend_id=None, fill_holes=True,
|
|
151
151
|
else:
|
152
152
|
django_env = False
|
153
153
|
|
154
|
-
# compression
|
155
|
-
if no_compression:
|
156
|
-
compression = False
|
157
|
-
else:
|
158
|
-
compression = True
|
159
|
-
|
160
154
|
# final filenames
|
161
155
|
filename, extension = os.path.splitext(path_to_labels)
|
162
156
|
if extension == '.gz':
|
@@ -350,7 +344,7 @@ def init_remove_outlier(image_id, final_id, label_id, fill_holes=True):
|
|
350
344
|
try:
|
351
345
|
main_helper(final.pic.path, img_id=image_id, friend_id=final.friend,
|
352
346
|
fill_holes=fill_holes, clean_threshold=label.delete_outliers, fill_threshold=label.fill_holes, remote=False,
|
353
|
-
|
347
|
+
compression=label.compression)
|
354
348
|
except Exception as e:
|
355
349
|
print(traceback.format_exc())
|
356
350
|
|
@@ -377,7 +371,7 @@ if __name__ == '__main__':
|
|
377
371
|
help='Remove outliers, e.g. 0.5 means that objects smaller than 50 percent of the size of the largest object will be removed')
|
378
372
|
parser.add_argument('-f', '--fill_threshold', type=float, default=0.9,
|
379
373
|
help='Fill holes, e.g. 0.5 means that all holes smaller than 50 percent of the entire label will be filled')
|
380
|
-
parser.add_argument('-nc', '--
|
374
|
+
parser.add_argument('-nc', '--no-compression', dest='compression', action='store_false',
|
381
375
|
help='Disable compression of segmentation results')
|
382
376
|
parser.add_argument('-iid','--img_id', type=str, default=None,
|
383
377
|
help='Image ID within django environment/browser version')
|
biomedisa/interpolation.py
CHANGED
@@ -42,7 +42,7 @@ class Biomedisa(object):
|
|
42
42
|
|
43
43
|
def smart_interpolation(data, labelData, nbrw=10, sorw=4000, acwe=False, acwe_alpha=1.0, acwe_smooth=1, acwe_steps=3,
|
44
44
|
path_to_data=None, path_to_labels=None, denoise=False, uncertainty=False, platform=None,
|
45
|
-
allaxis=False, ignore='none', only='all', smooth=0,
|
45
|
+
allaxis=False, ignore='none', only='all', smooth=0, compression=True, return_hits=False,
|
46
46
|
img_id=None, label_id=None, remote=False, queue=0, clean=None, fill=None):
|
47
47
|
|
48
48
|
freeze_support()
|
@@ -73,12 +73,6 @@ def smart_interpolation(data, labelData, nbrw=10, sorw=4000, acwe=False, acwe_al
|
|
73
73
|
else:
|
74
74
|
bm.django_env = False
|
75
75
|
|
76
|
-
# compression
|
77
|
-
if no_compression:
|
78
|
-
bm.compression = False
|
79
|
-
else:
|
80
|
-
bm.compression = True
|
81
|
-
|
82
76
|
# disable file saving when called as a function
|
83
77
|
if bm.data is not None:
|
84
78
|
bm.path_to_data = None
|
@@ -149,19 +143,18 @@ def smart_interpolation(data, labelData, nbrw=10, sorw=4000, acwe=False, acwe_al
|
|
149
143
|
bm.path_to_acwe = filename + '.acwe' + bm.final_image_type
|
150
144
|
bm.path_to_uq = filename + '.uncertainty.tif'
|
151
145
|
|
152
|
-
# data
|
153
|
-
if bm.data.dtype == '
|
154
|
-
pass
|
155
|
-
elif bm.data.dtype == 'int8':
|
146
|
+
# data types
|
147
|
+
if bm.data.dtype == 'int8':
|
156
148
|
bm.data = bm.data.astype(np.int16)
|
157
149
|
bm.data += 128
|
158
150
|
bm.data = bm.data.astype(np.uint8)
|
159
|
-
|
160
|
-
bm.data = bm.data.astype(
|
151
|
+
elif bm.data.dtype != 'uint8':
|
152
|
+
bm.data = bm.data.astype(np.float32)
|
161
153
|
bm.data -= np.amin(bm.data)
|
162
154
|
bm.data /= np.amax(bm.data)
|
163
155
|
bm.data *= 255.0
|
164
|
-
|
156
|
+
if bm.labelData.dtype in ['uint32','int64','uint64']:
|
157
|
+
bm.labelData = bm.labelData.astype(np.int32)
|
165
158
|
|
166
159
|
# denoise image data
|
167
160
|
if bm.denoise:
|
@@ -332,7 +325,7 @@ if __name__ == '__main__':
|
|
332
325
|
help='Smoothing steps of active contour')
|
333
326
|
parser.add_argument('--acwe_steps', metavar='STEPS', type=int, default=3,
|
334
327
|
help='Iterations of active contour')
|
335
|
-
parser.add_argument('-nc', '--
|
328
|
+
parser.add_argument('-nc', '--no-compression', dest='compression', action='store_false',
|
336
329
|
help='Disable compression of segmentation results')
|
337
330
|
parser.add_argument('-allx', '--allaxis', action='store_true', default=False,
|
338
331
|
help='If pre-segmentation is not exlusively in xy-plane')
|
biomedisa/mesh.py
CHANGED
@@ -29,6 +29,7 @@
|
|
29
29
|
|
30
30
|
import os
|
31
31
|
import numpy as np
|
32
|
+
import biomedisa
|
32
33
|
from biomedisa.features.biomedisa_helper import load_data, unique_file_path
|
33
34
|
from biomedisa.features.django_env import create_pid_object
|
34
35
|
from vtk.util.numpy_support import vtk_to_numpy, numpy_to_vtk
|
@@ -167,8 +168,7 @@ def save_mesh(path_to_result, labels, x_res=1, y_res=1, z_res=1,
|
|
167
168
|
mesh_final.attr[start:stop,0] = i+1
|
168
169
|
mesh_final.save(path_to_result)
|
169
170
|
|
170
|
-
def get_voxel_spacing(header,
|
171
|
-
|
171
|
+
def get_voxel_spacing(header, extension):
|
172
172
|
if extension == '.am':
|
173
173
|
# read header as string
|
174
174
|
b = header[0].tobytes()
|
@@ -176,21 +176,23 @@ def get_voxel_spacing(header, data, extension):
|
|
176
176
|
s = b.decode("utf-8")
|
177
177
|
except:
|
178
178
|
s = b.decode("latin1")
|
179
|
-
|
180
179
|
# get physical size from image header
|
180
|
+
lattice = re.search('define Lattice (.*)\n', s)
|
181
181
|
bounding_box = re.search('BoundingBox (.*),\n', s)
|
182
|
-
if bounding_box:
|
182
|
+
if bounding_box and lattice:
|
183
|
+
# get number of voxels
|
184
|
+
lattice = lattice.group(1)
|
185
|
+
xsh, ysh, zsh = lattice.split(' ')
|
186
|
+
xsh, ysh, zsh = float(xsh), float(ysh), float(zsh)
|
187
|
+
# get bounding box
|
183
188
|
bounding_box = bounding_box.group(1)
|
184
189
|
i0, i1, i2, i3, i4, i5 = bounding_box.split(' ')
|
185
|
-
|
186
|
-
# voxel spacing
|
187
|
-
zsh, ysh, xsh = data.shape
|
190
|
+
# calculate voxel spacing
|
188
191
|
xres = (float(i1)-float(i0)) / xsh
|
189
192
|
yres = (float(i3)-float(i2)) / ysh
|
190
193
|
zres = (float(i5)-float(i4)) / zsh
|
191
194
|
else:
|
192
195
|
xres, yres, zres = 1, 1, 1
|
193
|
-
|
194
196
|
elif extension in ['.hdr', '.mhd', '.mha', '.nrrd', '.nii', '.nii.gz']:
|
195
197
|
xres, yres, zres = header.GetSpacing()
|
196
198
|
elif extension == '.zip':
|
@@ -203,7 +205,6 @@ def get_voxel_spacing(header, data, extension):
|
|
203
205
|
else:
|
204
206
|
print('Warning: could not get voxel spacing. Using x_spacing, y_spacing, z_spacing = 1, 1, 1 instead.')
|
205
207
|
xres, yres, zres = 1, 1, 1
|
206
|
-
|
207
208
|
return xres, yres, zres
|
208
209
|
|
209
210
|
def init_create_mesh(id):
|
@@ -324,7 +325,7 @@ def init_create_mesh(id):
|
|
324
325
|
log=1, imageType=None, shortfilename='Invalid label data.')
|
325
326
|
else:
|
326
327
|
# get voxel spacing
|
327
|
-
xres, yres, zres = get_voxel_spacing(header,
|
328
|
+
xres, yres, zres = get_voxel_spacing(header, extension)
|
328
329
|
print(f'Voxel spacing: x_spacing, y_spacing, z_spacing = {xres}, {yres}, {zres}')
|
329
330
|
|
330
331
|
# create stl file
|
@@ -386,7 +387,7 @@ if __name__ == "__main__":
|
|
386
387
|
|
387
388
|
# get voxel spacing
|
388
389
|
if not all([bm.x_res, bm.y_res, bm.z_res]):
|
389
|
-
x_res, y_res, z_res = get_voxel_spacing(header,
|
390
|
+
x_res, y_res, z_res = get_voxel_spacing(header, extension)
|
390
391
|
if not bm.x_res:
|
391
392
|
bm.x_res = x_res
|
392
393
|
if not bm.y_res:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: biomedisa
|
3
|
-
Version: 24.
|
3
|
+
Version: 24.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
|
@@ -33,18 +33,21 @@ License-File: LICENSE
|
|
33
33
|
- [License](#license)
|
34
34
|
|
35
35
|
## Overview
|
36
|
-
Biomedisa (https://biomedisa.info) is a free and easy-to-use open-source application for segmenting large volumetric images
|
36
|
+
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
37
|
|
38
38
|
## Hardware Requirements
|
39
39
|
+ One or more NVIDIA GPUs with compute capability 3.0 or higher or an Intel CPU
|
40
40
|
|
41
41
|
## Installation (command-line based)
|
42
42
|
+ [Ubuntu 22.04 + CUDA + GPU (recommended)](https://github.com/biomedisa/biomedisa/blob/master/README/ubuntu2204_cuda11.8_gpu_cli.md)
|
43
|
-
+ [Ubuntu 22.04 + OpenCL + CPU (
|
43
|
+
+ [Ubuntu 22.04 + OpenCL + CPU (very slow)](https://github.com/biomedisa/biomedisa/blob/master/README/ubuntu2204_opencl_cpu_cli.md)
|
44
44
|
+ [Windows 10 + CUDA + GPU (recommended)](https://github.com/biomedisa/biomedisa/blob/master/README/windows10_cuda_gpu_cli.md)
|
45
45
|
+ [Windows 10 + OpenCL + GPU (easy to install but lacks features like allaxis, smoothing, uncertainty, optimized GPU memory usage)](https://github.com/biomedisa/biomedisa/blob/master/README/windows10_opencl_gpu_cli.md)
|
46
46
|
+ [Windows 10 + OpenCL + CPU (very slow)](https://github.com/biomedisa/biomedisa/blob/master/README/windows10_opencl_cpu_cli.md)
|
47
47
|
|
48
|
+
## Installation (3D Slicer extension)
|
49
|
+
+ [Ubuntu 22.04 + CUDA + GPU](https://github.com/biomedisa/biomedisa/blob/master/README/ubuntu2204_cuda11.8_gpu_slicer.md)
|
50
|
+
|
48
51
|
## Installation (browser based)
|
49
52
|
+ [Ubuntu 22.04](https://github.com/biomedisa/biomedisa/blob/master/README/ubuntu2204_cuda11.8.md)
|
50
53
|
|
@@ -52,6 +55,9 @@ Biomedisa (https://biomedisa.info) is a free and easy-to-use open-source applica
|
|
52
55
|
+ Download test data from our [gallery](https://biomedisa.info/gallery/)
|
53
56
|
|
54
57
|
## Revisions
|
58
|
+
24.7.1
|
59
|
+
+ 3D Slicer extension
|
60
|
+
+ Prediction of large data block by block
|
55
61
|
24.5.22
|
56
62
|
+ Pip is the preferred installation method
|
57
63
|
+ Commands, module names and imports have been changed to conform to the Pip standard
|
@@ -131,15 +137,14 @@ deep_learning(img_data, label_data, train=True, batch_size=12,
|
|
131
137
|
```
|
132
138
|
|
133
139
|
#### Command-line based (training)
|
134
|
-
Start training with a batch size of 12:
|
135
140
|
```
|
136
|
-
python -m biomedisa.deeplearning C:\Users\%USERNAME%\Downloads\training_heart C:\Users\%USERNAME%\Downloads\training_heart_labels -t
|
141
|
+
python -m biomedisa.deeplearning C:\Users\%USERNAME%\Downloads\training_heart C:\Users\%USERNAME%\Downloads\training_heart_labels -t
|
137
142
|
```
|
138
143
|
Monitor training progress using validation data:
|
139
144
|
```
|
140
145
|
python -m biomedisa.deeplearning C:\Users\%USERNAME%\Downloads\training_heart C:\Users\%USERNAME%\Downloads\training_heart_labels -t -vi=C:\Users\%USERNAME%\Downloads\val_img -vl=C:\Users\%USERNAME%\Downloads\val_labels
|
141
146
|
```
|
142
|
-
If running into ResourceExhaustedError due to out of memory (OOM), try to use a smaller batch size.
|
147
|
+
If running into ResourceExhaustedError due to out of memory (OOM), try to use a smaller batch size (e.g. -bs=12).
|
143
148
|
|
144
149
|
#### Python example (prediction)
|
145
150
|
```python
|
@@ -175,7 +180,7 @@ from biomedisa.mesh import get_voxel_spacing, save_mesh
|
|
175
180
|
data, header, extension = load_data('final.Head5.am', return_extension=True)
|
176
181
|
|
177
182
|
# get voxel spacing
|
178
|
-
x_res, y_res, z_res = get_voxel_spacing(header,
|
183
|
+
x_res, y_res, z_res = get_voxel_spacing(header, extension)
|
179
184
|
print(f'Voxel spacing: x_spacing, y_spacing, z_spacing = {x_res}, {y_res}, {z_res}')
|
180
185
|
|
181
186
|
# save stl file
|
@@ -1,26 +1,26 @@
|
|
1
1
|
biomedisa/__init__.py,sha256=hw4mzEjGFXm-vxus2DBfKFW0nKoG0ibL5SH6ShfchrY,1526
|
2
2
|
biomedisa/__main__.py,sha256=a1--8vhtztWEloHVtbM43FZLCfrFo4BELgdsgtWE8ls,536
|
3
|
-
biomedisa/deeplearning.py,sha256=
|
4
|
-
biomedisa/interpolation.py,sha256=
|
5
|
-
biomedisa/mesh.py,sha256=
|
6
|
-
biomedisa/features/DataGenerator.py,sha256=
|
3
|
+
biomedisa/deeplearning.py,sha256=eVFoy3FsSb8h2hqFBdI8kz63tATLUvuMfSPzSa_wX5Q,27813
|
4
|
+
biomedisa/interpolation.py,sha256=tehhMhm3WEVzUpfAemujMixJMK6BMNLQ6uw7Z9TR9Go,17163
|
5
|
+
biomedisa/mesh.py,sha256=8-iuVsrfW5JovaMrAez7qSxv1LCU3eiqOdik0s0DV1w,16062
|
6
|
+
biomedisa/features/DataGenerator.py,sha256=MYPSY9-ssMwPx9UOI_ZfE7_5rddOSn4aHbVQ0HtYQVA,12757
|
7
7
|
biomedisa/features/DataGeneratorCrop.py,sha256=23R4Z-8tB1CsjYTYfhHGovlJpAny_q9OV9hq8kc2GJg,5454
|
8
8
|
biomedisa/features/PredictDataGenerator.py,sha256=JH8SPGQm-Y7_Drec2fw3jBUupvpIkQ1FvkDXP7mUjDY,4074
|
9
9
|
biomedisa/features/PredictDataGeneratorCrop.py,sha256=HF5tJbGtlJMHr7lMT9IiIdLG2CTjXstbKoOjlZJ93Is,3431
|
10
10
|
biomedisa/features/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
-
biomedisa/features/active_contour.py,sha256=
|
11
|
+
biomedisa/features/active_contour.py,sha256=FnPuvYck_KL4xYRFqwzm4Grdm288EdlcLFt88E0OtqA,17938
|
12
12
|
biomedisa/features/assd.py,sha256=q9NUQXEoA4Pi3d8b5fmys615CWu06Sm0N9-OGwJOFnw,6537
|
13
|
-
biomedisa/features/biomedisa_helper.py,sha256=
|
14
|
-
biomedisa/features/create_slices.py,sha256=
|
13
|
+
biomedisa/features/biomedisa_helper.py,sha256=d5wCtiP8pRTUbXv-jrN-b7cNStm8T1B_FtQR2Msd5OY,32286
|
14
|
+
biomedisa/features/create_slices.py,sha256=uSDH1OcEYc5BFPZHSy3UpS4P2DuoVnxOZ-l7wmyT_Po,13108
|
15
15
|
biomedisa/features/crop_helper.py,sha256=si72n9Q-C7U0cXYOD9Ux2UqIbZdXbZSOARBYDeqRggI,24533
|
16
16
|
biomedisa/features/curvop_numba.py,sha256=AjKQJcUBoURTB8pq1HmugQYpBwBELthhcEu51_r_xPI,7049
|
17
17
|
biomedisa/features/django_env.py,sha256=pdiPcBpqu1BWuyvh-palIGVwHFaY-leQ4Gatlbm8hIg,8942
|
18
|
-
biomedisa/features/keras_helper.py,sha256=
|
18
|
+
biomedisa/features/keras_helper.py,sha256=0rgMLD3mvLuV9PFbsOwypH4fr3IhPkQveCtUiqTCZdc,57270
|
19
19
|
biomedisa/features/nc_reader.py,sha256=RoRMwu3ELSNfoV3qZtaT2OWACnXb2EhNFu_DAF1T93o,7406
|
20
20
|
biomedisa/features/pid.py,sha256=Jmn1VIp0fBlgBrqZ-yUIQVVb5-NAxNBdibXALVr2PPI,2545
|
21
21
|
biomedisa/features/process_image.py,sha256=VtS3fGDvglqJiiJLPK1toe76J58j914NJ8XQKg3CRwo,11091
|
22
22
|
biomedisa/features/pycuda_test.py,sha256=UGAGIz_dgcCJkzjyCqnMlflp-WJPzpCtFQmE9C5DwIo,3275
|
23
|
-
biomedisa/features/remove_outlier.py,sha256=
|
23
|
+
biomedisa/features/remove_outlier.py,sha256=C2m-Wsw-n3q8Ft21SGNwEd3wDU_T1ghqQ5hldwU_rqI,16627
|
24
24
|
biomedisa/features/split_volume.py,sha256=UgMpHhZPvH90xFo-mJ0Oc0tBXbrf8FQF0kzVySAlO8g,8917
|
25
25
|
biomedisa/features/amira_to_np/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
26
26
|
biomedisa/features/amira_to_np/amira_data_stream.py,sha256=JrZTyKP01CKDFB5d9BlGtSFwBgoAo0AJeAmn3pADH88,32618
|
@@ -37,8 +37,8 @@ biomedisa/features/random_walk/pyopencl_large.py,sha256=q79AxG3p3qFjxfiAZfUK9I5B
|
|
37
37
|
biomedisa/features/random_walk/pyopencl_small.py,sha256=opNlS-qzOa9qWafBNJdvf6r1aRAFf7_JXf6ISDnkdXE,17068
|
38
38
|
biomedisa/features/random_walk/rw_large.py,sha256=ZnITvk00Y11ZZlGuBRaJO1EwU0wYBdEwdpj9vvXCqF4,19805
|
39
39
|
biomedisa/features/random_walk/rw_small.py,sha256=RPzZe24YrEwYelJukDjvqaoD_SyhgdriEi7uV3kZGXI,14881
|
40
|
-
biomedisa-24.
|
41
|
-
biomedisa-24.
|
42
|
-
biomedisa-24.
|
43
|
-
biomedisa-24.
|
44
|
-
biomedisa-24.
|
40
|
+
biomedisa-24.7.1.dist-info/LICENSE,sha256=sehayP6UhydNnmstfL4yFR3genMRdpuUh6uZVWJN1H0,14152
|
41
|
+
biomedisa-24.7.1.dist-info/METADATA,sha256=4ziBlBpd6JbYp9YSmxs0ptZpm_XX3J4pERQAL6Kg118,10631
|
42
|
+
biomedisa-24.7.1.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
|
43
|
+
biomedisa-24.7.1.dist-info/top_level.txt,sha256=opsf1Eb4vCguPSxev4HHSeiUKCccT_C_RcUCdAYbHWQ,10
|
44
|
+
biomedisa-24.7.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|