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.
- {slide2vec-1.2.3/slide2vec.egg-info → slide2vec-1.3.0}/PKG-INFO +1 -1
- {slide2vec-1.2.3 → slide2vec-1.3.0}/pyproject.toml +1 -1
- {slide2vec-1.2.3 → slide2vec-1.3.0}/setup.cfg +1 -1
- slide2vec-1.3.0/slide2vec/__init__.py +1 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/aggregate.py +7 -2
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/embed.py +21 -9
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/main.py +39 -14
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/models.py +1 -1
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/utils/config.py +6 -4
- {slide2vec-1.2.3 → slide2vec-1.3.0/slide2vec.egg-info}/PKG-INFO +1 -1
- slide2vec-1.2.3/slide2vec/__init__.py +0 -1
- {slide2vec-1.2.3 → slide2vec-1.3.0}/LICENSE +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/MANIFEST.in +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/README.md +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/setup.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/data/__init__.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/data/augmentations.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/data/dataset.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/__init__.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/__init__.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/attention.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/block.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/dino_head.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/drop_path.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/layer_scale.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/mlp.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/patch_embed.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/layers/swiglu_ffn.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/vision_transformer_dino.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/models/vision_transformer_dinov2.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/tiling.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/utils/utils.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/wsi/__init__.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/wsi/utils.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec/wsi/wsi.py +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec.egg-info/SOURCES.txt +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec.egg-info/requires.txt +0 -0
- {slide2vec-1.2.3 → slide2vec-1.3.0}/slide2vec.egg-info/top_level.txt +0 -0
|
@@ -23,7 +23,7 @@ warn_unused_configs = true
|
|
|
23
23
|
no_implicit_reexport = true
|
|
24
24
|
|
|
25
25
|
[tool.bumpver]
|
|
26
|
-
current_version = "1.
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
254
|
+
if not run_on_cpu:
|
|
255
|
+
torch.cuda.empty_cache()
|
|
245
256
|
gc.collect()
|
|
246
257
|
|
|
247
|
-
|
|
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
|
|
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
|
|
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"] # [
|
|
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
|
-
|
|
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 +0,0 @@
|
|
|
1
|
-
__version__ = "1.2.3"
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|