biomedisa 24.8.11__py3-none-any.whl → 25.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/python3
2
2
  ##########################################################################
3
3
  ## ##
4
- ## Copyright (c) 2019-2024 Philipp Lösel. All rights reserved. ##
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
- # get meta data
218
- hf = h5py.File(bm.path_to_model, 'r')
219
- meta = hf.get('meta')
220
- configuration = meta.get('configuration')
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-2024 Philipp Lösel. All rights reserved. ##
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, classification, batch_size=32, dim=(32,32,32),
205
- dim_img=(32,32,32), n_classes=10, n_channels=1, augment=(False,False,False,False,0,False), patch_normalization=False, separation=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
- # Initialization
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
- if self.classification:
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
- if self.classification:
310
- tmp_X = self.img[k:k+self.dim[0],l:l+self.dim[1],m:m+self.dim[2]]
311
- tmp_y = self.label[k,l,m]
312
-
313
- # augmentation
314
- if self.train:
315
-
316
- # rotate in 3D
317
- if rotate:
318
- tmp_X = random_rotation_3d(tmp_X, max_angle=rotate)
319
-
320
- # flip patch along axes
321
- v = np.random.randint(n_aug+1)
322
- if np.any([flip_x, flip_y, flip_z]) and v>0:
323
- flip = flips[v-1]
324
- tmp_X = np.flip(tmp_X, flip)
325
-
326
- # swap axes
327
- if swapaxes:
328
- v = np.random.randint(4)
329
- if v==1:
330
- tmp_X = np.swapaxes(tmp_X,0,1)
331
- elif v==2:
332
- tmp_X = np.swapaxes(tmp_X,0,2)
333
- elif v==3:
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
- # rotate through a random 3d angle, uniformly distributed on a sphere.
370
- if rotate3d:
371
- tmp_X = np.empty((*self.dim, self.n_channels), dtype=np.float32)
372
- tmp_y = np.empty(self.dim, dtype=np.int32)
373
- rm_xx = rot_mtx[i, 0, 0]
374
- rm_xy = rot_mtx[i, 0, 1]
375
- rm_xz = rot_mtx[i, 0, 2]
376
- rm_yx = rot_mtx[i, 1, 0]
377
- rm_yy = rot_mtx[i, 1, 1]
378
- rm_yz = rot_mtx[i, 1, 2]
379
- rm_zx = rot_mtx[i, 2, 0]
380
- rm_zy = rot_mtx[i, 2, 1]
381
- rm_zz = rot_mtx[i, 2, 2]
382
- for c in range(self.n_channels):
383
- tmp_X[:,:,:,c] = rotate_img_patch_3d(self.img[:,:,:,c],tmp_X[:,:,:,c],k,l,m,
384
- rm_xx,rm_xy,rm_xz,rm_yx,rm_yy,rm_yz,rm_zx,rm_zy,rm_zz,
385
- self.dim[0],self.dim[1],self.dim[2],
386
- 256, self.dim_img[0],self.dim_img[1],self.dim_img[2])
387
- tmp_y = rotate_label_patch_3d(self.label,tmp_y,k,l,m,
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
- # flip patch along axes
393
- v = np.random.randint(n_aug+1)
394
- if np.any([flip_x, flip_y, flip_z]) and v>0:
395
- flip = flips[v-1]
396
- tmp_X = np.flip(tmp_X, flip)
397
- tmp_y = np.flip(tmp_y, flip)
398
-
399
- # swap axes
400
- if swapaxes:
401
- v = np.random.randint(4)
402
- if v==1:
403
- tmp_X = np.swapaxes(tmp_X,0,1)
404
- tmp_y = np.swapaxes(tmp_y,0,1)
405
- elif v==2:
406
- tmp_X = np.swapaxes(tmp_X,0,2)
407
- tmp_y = np.swapaxes(tmp_y,0,2)
408
- elif v==3:
409
- tmp_X = np.swapaxes(tmp_X,1,2)
410
- tmp_y = np.swapaxes(tmp_y,1,2)
411
-
412
- # patch normalization
413
- if self.patch_normalization:
414
- tmp_X = tmp_X.copy().astype(np.float32)
415
- for c in range(self.n_channels):
416
- tmp_X[:,:,:,c] -= np.mean(tmp_X[:,:,:,c])
417
- tmp_X[:,:,:,c] /= max(np.std(tmp_X[:,:,:,c]), 1e-6)
418
-
419
- # assign to batch
420
- X[i] = tmp_X
421
- y[i,:,:,:,0] = tmp_y
422
-
423
- return X, tf.keras.utils.to_categorical(y, num_classes=self.n_classes)
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-2024 Philipp Lösel. All rights reserved. ##
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 = np.copy(tmp_X, order='C')
81
- for c in range(self.n_channels):
82
- tmp_X[:,:,:,c] -= np.mean(tmp_X[:,:,:,c])
83
- tmp_X[:,:,:,c] /= max(np.std(tmp_X[:,:,:,c]), 1e-6)
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
- try:
58
- arr = arr.ravel()
59
- counts = np.zeros(np.amax(arr)+1, dtype=int)
60
- @numba.jit(nopython=True)
61
- def __unique__(arr, size, counts):
62
- for k in range(size):
63
- counts[arr[k]] += 1
64
- return counts
65
- counts = __unique__(arr, arr.size, counts)
66
- labels = np.where(counts)[0]
67
- if return_counts:
68
- return labels, counts[labels]
69
- else:
70
- return labels
71
- except Exception as e:
72
- print(f"Error: {e}")
73
- return None
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
- pass
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
- print('Error:', message)
418
- bm.success = False
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
- all_platforms = cl.get_platforms()
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
- all_platforms = cl.get_platforms()
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:
@@ -1,6 +1,6 @@
1
1
  ##########################################################################
2
2
  ## ##
3
- ## Copyright (c) 2019-2024 Philipp Lösel. All rights reserved. ##
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
  ## ##
@@ -28,7 +28,8 @@
28
28
 
29
29
  import os
30
30
  from biomedisa.features.keras_helper import read_img_list
31
- from biomedisa.features.biomedisa_helper import img_resize, load_data, save_data, set_labels_to_zero
31
+ from biomedisa.features.biomedisa_helper import (img_resize,
32
+ load_data, save_data, set_labels_to_zero, welford_mean_std)
32
33
  from tensorflow.python.framework.errors_impl import ResourceExhaustedError
33
34
  from tensorflow.keras.applications import DenseNet121, densenet
34
35
  from tensorflow.keras.optimizers import Adam
@@ -161,7 +162,7 @@ def load_cropping_training_data(normalize, img_list, label_list, x_scale, y_scal
161
162
  # normalize first validation image
162
163
  if normalize and np.any(normalization_parameters):
163
164
  for c in range(channels):
164
- mean, std = np.mean(img[:,:,:,c]), np.std(img[:,:,:,c])
165
+ mean, std = welford_mean_std(img[:,:,:,c])
165
166
  img[:,:,:,c] = (img[:,:,:,c] - mean) / std
166
167
  img[:,:,:,c] = img[:,:,:,c] * normalization_parameters[1,c] + normalization_parameters[0,c]
167
168
 
@@ -170,8 +171,7 @@ def load_cropping_training_data(normalize, img_list, label_list, x_scale, y_scal
170
171
  normalization_parameters = np.zeros((2,channels))
171
172
  if normalize:
172
173
  for c in range(channels):
173
- normalization_parameters[0,c] = np.mean(img[:,:,:,c])
174
- normalization_parameters[1,c] = np.std(img[:,:,:,c])
174
+ normalization_parameters[:,c] = welford_mean_std(img[:,:,:,c])
175
175
 
176
176
  # loop over list of images
177
177
  if any(img_list) or type(img_in) is list:
@@ -223,7 +223,7 @@ def load_cropping_training_data(normalize, img_list, label_list, x_scale, y_scal
223
223
  next_img[:,:,:,c] -= np.amin(next_img[:,:,:,c])
224
224
  next_img[:,:,:,c] /= np.amax(next_img[:,:,:,c])
225
225
  if normalize:
226
- mean, std = np.mean(next_img[:,:,:,c]), np.std(next_img[:,:,:,c])
226
+ mean, std = welford_mean_std(next_img[:,:,:,c])
227
227
  next_img[:,:,:,c] = (next_img[:,:,:,c] - mean) / std
228
228
  next_img[:,:,:,c] = next_img[:,:,:,c] * normalization_parameters[1,c] + normalization_parameters[0,c]
229
229
  img = np.append(img, next_img, axis=0)
@@ -391,7 +391,7 @@ def load_data_to_crop(path_to_img, channels, x_scale, y_scale, z_scale,
391
391
  img[:,:,:,c] -= np.amin(img[:,:,:,c])
392
392
  img[:,:,:,c] /= np.amax(img[:,:,:,c])
393
393
  if normalize:
394
- mean, std = np.mean(img[:,:,:,c]), np.std(img[:,:,:,c])
394
+ mean, std = welford_mean_std(img[:,:,:,c])
395
395
  img[:,:,:,c] = (img[:,:,:,c] - mean) / std
396
396
  img[:,:,:,c] = img[:,:,:,c] * normalization_parameters[1,c] + normalization_parameters[0,c]
397
397
  img[img<0] = 0