garf 2.7__tar.gz → 2.9__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: garf
3
- Version: 2.7
3
+ Version: 2.9
4
4
  Summary: Utility tools for GATE ARF simulations
5
5
  Author-email: David Sarrut <david.sarrut@creatis.insa-lyon.fr>
6
6
  License: Apache License
@@ -212,7 +212,7 @@ Requires-Python: >=3.8
212
212
  Description-Content-Type: text/markdown
213
213
  License-File: LICENSE
214
214
  Requires-Dist: itk
215
- Requires-Dist: numpy
215
+ Requires-Dist: numpy<2.0.0
216
216
  Requires-Dist: pre-commit
217
217
  Requires-Dist: torch>=2.1.0
218
218
  Requires-Dist: tqdm
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
- import sys
5
4
  import numpy as np
6
- import itk
5
+ import SimpleITK as sitk
7
6
  import matplotlib.pyplot as plt
8
7
  import click
9
8
 
@@ -24,8 +23,8 @@ CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
24
23
  @click.option("--wslice", "-w", default=int(3), help="Slice width (to smooth)")
25
24
  def garf_compare_image_profile(image1_mhd, image2_mhd, islice, events, wslice):
26
25
  # Load image
27
- img_ref = itk.imread(image1_mhd)
28
- img = itk.imread(image2_mhd)
26
+ img_ref = sitk.ReadImage(image1_mhd)
27
+ img = sitk.ReadImage(image2_mhd)
29
28
  events = float(events)
30
29
  islice = int(islice)
31
30
  wslice = int(wslice)
@@ -34,8 +33,8 @@ def garf_compare_image_profile(image1_mhd, image2_mhd, islice, events, wslice):
34
33
  img = img * events
35
34
 
36
35
  # Get the pixels values as np array
37
- data_ref = itk.GetArrayFromImage(img_ref).astype(float)
38
- data = itk.GetArrayFromImage(img).astype(float)
36
+ data_ref = sitk.GetArrayFromImage(img_ref).astype(float)
37
+ data = sitk.GetArrayFromImage(img).astype(float)
39
38
 
40
39
  # Sometimes not same nb of slices -> crop the data_ref
41
40
  if len(data_ref) > len(data):
@@ -19,7 +19,7 @@ def load_nn(filename, verbose=True, gpu_mode="auto"):
19
19
  verbose and print(f"GPU mode ?", current_gpu_mode, current_gpu_device)
20
20
 
21
21
  verbose and print("Loading model ", filename)
22
- nn = torch.load(filename, map_location=current_gpu_device)
22
+ nn = torch.load(filename, map_location=current_gpu_device, weights_only=False)
23
23
  model_data = nn["model_data"]
24
24
 
25
25
  # set to gpu mode
@@ -206,9 +206,7 @@ class GarfDetector:
206
206
  # FIXME : float instead of double because of w_pred is float
207
207
  # TODO : check float / double ?
208
208
  self.output_image = np.zeros(tuple(t_image_size), dtype=np.float32)
209
- if self.current_gpu_mode == "mps":
210
- self.output_image = self.output_image.astype(np.float32)
211
- self.output_image = Tensor(torch.from_numpy(self.output_image))
209
+ self.output_image = torch.from_numpy(self.output_image)
212
210
  self.output_image = self.output_image.to(self.current_gpu_device)
213
211
 
214
212
  def initialize_planes_numpy(self, gaga_batch_size):
@@ -273,7 +271,7 @@ class GarfDetector:
273
271
  if len(projected_batch) > self.batch_size:
274
272
  print(
275
273
  f"Cannot use GARF, {len(projected_batch)} points while "
276
- f"batch size is {self.batch_size}"
274
+ f"batch size is {self.batch_size}. Increase the garf batch_size."
277
275
  )
278
276
  exit(-1)
279
277
  projected_points[i] = projected_batch
@@ -410,7 +408,8 @@ class GarfDetector:
410
408
  def image_from_coordinates_add_torch(self, img, vu, w_pred):
411
409
  img_r = img.ravel()
412
410
  ind_r = vu[:, 1] * img.shape[0] + vu[:, 0]
413
- img_r.index_add_(0, ind_r, w_pred)
411
+ # img_r.index_add_(dim=0, index=ind_r, source=w_pred) # <--- BUG on MPS
412
+ img_r = img_r.index_add(dim=0, index=ind_r, source=w_pred)
414
413
  img = img_r.reshape_as(img)
415
414
  return img
416
415
 
@@ -529,8 +528,12 @@ def normalize_logproba(x):
529
528
  exb_sum = torch.sum(exb, dim=1)
530
529
  # divide if not equal at zero
531
530
  p = torch.divide(exb.T, exb_sum, out=torch.zeros_like(exb.T)).T
531
+
532
+ # sometimes: nan.
533
+ p[torch.isnan(p)] = 0
534
+
532
535
  # check (should be equal to 1.0)
533
- # check = np.sum(p, axis=1)
536
+ # check = torch.sum(p, dim=1)
534
537
  # print(check)
535
538
  return p
536
539
 
@@ -646,7 +649,11 @@ def normalize_proba_with_russian_roulette(w_pred, channel, rr):
646
649
 
647
650
  # normalize
648
651
  p_sum = torch.sum(w_pred, axis=1, keepdims=True)
649
- w_pred = w_pred / p_sum
652
+ # w_pred = w_pred / p_sum
653
+ w_pred = torch.divide(w_pred, p_sum, out=torch.zeros_like(w_pred))
654
+
655
+ # remove nan
656
+ w_pred[torch.isnan(w_pred)] = 0
650
657
 
651
658
  # check
652
659
  # p_sum = torch.sum(w_pred, axis=1)
@@ -688,16 +695,21 @@ def image_from_coordinates_add_numpy(img, u, v, w_pred, hit_slice=False):
688
695
  # fill image using index broadcasting
689
696
  # Important: the >0 condition is to avoid outside elements.
690
697
  tiny = 0 # ??
691
- for i in range(1, nb_ene):
692
- # sum up values for pixel coordinates which occur multiple times
693
- chx = np.bincount(uv32, weights=w_pred[:, i])
694
- img[i, uv16Bins[chx > tiny, 0], uv16Bins[chx > tiny, 1]] += chx[chx > tiny]
695
-
696
- # Consider the hit slice ?
697
698
  if hit_slice:
699
+ for i in range(1, nb_ene):
700
+ # sum up values for pixel coordinates which occur multiple times
701
+ chx = np.bincount(uv32, weights=w_pred[:, i])
702
+ img[i, uv16Bins[chx > tiny, 0], uv16Bins[chx > tiny, 1]] += chx[chx > tiny]
703
+ # Consider the hit slice
698
704
  chx = np.bincount(uv32)
699
705
  img[0, uv16Bins[chx > tiny, 0], uv16Bins[chx > tiny, 1]] += chx[chx > tiny]
700
706
 
707
+ else:
708
+ for i in range(0, nb_ene - 1):
709
+ # sum up values for pixel coordinates which occur multiple times
710
+ chx = np.bincount(uv32, weights=w_pred[:, i + 1])
711
+ img[i, uv16Bins[chx > tiny, 0], uv16Bins[chx > tiny, 1]] += chx[chx > tiny]
712
+
701
713
 
702
714
  def arf_plane_intersection(batch, plane, image_plane_size_mm):
703
715
  """
@@ -797,7 +809,7 @@ def arf_plane_intersection(batch, plane, image_plane_size_mm):
797
809
  return batch
798
810
 
799
811
 
800
- def arf_from_points_to_image_counts(
812
+ def arf_from_points_to_image_counts_OLD(
801
813
  projected_batch, # 5D: 2 plane coordinates, 2 angles, 1 energy
802
814
  model, # ARF neural network model
803
815
  model_data, # associated model data
@@ -849,3 +861,62 @@ def arf_from_points_to_image_counts(
849
861
  )
850
862
 
851
863
  return u, v, w_pred
864
+
865
+
866
+ def arf_from_points_to_image_counts(
867
+ projected_batch, # 5D: 2 plane coordinates, 2 angles, 1 energy, 1 weight
868
+ model, # ARF neural network model
869
+ model_data, # associated model data
870
+ distance_to_crystal, # from detection plane to crystal center
871
+ image_plane_size_mm, # image plane in mm
872
+ image_plane_size_pixel, # image plane in pixel
873
+ image_plane_spacing,
874
+ ): # image plane spacing
875
+ """
876
+ Input : position, direction on the detector plane, energy
877
+ Compute
878
+ - garf.nn_predict
879
+ - garf.compute_angle_offset
880
+ - garf.remove_out_of_image_boundaries2
881
+
882
+ Used in 1) GarfDetector class and 2) gate ARFActor
883
+
884
+ """
885
+
886
+ # get the two angles and the energy
887
+ ax = projected_batch[:, 2:5]
888
+
889
+ # predict weights
890
+ w_pred = nn_predict_numpy(model, model_data, ax)
891
+
892
+ # particle weight ?
893
+ if projected_batch.shape[1] == 6:
894
+ weights = projected_batch[:, 5]
895
+ w_pred = w_pred * weights[:, np.newaxis]
896
+
897
+ # Get the two first columns = points coordinates
898
+ cx = projected_batch[:, 0:2]
899
+
900
+ # Get the two next columns = angles
901
+ angles = projected_batch[:, 2:4]
902
+
903
+ # Take angle into account: consider position at collimator + half crystal
904
+ t = compute_angle_offset_numpy(angles, distance_to_crystal)
905
+ cx = cx + t
906
+
907
+ # convert coord to pixel
908
+ coord = (
909
+ cx + image_plane_size_mm / 2 - image_plane_spacing / 2
910
+ ) / image_plane_spacing
911
+ coord = np.around(coord).astype(int)
912
+
913
+ # why vu and not uv ?
914
+ v = coord[:, 0]
915
+ u = coord[:, 1]
916
+
917
+ # remove points outside the image
918
+ u, v, w_pred = remove_out_of_image_boundaries_numpy(
919
+ u, v, w_pred, image_plane_size_pixel
920
+ )
921
+
922
+ return u, v, w_pred
@@ -220,28 +220,29 @@ def train_nn(x_train, y_train, params):
220
220
  "{} epochs without improvement, early stop.".format(early_stopping)
221
221
  )
222
222
  stop = True
223
- break
224
223
 
225
224
  # scheduler for learning rate
226
225
  scheduler.step(mean_loss)
227
226
 
228
227
  # FIXME WRONG
229
228
  # Check if need to print and store this epoch
230
- if epoch % epoch_store_every == 0 or best_train_loss < previous_best:
231
- tqdm.write(
232
- "Epoch {} best is {:.5f} at epoch {:.0f}".format(
233
- epoch, best_loss, best_epoch
234
- )
235
- )
229
+ if best_train_loss < previous_best:
230
+ tqdm.write("Epoch {} loss is {:.5f}".format(epoch, best_loss))
231
+ previous_best = best_train_loss
232
+
233
+ if (
234
+ (epoch != 0 and epoch % epoch_store_every == 0)
235
+ or stop
236
+ or epoch >= epoch_max - 1
237
+ ):
236
238
  optim_data = dict()
239
+ print("Store weights", epoch)
237
240
  optim_data["epoch"] = epoch
238
241
  optim_data["train_loss"] = train_loss
239
242
  state = copy.deepcopy(model.state_dict())
240
243
  nn["optim"]["model_state"].append(state)
241
244
  nn["optim"]["data"].append(optim_data)
242
- if best_train_loss < previous_best:
243
- best_epoch_index = len(nn["optim"]["model_state"]) - 1
244
- previous_best = best_train_loss
245
+ best_epoch_index = epoch
245
246
 
246
247
  # update progress bar
247
248
  pbar.update(1)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: garf
3
- Version: 2.7
3
+ Version: 2.9
4
4
  Summary: Utility tools for GATE ARF simulations
5
5
  Author-email: David Sarrut <david.sarrut@creatis.insa-lyon.fr>
6
6
  License: Apache License
@@ -212,7 +212,7 @@ Requires-Python: >=3.8
212
212
  Description-Content-Type: text/markdown
213
213
  License-File: LICENSE
214
214
  Requires-Dist: itk
215
- Requires-Dist: numpy
215
+ Requires-Dist: numpy<2.0.0
216
216
  Requires-Dist: pre-commit
217
217
  Requires-Dist: torch>=2.1.0
218
218
  Requires-Dist: tqdm
@@ -1,5 +1,5 @@
1
1
  itk
2
- numpy
2
+ numpy<2.0.0
3
3
  pre-commit
4
4
  torch>=2.1.0
5
5
  tqdm
@@ -17,7 +17,7 @@ find = { } # Scanning implicit namespaces is active by default
17
17
 
18
18
  [project]
19
19
  name = "garf"
20
- version = "2.7"
20
+ version = "2.9"
21
21
  dynamic = ["readme"]
22
22
  authors = [{ name = "David Sarrut", email = "david.sarrut@creatis.insa-lyon.fr" }]
23
23
  description = "Utility tools for GATE ARF simulations"
@@ -30,7 +30,7 @@ classifiers = [
30
30
  requires-python = ">=3.8"
31
31
  dependencies = [
32
32
  "itk",
33
- "numpy",
33
+ "numpy<2.0.0",
34
34
  "pre-commit",
35
35
  "torch>=2.1.0",
36
36
  "tqdm",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes