Skip to content

Commit a02fbe2

Browse files
authored
Merge branch 'dev' into 8587-test-erros-on-pytorch-release-2508-on-series-50
2 parents 0802904 + 6187529 commit a02fbe2

44 files changed

Lines changed: 1795 additions & 217 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

monai/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def filter(self, record):
131131
# workaround related to https://github.com/Project-MONAI/MONAI/issues/7575
132132
if hasattr(torch.cuda.device_count, "cache_clear"):
133133
torch.cuda.device_count.cache_clear()
134-
except BaseException:
134+
except Exception:
135135
from .utils.misc import MONAIEnvVars
136136

137137
if MONAIEnvVars.debug():

monai/apps/auto3dseg/auto_runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from monai.apps.auto3dseg.hpo_gen import NNIGen
2727
from monai.apps.auto3dseg.utils import export_bundle_algo_history, import_bundle_algo_history
2828
from monai.apps.utils import get_logger
29-
from monai.auto3dseg.utils import algo_to_pickle
29+
from monai.auto3dseg.utils import algo_to_json
3030
from monai.bundle import ConfigParser
3131
from monai.transforms import SaveImage
3232
from monai.utils import AlgoKeys, has_option, look_up_option, optional_import
@@ -740,7 +740,7 @@ def _train_algo_in_sequence(self, history: list[dict[str, Any]]) -> None:
740740
acc = algo.get_score()
741741

742742
algo_meta_data = {str(AlgoKeys.SCORE): acc}
743-
algo_to_pickle(algo, template_path=algo.template_path, **algo_meta_data)
743+
algo_to_json(algo, template_path=algo.template_path, **algo_meta_data)
744744

745745
def _train_algo_in_nni(self, history: list[dict[str, Any]]) -> None:
746746
"""

monai/apps/auto3dseg/bundle_gen.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
_prepare_cmd_torchrun,
3737
_run_cmd_bcprun,
3838
_run_cmd_torchrun,
39-
algo_to_pickle,
39+
algo_to_json,
4040
)
4141
from monai.bundle.config_parser import ConfigParser
4242
from monai.config import PathLike
@@ -366,6 +366,39 @@ def get_output_path(self):
366366
"""Returns the algo output paths to find the algo scripts and configs."""
367367
return self.output_path
368368

369+
def state_dict(self) -> dict:
370+
"""
371+
Return state for serialization.
372+
373+
Returns:
374+
A dictionary containing the BundleAlgo state to serialize.
375+
376+
Note:
377+
template_path is excluded as it is determined dynamically at load time
378+
based on which path successfully imports the Algo class.
379+
"""
380+
return {
381+
"data_stats_files": self.data_stats_files,
382+
"data_list_file": self.data_list_file,
383+
"mlflow_tracking_uri": self.mlflow_tracking_uri,
384+
"mlflow_experiment_name": self.mlflow_experiment_name,
385+
"output_path": self.output_path,
386+
"name": self.name,
387+
"best_metric": self.best_metric,
388+
"fill_records": self.fill_records,
389+
"device_setting": self.device_setting,
390+
}
391+
392+
def load_state_dict(self, state: dict) -> None:
393+
"""
394+
Restore state from a dictionary.
395+
396+
Args:
397+
state: A dictionary containing the state to restore.
398+
"""
399+
for key, value in state.items():
400+
setattr(self, key, value)
401+
369402

370403
# path to download the algo_templates
371404
default_algo_zip = (
@@ -658,7 +691,7 @@ def generate(
658691
else:
659692
gen_algo.export_to_disk(output_folder, name, fold=f_id)
660693

661-
algo_to_pickle(gen_algo, template_path=algo.template_path)
694+
algo_to_json(gen_algo, template_path=algo.template_path)
662695
self.history.append(
663696
{AlgoKeys.ID: name, AlgoKeys.ALGO: gen_algo}
664697
) # track the previous, may create a persistent history

monai/apps/auto3dseg/data_analyzer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ def _get_all_case_stats(
341341
_label_argmax = True # track if label is argmaxed
342342
batch_data[self.label_key] = label.to(device)
343343
d = summarizer(batch_data)
344-
except BaseException as err:
344+
except Exception as err:
345345
if "image_meta_dict" in batch_data.keys():
346346
filename = batch_data["image_meta_dict"][ImageMetaKey.FILENAME_OR_OBJ]
347347
else:
@@ -357,7 +357,7 @@ def _get_all_case_stats(
357357
label = torch.argmax(label, dim=0) if label.shape[0] > 1 else label[0]
358358
batch_data[self.label_key] = label.to("cpu")
359359
d = summarizer(batch_data)
360-
except BaseException as err:
360+
except Exception as err:
361361
logger.info(f"Unable to process data {filename} on {device}. {err}")
362362
continue
363363
else:

monai/apps/auto3dseg/ensemble_builder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def __call__(self, pred_param: dict | None = None) -> list:
216216
if "image_save_func" in param:
217217
try:
218218
ensemble_preds = self.ensemble_pred(preds, sigmoid=sigmoid)
219-
except BaseException:
219+
except Exception:
220220
ensemble_preds = self.ensemble_pred([_.to("cpu") for _ in preds], sigmoid=sigmoid)
221221
res = img_saver(ensemble_preds)
222222
# res is the path to the saved results

monai/apps/auto3dseg/hpo_gen.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from monai.apps.auto3dseg.bundle_gen import BundleAlgo
2121
from monai.apps.utils import get_logger
22-
from monai.auto3dseg import Algo, AlgoGen, algo_from_pickle, algo_to_pickle
22+
from monai.auto3dseg import Algo, AlgoGen, algo_from_json, algo_to_json
2323
from monai.bundle.config_parser import ConfigParser
2424
from monai.config import PathLike
2525
from monai.utils import optional_import
@@ -36,7 +36,7 @@ class HPOGen(AlgoGen):
3636
"""
3737
The base class for hyperparameter optimization (HPO) interfaces to generate algos in the Auto3Dseg pipeline.
3838
The auto-generated algos are saved at their ``output_path`` on the disk. The files in the ``output_path``
39-
may contain scripts that define the algo, configuration files, and pickle files that save the internal states
39+
may contain scripts that define the algo, configuration files, and JSON files that save the internal states
4040
of the algo before/after the training. Compared to the BundleGen class, HPOGen generates Algo on-the-fly, so
4141
training and algo generation may be executed alternatively and take a long time to finish the generation process.
4242
@@ -72,7 +72,7 @@ class NNIGen(HPOGen):
7272
7373
Args:
7474
algo: an Algo object (e.g. BundleAlgo) with defined methods: ``get_output_path`` and train
75-
and supports saving to and loading from pickle files via ``algo_from_pickle`` and ``algo_to_pickle``.
75+
and supports saving to and loading via ``algo_from_json`` and ``algo_to_json``.
7676
params: a set of parameter to override the algo if override is supported by Algo subclass.
7777
7878
Examples::
@@ -81,16 +81,16 @@ class NNIGen(HPOGen):
8181
├── algorithm_templates
8282
│ └── unet
8383
├── unet_0
84-
│ ├── algo_object.pkl
84+
│ ├── algo_object.json
8585
│ ├── configs
8686
│ └── scripts
8787
├── unet_0_learning_rate_0.01
88-
│ ├── algo_object.pkl
88+
│ ├── algo_object.json
8989
│ ├── configs
9090
│ ├── model_fold0
9191
│ └── scripts
9292
└── unet_0_learning_rate_0.1
93-
├── algo_object.pkl
93+
├── algo_object.json
9494
├── configs
9595
├── model_fold0
9696
└── scripts
@@ -129,10 +129,10 @@ def __init__(self, algo: Algo | None = None, params: dict | None = None):
129129
else:
130130
self.algo = algo
131131

132-
self.obj_filename = algo_to_pickle(self.algo, template_path=self.algo.template_path)
132+
self.obj_filename = algo_to_json(self.algo, template_path=self.algo.template_path)
133133

134134
def get_obj_filename(self):
135-
"""Return the filename of the dumped pickle algo object."""
135+
"""Return the filename of the dumped algo object."""
136136
return self.obj_filename
137137

138138
def print_bundle_algo_instruction(self):
@@ -190,7 +190,7 @@ def generate(self, output_folder: str = ".") -> None:
190190
task_id = self.get_task_id()
191191
task_prefix = os.path.basename(self.algo.get_output_path())
192192
write_path = os.path.join(output_folder, task_prefix + task_id)
193-
self.obj_filename = os.path.join(write_path, "algo_object.pkl")
193+
self.obj_filename = os.path.join(write_path, "algo_object.json")
194194

195195
if isinstance(self.algo, BundleAlgo):
196196
self.algo.export_to_disk(
@@ -214,15 +214,15 @@ def run_algo(self, obj_filename: str, output_folder: str = ".", template_path: P
214214
The python interface for NNI to run.
215215
216216
Args:
217-
obj_filename: the pickle-exported Algo object.
217+
obj_filename: the serialized Algo object.
218218
output_folder: the root path of the algorithms templates.
219219
template_path: the algorithm_template. It must contain algo.py in the follow path:
220220
``{algorithm_templates_dir}/{network}/scripts/algo.py``
221221
"""
222222
if not os.path.isfile(obj_filename):
223223
raise ValueError(f"{obj_filename} is not found")
224224

225-
self.algo, algo_meta_data = algo_from_pickle(obj_filename, template_path=template_path)
225+
self.algo, algo_meta_data = algo_from_json(obj_filename, template_path=template_path)
226226

227227
# step 1 sample hyperparams
228228
params = self.get_hyperparameters()
@@ -235,7 +235,7 @@ def run_algo(self, obj_filename: str, output_folder: str = ".", template_path: P
235235
acc = self.algo.get_score()
236236
algo_meta_data = {str(AlgoKeys.SCORE): acc}
237237

238-
algo_to_pickle(self.algo, template_path=self.algo.template_path, **algo_meta_data)
238+
algo_to_json(self.algo, template_path=self.algo.template_path, **algo_meta_data)
239239
self.set_score(acc)
240240

241241

@@ -250,7 +250,7 @@ class OptunaGen(HPOGen):
250250
251251
Args:
252252
algo: an Algo object (e.g. BundleAlgo). The object must at least define two methods: get_output_path and train
253-
and supports saving to and loading from pickle files via ``algo_from_pickle`` and ``algo_to_pickle``.
253+
and supports saving to and loading via ``algo_from_json`` and ``algo_to_json``.
254254
params: a set of parameter to override the algo if override is supported by Algo subclass.
255255
256256
Examples::
@@ -259,16 +259,16 @@ class OptunaGen(HPOGen):
259259
├── algorithm_templates
260260
│ └── unet
261261
├── unet_0
262-
│ ├── algo_object.pkl
262+
│ ├── algo_object.json
263263
│ ├── configs
264264
│ └── scripts
265265
├── unet_0_learning_rate_0.01
266-
│ ├── algo_object.pkl
266+
│ ├── algo_object.json
267267
│ ├── configs
268268
│ ├── model_fold0
269269
│ └── scripts
270270
└── unet_0_learning_rate_0.1
271-
├── algo_object.pkl
271+
├── algo_object.json
272272
├── configs
273273
├── model_fold0
274274
└── scripts
@@ -296,10 +296,10 @@ def __init__(self, algo: Algo | None = None, params: dict | None = None) -> None
296296
else:
297297
self.algo = algo
298298

299-
self.obj_filename = algo_to_pickle(self.algo, template_path=self.algo.template_path)
299+
self.obj_filename = algo_to_json(self.algo, template_path=self.algo.template_path)
300300

301301
def get_obj_filename(self):
302-
"""Return the dumped pickle object of algo."""
302+
"""Return the dumped object of algo."""
303303
return self.obj_filename
304304

305305
def get_hyperparameters(self):
@@ -329,7 +329,7 @@ def __call__(
329329
Callable that Optuna will use to optimize the hyper-parameters
330330
331331
Args:
332-
obj_filename: the pickle-exported Algo object.
332+
obj_filename: the serialized Algo object.
333333
output_folder: the root path of the algorithms templates.
334334
template_path: the algorithm_template. It must contain algo.py in the follow path:
335335
``{algorithm_templates_dir}/{network}/scripts/algo.py``
@@ -364,7 +364,7 @@ def generate(self, output_folder: str = ".") -> None:
364364
task_id = self.get_task_id()
365365
task_prefix = os.path.basename(self.algo.get_output_path())
366366
write_path = os.path.join(output_folder, task_prefix + task_id)
367-
self.obj_filename = os.path.join(write_path, "algo_object.pkl")
367+
self.obj_filename = os.path.join(write_path, "algo_object.json")
368368

369369
if isinstance(self.algo, BundleAlgo):
370370
self.algo.export_to_disk(output_folder, task_prefix + task_id, fill_with_datastats=False)
@@ -377,15 +377,15 @@ def run_algo(self, obj_filename: str, output_folder: str = ".", template_path: P
377377
The python interface for NNI to run.
378378
379379
Args:
380-
obj_filename: the pickle-exported Algo object.
380+
obj_filename: the serialized Algo object.
381381
output_folder: the root path of the algorithms templates.
382382
template_path: the algorithm_template. It must contain algo.py in the follow path:
383383
``{algorithm_templates_dir}/{network}/scripts/algo.py``
384384
"""
385385
if not os.path.isfile(obj_filename):
386386
raise ValueError(f"{obj_filename} is not found")
387387

388-
self.algo, algo_meta_data = algo_from_pickle(obj_filename, template_path=template_path)
388+
self.algo, algo_meta_data = algo_from_json(obj_filename, template_path=template_path)
389389

390390
# step 1 sample hyperparams
391391
params = self.get_hyperparameters()
@@ -397,5 +397,5 @@ def run_algo(self, obj_filename: str, output_folder: str = ".", template_path: P
397397
# step 4 report validation acc to controller
398398
acc = self.algo.get_score()
399399
algo_meta_data = {str(AlgoKeys.SCORE): acc}
400-
algo_to_pickle(self.algo, template_path=self.algo.template_path, **algo_meta_data)
400+
algo_to_json(self.algo, template_path=self.algo.template_path, **algo_meta_data)
401401
self.set_score(acc)

monai/apps/auto3dseg/utils.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import os
1515

1616
from monai.apps.auto3dseg.bundle_gen import BundleAlgo
17-
from monai.auto3dseg import algo_from_pickle, algo_to_pickle
17+
from monai.auto3dseg import algo_from_json, algo_to_json
1818
from monai.utils.enums import AlgoKeys
1919

2020
__all__ = ["import_bundle_algo_history", "export_bundle_algo_history", "get_name_from_algo_id"]
@@ -42,22 +42,29 @@ def import_bundle_algo_history(
4242
if not os.path.isdir(write_path):
4343
continue
4444

45-
obj_filename = os.path.join(write_path, "algo_object.pkl")
46-
if not os.path.isfile(obj_filename): # saved mode pkl
45+
# Prefer JSON format, fall back to legacy pickle
46+
json_filename = os.path.join(write_path, "algo_object.json")
47+
pkl_filename = os.path.join(write_path, "algo_object.pkl")
48+
49+
if os.path.isfile(json_filename):
50+
obj_filename = json_filename
51+
elif os.path.isfile(pkl_filename):
52+
obj_filename = pkl_filename
53+
else:
4754
continue
4855

49-
algo, algo_meta_data = algo_from_pickle(obj_filename, template_path=template_path)
56+
algo, algo_meta_data = algo_from_json(obj_filename, template_path=template_path)
5057

5158
best_metric = algo_meta_data.get(AlgoKeys.SCORE, None)
5259
if best_metric is None:
5360
try:
5461
best_metric = algo.get_score()
55-
except BaseException:
62+
except Exception:
5663
pass
5764

5865
is_trained = best_metric is not None
5966

60-
if (only_trained and is_trained) or not only_trained:
67+
if is_trained or not only_trained:
6168
history.append(
6269
{AlgoKeys.ID: name, AlgoKeys.ALGO: algo, AlgoKeys.SCORE: best_metric, AlgoKeys.IS_TRAINED: is_trained}
6370
)
@@ -67,14 +74,14 @@ def import_bundle_algo_history(
6774

6875
def export_bundle_algo_history(history: list[dict[str, BundleAlgo]]) -> None:
6976
"""
70-
Save all the BundleAlgo in the history to algo_object.pkl in each individual folder
77+
Save all the BundleAlgo in the history to algo_object.json in each individual folder.
7178
7279
Args:
7380
history: a List of Bundle. Typically, the history can be obtained from BundleGen get_history method
7481
"""
7582
for algo_dict in history:
7683
algo = algo_dict[AlgoKeys.ALGO]
77-
algo_to_pickle(algo, template_path=algo.template_path)
84+
algo_to_json(algo, template_path=algo.template_path)
7885

7986

8087
def get_name_from_algo_id(id: str) -> str:

monai/apps/detection/metrics/coco.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ def _compute_stats_single_threshold(
541541
for save_idx, array_index in enumerate(inds):
542542
precision[save_idx] = pr[array_index]
543543
th_scores[save_idx] = dt_scores_sorted[array_index]
544-
except BaseException:
544+
except Exception:
545545
pass
546546

547547
return recall, np.array(precision), np.array(th_scores)

monai/apps/nnunet/nnunetv2_runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ def __init__(
200200
from nnunetv2.utilities.dataset_name_id_conversion import maybe_convert_to_dataset_name
201201

202202
self.dataset_name = maybe_convert_to_dataset_name(int(self.dataset_name_or_id))
203-
except BaseException:
203+
except Exception:
204204
logger.warning(
205205
f"Dataset with name/ID: {self.dataset_name_or_id} cannot be found in the record. "
206206
"Please ignore the message above if you are running the pipeline from a fresh start. "
@@ -278,7 +278,7 @@ def convert_dataset(self):
278278
num_input_channels=num_input_channels,
279279
output_datafolder=raw_data_foldername,
280280
)
281-
except BaseException as err:
281+
except Exception as err:
282282
logger.warning(f"Input config may be incorrect. Detail info: error/exception message is:\n {err}")
283283
return
284284

monai/auto3dseg/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
from .operations import Operations, SampleOperations, SummaryOperations
2626
from .seg_summarizer import SegSummarizer
2727
from .utils import (
28+
algo_from_json,
2829
algo_from_pickle,
30+
algo_to_json,
2931
algo_to_pickle,
3032
concat_multikeys_to_dict,
3133
concat_val_to_np,

0 commit comments

Comments
 (0)