slide2vec 1.2.3__tar.gz → 1.3.0__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.
Files changed (44) hide show
  1. {slide2vec-1.2.3/slide2vec.egg-info → slide2vec-1.3.0}/PKG-INFO +1 -1
  2. {slide2vec-1.2.3 → slide2vec-1.3.0}/pyproject.toml +1 -1
  3. {slide2vec-1.2.3 → slide2vec-1.3.0}/setup.cfg +1 -1
  4. slide2vec-1.3.0/slide2vec/__init__.py +1 -0
  5. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/aggregate.py +7 -2
  6. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/embed.py +21 -9
  7. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/main.py +39 -14
  8. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/models.py +1 -1
  9. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/utils/config.py +6 -4
  10. {slide2vec-1.2.3 → slide2vec-1.3.0/slide2vec.egg-info}/PKG-INFO +1 -1
  11. slide2vec-1.2.3/slide2vec/__init__.py +0 -1
  12. {slide2vec-1.2.3 → slide2vec-1.3.0}/LICENSE +0 -0
  13. {slide2vec-1.2.3 → slide2vec-1.3.0}/MANIFEST.in +0 -0
  14. {slide2vec-1.2.3 → slide2vec-1.3.0}/README.md +0 -0
  15. {slide2vec-1.2.3 → slide2vec-1.3.0}/setup.py +0 -0
  16. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/configs/__init__.py +0 -0
  17. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/data/__init__.py +0 -0
  18. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/data/augmentations.py +0 -0
  19. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/data/dataset.py +0 -0
  20. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/distributed/__init__.py +0 -0
  21. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/__init__.py +0 -0
  22. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/__init__.py +0 -0
  23. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/attention.py +0 -0
  24. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/block.py +0 -0
  25. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/dino_head.py +0 -0
  26. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/drop_path.py +0 -0
  27. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/layer_scale.py +0 -0
  28. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/mlp.py +0 -0
  29. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/patch_embed.py +0 -0
  30. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/swiglu_ffn.py +0 -0
  31. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/vision_transformer_dino.py +0 -0
  32. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/vision_transformer_dinov2.py +0 -0
  33. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/tiling.py +0 -0
  34. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/utils/__init__.py +0 -0
  35. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/utils/log_utils.py +0 -0
  36. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/utils/utils.py +0 -0
  37. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/wsi/__init__.py +0 -0
  38. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/wsi/utils.py +0 -0
  39. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/wsi/wsi.py +0 -0
  40. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec.egg-info/SOURCES.txt +0 -0
  41. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec.egg-info/dependency_links.txt +0 -0
  42. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec.egg-info/not-zip-safe +0 -0
  43. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec.egg-info/requires.txt +0 -0
  44. {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slide2vec
3
- Version: 1.2.3
3
+ Version: 1.3.0
4
4
  Summary: Embedding of whole slide images with Foundation Models
5
5
  Home-page: https://github.com/clemsgrs/slide2vec
6
6
  Author: Clément Grisi
@@ -23,7 +23,7 @@ warn_unused_configs = true
23
23
  no_implicit_reexport = true
24
24
 
25
25
  [tool.bumpver]
26
- current_version = "1.2.3"
26
+ current_version = "1.3.0"
27
27
  version_pattern = "MAJOR.MINOR.PATCH"
28
28
  commit = false # We do version bumping in CI, not as a commit
29
29
  tag = false # Git tag already exists — we don't auto-tag
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = slide2vec
3
- version = 1.2.3
3
+ version = 1.3.0
4
4
  description = Embedding of whole slide images with Foundation Models
5
5
  author = Clément Grisi
6
6
  platforms = unix, linux, osx, cygwin, win32
@@ -0,0 +1 @@
1
+ __version__ = "1.3.0"
@@ -33,6 +33,9 @@ def get_args_parser(add_help: bool = True):
33
33
  default="",
34
34
  help="Name of output subdirectory",
35
35
  )
36
+ parser.add_argument(
37
+ "--run-on-cpu", action="store_true", help="run inference on cpu"
38
+ )
36
39
  return parser
37
40
 
38
41
 
@@ -49,6 +52,7 @@ def scale_coordinates(wsi_fp, coordinates, spacing, backend):
49
52
 
50
53
  def main(args):
51
54
  # setup configuration
55
+ run_on_cpu = args.run_on_cpu
52
56
  cfg = get_cfg_from_file(args.config_file)
53
57
  output_dir = Path(cfg.output_dir, args.run_id)
54
58
  cfg.output_dir = str(output_dir)
@@ -87,7 +91,7 @@ def main(args):
87
91
 
88
92
  autocast_context = (
89
93
  torch.autocast(device_type="cuda", dtype=torch.float16)
90
- if cfg.speed.fp16
94
+ if (cfg.speed.fp16 and not run_on_cpu)
91
95
  else nullcontext()
92
96
  )
93
97
  feature_aggregation_updates = {}
@@ -136,7 +140,8 @@ def main(args):
136
140
 
137
141
  torch.save(wsi_feature, feature_path)
138
142
  del wsi_feature
139
- torch.cuda.empty_cache()
143
+ if not run_on_cpu:
144
+ torch.cuda.empty_cache()
140
145
  gc.collect()
141
146
 
142
147
  feature_aggregation_updates[str(wsi_fp)] = {"status": "success"}
@@ -33,6 +33,9 @@ def get_args_parser(add_help: bool = True):
33
33
  default="",
34
34
  help="Name of output subdirectory",
35
35
  )
36
+ parser.add_argument(
37
+ "--run-on-cpu", action="store_true", help="run inference on cpu"
38
+ )
36
39
  return parser
37
40
 
38
41
 
@@ -61,14 +64,15 @@ def create_dataset(wsi_fp, coordinates_dir, spacing, backend, transforms):
61
64
  )
62
65
 
63
66
 
64
- def run_inference(dataloader, model, device, autocast_context, unit, batch_size, feature_path, feature_dim, dtype):
67
+ def run_inference(dataloader, model, device, autocast_context, unit, batch_size, feature_path, feature_dim, dtype, run_on_cpu: False):
68
+ device_name = f"GPU {distributed.get_global_rank()}" if not run_on_cpu else "CPU"
65
69
  with h5py.File(feature_path, "w") as f:
66
70
  features = f.create_dataset("features", shape=(0, *feature_dim), maxshape=(None, *feature_dim), dtype=dtype, chunks=(batch_size, *feature_dim))
67
71
  indices = f.create_dataset("indices", shape=(0,), maxshape=(None,), dtype='int64', chunks=(batch_size,))
68
72
  with torch.inference_mode(), autocast_context:
69
73
  for batch in tqdm.tqdm(
70
74
  dataloader,
71
- desc=f"Inference on GPU {distributed.get_global_rank()}",
75
+ desc=f"Inference on {device_name}",
72
76
  unit=unit,
73
77
  unit_scale=batch_size,
74
78
  leave=False,
@@ -86,7 +90,8 @@ def run_inference(dataloader, model, device, autocast_context, unit, batch_size,
86
90
  del image, feature
87
91
 
88
92
  # cleanup
89
- torch.cuda.empty_cache()
93
+ if not run_on_cpu:
94
+ torch.cuda.empty_cache()
90
95
  gc.collect()
91
96
 
92
97
 
@@ -116,11 +121,13 @@ def load_sort_and_deduplicate_features(tmp_dir, name, expected_len=None):
116
121
 
117
122
  def main(args):
118
123
  # setup configuration
124
+ run_on_cpu = args.run_on_cpu
119
125
  cfg = get_cfg_from_file(args.config_file)
120
126
  output_dir = Path(cfg.output_dir, args.run_id)
121
127
  cfg.output_dir = str(output_dir)
122
128
 
123
- setup_distributed()
129
+ if not run_on_cpu:
130
+ setup_distributed()
124
131
 
125
132
  if cfg.tiling.read_coordinates_from:
126
133
  coordinates_dir = Path(cfg.tiling.read_coordinates_from)
@@ -155,7 +162,8 @@ def main(args):
155
162
  model = ModelFactory(cfg.model).get_model()
156
163
  if distributed.is_main_process():
157
164
  print(f"Starting {unit}-level feature extraction...")
158
- torch.distributed.barrier()
165
+ if not run_on_cpu:
166
+ torch.distributed.barrier()
159
167
 
160
168
  # select slides that were successfully tiled but not yet processed for feature extraction
161
169
  tiled_df = process_df[process_df.tiling_status == "success"]
@@ -174,7 +182,7 @@ def main(args):
174
182
 
175
183
  autocast_context = (
176
184
  torch.autocast(device_type="cuda", dtype=torch.float16)
177
- if cfg.speed.fp16
185
+ if (cfg.speed.fp16 and not run_on_cpu)
178
186
  else nullcontext()
179
187
  )
180
188
  feature_extraction_updates = {}
@@ -231,9 +239,11 @@ def main(args):
231
239
  tmp_feature_path,
232
240
  feature_dim,
233
241
  dtype,
242
+ run_on_cpu,
234
243
  )
235
244
 
236
- torch.distributed.barrier()
245
+ if not run_on_cpu:
246
+ torch.distributed.barrier()
237
247
 
238
248
  if distributed.is_main_process():
239
249
  wsi_feature = load_sort_and_deduplicate_features(tmp_dir, name, expected_len=len(dataset))
@@ -241,10 +251,12 @@ def main(args):
241
251
 
242
252
  # cleanup
243
253
  del wsi_feature
244
- torch.cuda.empty_cache()
254
+ if not run_on_cpu:
255
+ torch.cuda.empty_cache()
245
256
  gc.collect()
246
257
 
247
- torch.distributed.barrier()
258
+ if not run_on_cpu:
259
+ torch.distributed.barrier()
248
260
 
249
261
  feature_extraction_updates[str(wsi_fp)] = {"status": "success"}
250
262
 
@@ -18,6 +18,12 @@ def get_args_parser(add_help: bool = True):
18
18
  parser.add_argument(
19
19
  "--config-file", default="", metavar="FILE", help="path to config file"
20
20
  )
21
+ parser.add_argument(
22
+ "--skip-datetime", action="store_true", help="skip run id datetime prefix"
23
+ )
24
+ parser.add_argument(
25
+ "--run-on-cpu", action="store_true", help="run inference on cpu"
26
+ )
21
27
  return parser
22
28
 
23
29
 
@@ -59,7 +65,7 @@ def run_tiling(config_file, run_id):
59
65
  sys.exit(proc.returncode)
60
66
 
61
67
 
62
- def run_feature_extraction(config_file, run_id):
68
+ def run_feature_extraction(config_file, run_id, run_on_cpu: False):
63
69
  print("Running embed.py...")
64
70
  # find a free port
65
71
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
@@ -77,6 +83,16 @@ def run_feature_extraction(config_file, run_id):
77
83
  "--config-file",
78
84
  config_file,
79
85
  ]
86
+ if run_on_cpu:
87
+ cmd = [
88
+ sys.executable,
89
+ "slide2vec/embed.py",
90
+ "--run-id",
91
+ run_id,
92
+ "--config-file",
93
+ config_file,
94
+ "--run-on-cpu",
95
+ ]
80
96
  # launch in its own process group.
81
97
  proc = subprocess.Popen(
82
98
  cmd,
@@ -103,7 +119,7 @@ def run_feature_extraction(config_file, run_id):
103
119
  sys.exit(proc.returncode)
104
120
 
105
121
 
106
- def run_feature_aggregation(config_file, run_id):
122
+ def run_feature_aggregation(config_file, run_id, run_on_cpu: False):
107
123
  print("Running aggregate.py...")
108
124
  # find a free port
109
125
  cmd = [
@@ -114,6 +130,8 @@ def run_feature_aggregation(config_file, run_id):
114
130
  "--config-file",
115
131
  config_file,
116
132
  ]
133
+ if run_on_cpu:
134
+ cmd.append("--run-on-cpu")
117
135
  # launch in its own process group.
118
136
  proc = subprocess.Popen(
119
137
  cmd,
@@ -131,7 +149,7 @@ def run_feature_aggregation(config_file, run_id):
131
149
  sys.stdout.flush()
132
150
  proc.wait()
133
151
  except KeyboardInterrupt:
134
- print("Received CTRL+C, terminating embed.py process group...")
152
+ print("Received CTRL+C, terminating aggregate.py process group...")
135
153
  os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
136
154
  proc.wait()
137
155
  sys.exit(1)
@@ -142,18 +160,18 @@ def run_feature_aggregation(config_file, run_id):
142
160
 
143
161
  def main(args):
144
162
  config_file = args.config_file
163
+ skip_datetime = args.skip_datetime
164
+ run_on_cpu = args.run_on_cpu
145
165
 
146
- cfg = setup(config_file)
166
+ cfg, run_id = setup(config_file, skip_datetime=skip_datetime)
147
167
  hf_login()
148
168
 
149
- output_dir = Path(cfg.output_dir)
150
- run_id = output_dir.stem
151
-
152
169
  run_tiling(config_file, run_id)
153
170
 
154
171
  print("Tiling completed.")
155
172
  print("=+=" * 10)
156
173
 
174
+ output_dir = Path(cfg.output_dir)
157
175
  features_dir = output_dir / "features"
158
176
  if cfg.wandb.enable:
159
177
  stop_event = threading.Event()
@@ -162,15 +180,17 @@ def main(args):
162
180
  )
163
181
  log_thread.start()
164
182
 
165
- run_feature_extraction(config_file, run_id)
166
- print("Feature extraction completed.")
167
- print("=+=" * 10)
183
+ run_feature_extraction(config_file, run_id, run_on_cpu)
168
184
 
169
185
  if cfg.model.level == "slide":
170
- run_feature_aggregation(config_file, run_id)
171
- print("Feature aggregation completed.")
186
+ run_feature_aggregation(config_file, run_id, run_on_cpu)
187
+ print("Feature extraction completed.")
188
+ print("=+=" * 10)
189
+ else:
190
+ print("Feature extraction completed.")
172
191
  print("=+=" * 10)
173
192
 
193
+
174
194
  if cfg.wandb.enable:
175
195
  stop_event.set()
176
196
  log_thread.join()
@@ -180,16 +200,21 @@ def main(args):
180
200
 
181
201
 
182
202
  if __name__ == "__main__":
183
- import warnings
184
203
 
204
+ import warnings
185
205
  import torchvision
206
+
186
207
  torchvision.disable_beta_transforms_warning()
187
-
208
+
188
209
  warnings.filterwarnings("ignore", message=".*Could not set the permissions.*")
189
210
  warnings.filterwarnings("ignore", message=".*antialias.*", category=UserWarning)
190
211
  warnings.filterwarnings("ignore", message=".*TypedStorage.*", category=UserWarning)
191
212
  warnings.filterwarnings("ignore", category=FutureWarning)
192
213
  warnings.filterwarnings("ignore", message="The given NumPy array is not writable")
214
+ warnings.filterwarnings(
215
+ "ignore",
216
+ message=".*'frozen' attribute with value True was provided to the `Field`.*"
217
+ )
193
218
 
194
219
  args = get_args_parser(add_help=True).parse_args()
195
220
  main(args)
@@ -578,5 +578,5 @@ class PRISM(SlideFeatureExtractor):
578
578
  def forward_slide(self, tile_features, **kwargs):
579
579
  tile_features = tile_features.unsqueeze(0)
580
580
  reprs = self.slide_encoder.slide_representations(tile_features)
581
- output = reprs["image_embedding"] # [1, 1280]
581
+ output = reprs["image_embedding"].squeeze(0) # [1280]
582
582
  return output
@@ -32,7 +32,7 @@ def get_cfg_from_file(config_file):
32
32
  return cfg
33
33
 
34
34
 
35
- def setup(config_file):
35
+ def setup(config_file, skip_datetime: bool = False):
36
36
  """
37
37
  Basic configuration setup without any distributed or GPU-specific initialization.
38
38
  This function:
@@ -45,8 +45,10 @@ def setup(config_file):
45
45
 
46
46
  if cfg.resume:
47
47
  run_id = cfg.resume_dirname
48
- else:
48
+ elif not skip_datetime:
49
49
  run_id = datetime.datetime.now().strftime("%Y-%m-%d_%H_%M")
50
+ else:
51
+ run_id = ""
50
52
 
51
53
  if cfg.wandb.enable:
52
54
  key = os.environ.get("WANDB_API_KEY")
@@ -56,7 +58,7 @@ def setup(config_file):
56
58
 
57
59
  output_dir = Path(cfg.output_dir, run_id)
58
60
  if distributed.is_main_process():
59
- output_dir.mkdir(exist_ok=cfg.resume, parents=True)
61
+ output_dir.mkdir(exist_ok=cfg.resume or skip_datetime, parents=True)
60
62
  cfg.output_dir = str(output_dir)
61
63
 
62
64
  fix_random_seeds(0)
@@ -65,7 +67,7 @@ def setup(config_file):
65
67
  cfg_path = write_config(cfg, cfg.output_dir)
66
68
  if cfg.wandb.enable:
67
69
  wandb_run.save(cfg_path)
68
- return cfg
70
+ return cfg, run_id
69
71
 
70
72
 
71
73
  def setup_distributed():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slide2vec
3
- Version: 1.2.3
3
+ Version: 1.3.0
4
4
  Summary: Embedding of whole slide images with Foundation Models
5
5
  Home-page: https://github.com/clemsgrs/slide2vec
6
6
  Author: Clément Grisi
@@ -1 +0,0 @@
1
- __version__ = "1.2.3"
File without changes
File without changes
File without changes
File without changes
File without changes