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.
- {garf-2.7 → garf-2.9}/PKG-INFO +2 -2
- {garf-2.7 → garf-2.9}/garf/bin/garf_compare_image_profile.py +5 -6
- {garf-2.7 → garf-2.9}/garf/garf_detector.py +86 -15
- {garf-2.7 → garf-2.9}/garf/garf_train.py +11 -10
- {garf-2.7 → garf-2.9}/garf.egg-info/PKG-INFO +2 -2
- {garf-2.7 → garf-2.9}/garf.egg-info/requires.txt +1 -1
- {garf-2.7 → garf-2.9}/pyproject.toml +2 -2
- {garf-2.7 → garf-2.9}/LICENSE +0 -0
- {garf-2.7 → garf-2.9}/garf/__init__.py +0 -0
- {garf-2.7 → garf-2.9}/garf/bin/__init__.py +0 -0
- {garf-2.7 → garf-2.9}/garf/bin/garf_nn_info.py +0 -0
- {garf-2.7 → garf-2.9}/garf/bin/garf_plot_test_dataset.py +0 -0
- {garf-2.7 → garf-2.9}/garf/bin/garf_plot_training_dataset.py +0 -0
- {garf-2.7 → garf-2.9}/garf/bin/garf_train.py +0 -0
- {garf-2.7 → garf-2.9}/garf/bin/pytorch_info.py +0 -0
- {garf-2.7 → garf-2.9}/garf/garf_model.py +0 -0
- {garf-2.7 → garf-2.9}/garf/helpers.py +0 -0
- {garf-2.7 → garf-2.9}/garf.egg-info/SOURCES.txt +0 -0
- {garf-2.7 → garf-2.9}/garf.egg-info/dependency_links.txt +0 -0
- {garf-2.7 → garf-2.9}/garf.egg-info/entry_points.txt +0 -0
- {garf-2.7 → garf-2.9}/garf.egg-info/not-zip-safe +0 -0
- {garf-2.7 → garf-2.9}/garf.egg-info/top_level.txt +0 -0
- {garf-2.7 → garf-2.9}/readme.md +0 -0
- {garf-2.7 → garf-2.9}/setup.cfg +0 -0
{garf-2.7 → garf-2.9}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: garf
|
|
3
|
-
Version: 2.
|
|
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
|
|
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 =
|
|
28
|
-
img =
|
|
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 =
|
|
38
|
-
data =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
231
|
-
tqdm.write(
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
@@ -17,7 +17,7 @@ find = { } # Scanning implicit namespaces is active by default
|
|
|
17
17
|
|
|
18
18
|
[project]
|
|
19
19
|
name = "garf"
|
|
20
|
-
version = "2.
|
|
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",
|
{garf-2.7 → garf-2.9}/LICENSE
RENAMED
|
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
|
|
File without changes
|
{garf-2.7 → garf-2.9}/readme.md
RENAMED
|
File without changes
|
{garf-2.7 → garf-2.9}/setup.cfg
RENAMED
|
File without changes
|