bioimageio.core.cli
bioimageio CLI
Note: Some docstrings use a hair space ' ' to place the added '(default: ...)' on a new line.
1"""bioimageio CLI 2 3Note: Some docstrings use a hair space ' ' 4 to place the added '(default: ...)' on a new line. 5""" 6 7import json 8import shutil 9import subprocess 10import sys 11from abc import ABC 12from argparse import RawTextHelpFormatter 13from difflib import SequenceMatcher 14from functools import cached_property 15from io import StringIO 16from pathlib import Path 17from pprint import pformat, pprint 18from typing import ( 19 Any, 20 Dict, 21 Iterable, 22 List, 23 Literal, 24 Mapping, 25 Optional, 26 Sequence, 27 Set, 28 Tuple, 29 Type, 30 Union, 31) 32 33import rich.markdown 34from loguru import logger 35from pydantic import AliasChoices, BaseModel, Field, model_validator 36from pydantic_settings import ( 37 BaseSettings, 38 CliPositionalArg, 39 CliSettingsSource, 40 CliSubCommand, 41 JsonConfigSettingsSource, 42 PydanticBaseSettingsSource, 43 SettingsConfigDict, 44 YamlConfigSettingsSource, 45) 46from tqdm import tqdm 47from typing_extensions import assert_never 48 49import bioimageio.spec 50from bioimageio.core import __version__ 51from bioimageio.spec import ( 52 AnyModelDescr, 53 InvalidDescr, 54 ResourceDescr, 55 load_description, 56 save_bioimageio_yaml_only, 57 settings, 58 update_format, 59 update_hashes, 60) 61from bioimageio.spec._internal.io import is_yaml_value 62from bioimageio.spec._internal.io_utils import open_bioimageio_yaml 63from bioimageio.spec._internal.types import FormatVersionPlaceholder, NotEmpty 64from bioimageio.spec.dataset import DatasetDescr 65from bioimageio.spec.model import ModelDescr, v0_4, v0_5 66from bioimageio.spec.notebook import NotebookDescr 67from bioimageio.spec.utils import ensure_description_is_model, get_reader, write_yaml 68 69from .commands import WeightFormatArgAll, WeightFormatArgAny, package, test 70from .common import MemberId, SampleId, SupportedWeightsFormat 71from .digest_spec import get_member_ids, load_sample_for_model 72from .io import load_dataset_stat, save_dataset_stat, save_sample 73from .prediction import create_prediction_pipeline 74from .proc_setup import ( 75 DatasetMeasure, 76 Measure, 77 MeasureValue, 78 StatsCalculator, 79 get_required_dataset_measures, 80) 81from .sample import Sample 82from .stat_measures import Stat 83from .utils import compare 84from .weight_converters._add_weights import add_weights 85 86WEIGHT_FORMAT_ALIASES = AliasChoices( 87 "weight-format", 88 "weights-format", 89 "weight_format", 90 "weights_format", 91) 92 93 94class CmdBase(BaseModel, use_attribute_docstrings=True, cli_implicit_flags=True): 95 pass 96 97 98class ArgMixin(BaseModel, use_attribute_docstrings=True, cli_implicit_flags=True): 99 pass 100 101 102class WithSummaryLogging(ArgMixin): 103 summary: List[Union[Literal["display"], Path]] = Field( 104 default_factory=lambda: ["display"], 105 examples=[ 106 Path("summary.md"), 107 Path("bioimageio_summaries/"), 108 ["display", Path("summary.md")], 109 ], 110 ) 111 """Display the validation summary or save it as JSON, Markdown or HTML. 112 The format is chosen based on the suffix: `.json`, `.md`, `.html`. 113 If a folder is given (path w/o suffix) the summary is saved in all formats. 114 Choose/add `"display"` to render the validation summary to the terminal. 115 """ 116 117 def log(self, descr: Union[ResourceDescr, InvalidDescr]): 118 _ = descr.validation_summary.log(self.summary) 119 120 121class WithSource(ArgMixin): 122 source: CliPositionalArg[str] 123 """Url/path to a (folder with a) bioimageio.yaml/rdf.yaml file 124 or a bioimage.io resource identifier, e.g. 'affable-shark'""" 125 126 @cached_property 127 def descr(self): 128 return load_description(self.source) 129 130 @property 131 def descr_id(self) -> str: 132 """a more user-friendly description id 133 (replacing legacy ids with their nicknames) 134 """ 135 if isinstance(self.descr, InvalidDescr): 136 return str(getattr(self.descr, "id", getattr(self.descr, "name"))) 137 138 nickname = None 139 if ( 140 isinstance(self.descr.config, v0_5.Config) 141 and (bio_config := self.descr.config.bioimageio) 142 and bio_config.model_extra is not None 143 ): 144 nickname = bio_config.model_extra.get("nickname") 145 146 return str(nickname or self.descr.id or self.descr.name) 147 148 149class ValidateFormatCmd(CmdBase, WithSource, WithSummaryLogging): 150 """Validate the meta data format of a bioimageio resource.""" 151 152 perform_io_checks: bool = Field( 153 settings.perform_io_checks, alias="perform-io-checks" 154 ) 155 """Wether or not to perform validations that requires downloading remote files. 156 Note: Default value is set by `BIOIMAGEIO_PERFORM_IO_CHECKS` environment variable. 157 """ 158 159 @cached_property 160 def descr(self): 161 return load_description(self.source, perform_io_checks=self.perform_io_checks) 162 163 def run(self): 164 self.log(self.descr) 165 sys.exit( 166 0 167 if self.descr.validation_summary.status in ("valid-format", "passed") 168 else 1 169 ) 170 171 172class TestCmd(CmdBase, WithSource, WithSummaryLogging): 173 """Test a bioimageio resource (beyond meta data formatting).""" 174 175 weight_format: WeightFormatArgAll = Field( 176 "all", 177 alias="weight-format", 178 validation_alias=WEIGHT_FORMAT_ALIASES, 179 ) 180 """The weight format to limit testing to. 181 182 (only relevant for model resources)""" 183 184 devices: Optional[List[str]] = None 185 """Device(s) to use for testing""" 186 187 runtime_env: Union[Literal["currently-active", "as-described"], Path] = Field( 188 "currently-active", alias="runtime-env" 189 ) 190 """The python environment to run the tests in 191 - `"currently-active"`: use active Python interpreter 192 - `"as-described"`: generate a conda environment YAML file based on the model 193 weights description. 194 - A path to a conda environment YAML. 195 Note: The `bioimageio.core` dependency will be added automatically if not present. 196 """ 197 198 determinism: Literal["seed_only", "full"] = "seed_only" 199 """Modes to improve reproducibility of test outputs.""" 200 201 stop_early: bool = Field( 202 False, alias="stop-early", validation_alias=AliasChoices("stop-early", "x") 203 ) 204 """Do not run further subtests after a failed one.""" 205 206 format_version: Union[FormatVersionPlaceholder, str] = Field( 207 "discover", alias="format-version" 208 ) 209 """The format version to use for testing. 210 - 'latest': Use the latest implemented format version for the given resource type (may trigger auto updating) 211 - 'discover': Use the format version as described in the resource description 212 - '0.4', '0.5', ...: Use the specified format version (may trigger auto updating) 213 """ 214 215 def run(self): 216 sys.exit( 217 test( 218 self.descr, 219 weight_format=self.weight_format, 220 devices=self.devices, 221 summary=self.summary, 222 runtime_env=self.runtime_env, 223 determinism=self.determinism, 224 format_version=self.format_version, 225 ) 226 ) 227 228 229class PackageCmd(CmdBase, WithSource, WithSummaryLogging): 230 """Save a resource's metadata with its associated files.""" 231 232 path: CliPositionalArg[Path] 233 """The path to write the (zipped) package to. 234 If it does not have a `.zip` suffix 235 this command will save the package as an unzipped folder instead.""" 236 237 weight_format: WeightFormatArgAll = Field( 238 "all", 239 alias="weight-format", 240 validation_alias=WEIGHT_FORMAT_ALIASES, 241 ) 242 """The weight format to include in the package (for model descriptions only).""" 243 244 def run(self): 245 if isinstance(self.descr, InvalidDescr): 246 self.log(self.descr) 247 raise ValueError(f"Invalid {self.descr.type} description.") 248 249 sys.exit( 250 package( 251 self.descr, 252 self.path, 253 weight_format=self.weight_format, 254 ) 255 ) 256 257 258def _get_stat( 259 model_descr: AnyModelDescr, 260 dataset: Iterable[Sample], 261 dataset_length: int, 262 stats_path: Path, 263) -> Mapping[DatasetMeasure, MeasureValue]: 264 req_dataset_meas, _ = get_required_dataset_measures(model_descr) 265 if not req_dataset_meas: 266 return {} 267 268 req_dataset_meas, _ = get_required_dataset_measures(model_descr) 269 270 if stats_path.exists(): 271 logger.info("loading precomputed dataset measures from {}", stats_path) 272 stat = load_dataset_stat(stats_path) 273 for m in req_dataset_meas: 274 if m not in stat: 275 raise ValueError(f"Missing {m} in {stats_path}") 276 277 return stat 278 279 stats_calc = StatsCalculator(req_dataset_meas) 280 281 for sample in tqdm( 282 dataset, total=dataset_length, desc="precomputing dataset stats", unit="sample" 283 ): 284 stats_calc.update(sample) 285 286 stat = stats_calc.finalize() 287 save_dataset_stat(stat, stats_path) 288 289 return stat 290 291 292class UpdateCmdBase(CmdBase, WithSource, ABC): 293 output: Union[Literal["display", "stdout"], Path] = "display" 294 """Output updated bioimageio.yaml to the terminal or write to a file. 295 Notes: 296 - `"display"`: Render to the terminal with syntax highlighting. 297 - `"stdout"`: Write to sys.stdout without syntax highligthing. 298 (More convenient for copying the updated bioimageio.yaml from the terminal.) 299 """ 300 301 diff: Union[bool, Path] = Field(True, alias="diff") 302 """Output a diff of original and updated bioimageio.yaml. 303 If a given path has an `.html` extension, a standalone HTML file is written, 304 otherwise the diff is saved in unified diff format (pure text). 305 """ 306 307 exclude_unset: bool = Field(True, alias="exclude-unset") 308 """Exclude fields that have not explicitly be set.""" 309 310 exclude_defaults: bool = Field(False, alias="exclude-defaults") 311 """Exclude fields that have the default value (even if set explicitly).""" 312 313 @cached_property 314 def updated(self) -> Union[ResourceDescr, InvalidDescr]: 315 raise NotImplementedError 316 317 def run(self): 318 original_yaml = open_bioimageio_yaml(self.source).unparsed_content 319 assert isinstance(original_yaml, str) 320 stream = StringIO() 321 322 save_bioimageio_yaml_only( 323 self.updated, 324 stream, 325 exclude_unset=self.exclude_unset, 326 exclude_defaults=self.exclude_defaults, 327 ) 328 updated_yaml = stream.getvalue() 329 330 diff = compare( 331 original_yaml.split("\n"), 332 updated_yaml.split("\n"), 333 diff_format=( 334 "html" 335 if isinstance(self.diff, Path) and self.diff.suffix == ".html" 336 else "unified" 337 ), 338 ) 339 340 if isinstance(self.diff, Path): 341 _ = self.diff.write_text(diff, encoding="utf-8") 342 elif self.diff: 343 console = rich.console.Console() 344 diff_md = f"## Diff\n\n````````diff\n{diff}\n````````" 345 console.print(rich.markdown.Markdown(diff_md)) 346 347 if isinstance(self.output, Path): 348 _ = self.output.write_text(updated_yaml, encoding="utf-8") 349 logger.info(f"written updated description to {self.output}") 350 elif self.output == "display": 351 updated_md = f"## Updated bioimageio.yaml\n\n```yaml\n{updated_yaml}\n```" 352 rich.console.Console().print(rich.markdown.Markdown(updated_md)) 353 elif self.output == "stdout": 354 print(updated_yaml) 355 else: 356 assert_never(self.output) 357 358 if isinstance(self.updated, InvalidDescr): 359 logger.warning("Update resulted in invalid description") 360 _ = self.updated.validation_summary.display() 361 362 363class UpdateFormatCmd(UpdateCmdBase): 364 """Update the metadata format to the latest format version.""" 365 366 exclude_defaults: bool = Field(True, alias="exclude-defaults") 367 """Exclude fields that have the default value (even if set explicitly). 368 369 Note: 370 The update process sets most unset fields explicitly with their default value. 371 """ 372 373 perform_io_checks: bool = Field( 374 settings.perform_io_checks, alias="perform-io-checks" 375 ) 376 """Wether or not to attempt validation that may require file download. 377 If `True` file hash values are added if not present.""" 378 379 @cached_property 380 def updated(self): 381 return update_format( 382 self.source, 383 exclude_defaults=self.exclude_defaults, 384 perform_io_checks=self.perform_io_checks, 385 ) 386 387 388class UpdateHashesCmd(UpdateCmdBase): 389 """Create a bioimageio.yaml description with updated file hashes.""" 390 391 @cached_property 392 def updated(self): 393 return update_hashes(self.source) 394 395 396class PredictCmd(CmdBase, WithSource): 397 """Run inference on your data with a bioimage.io model.""" 398 399 inputs: NotEmpty[List[Union[str, NotEmpty[List[str]]]]] = Field( 400 default_factory=lambda: ["{input_id}/001.tif"] 401 ) 402 """Model input sample paths (for each input tensor) 403 404 The input paths are expected to have shape... 405 - (n_samples,) or (n_samples,1) for models expecting a single input tensor 406 - (n_samples,) containing the substring '{input_id}', or 407 - (n_samples, n_model_inputs) to provide each input tensor path explicitly. 408 409 All substrings that are replaced by metadata from the model description: 410 - '{model_id}' 411 - '{input_id}' 412 413 Example inputs to process sample 'a' and 'b' 414 for a model expecting a 'raw' and a 'mask' input tensor: 415 --inputs="[[\\"a_raw.tif\\",\\"a_mask.tif\\"],[\\"b_raw.tif\\",\\"b_mask.tif\\"]]" 416 (Note that JSON double quotes need to be escaped.) 417 418 Alternatively a `bioimageio-cli.yaml` (or `bioimageio-cli.json`) file 419 may provide the arguments, e.g.: 420 ```yaml 421 inputs: 422 - [a_raw.tif, a_mask.tif] 423 - [b_raw.tif, b_mask.tif] 424 ``` 425 426 `.npy` and any file extension supported by imageio are supported. 427 Aavailable formats are listed at 428 https://imageio.readthedocs.io/en/stable/formats/index.html#all-formats. 429 Some formats have additional dependencies. 430 431 432 """ 433 434 outputs: Union[str, NotEmpty[Tuple[str, ...]]] = ( 435 "outputs_{model_id}/{output_id}/{sample_id}.tif" 436 ) 437 """Model output path pattern (per output tensor) 438 439 All substrings that are replaced: 440 - '{model_id}' (from model description) 441 - '{output_id}' (from model description) 442 - '{sample_id}' (extracted from input paths) 443 444 445 """ 446 447 overwrite: bool = False 448 """allow overwriting existing output files""" 449 450 blockwise: bool = False 451 """process inputs blockwise""" 452 453 stats: Path = Path("dataset_statistics.json") 454 """path to dataset statistics 455 (will be written if it does not exist, 456 but the model requires statistical dataset measures) 457 """ 458 459 preview: bool = False 460 """preview which files would be processed 461 and what outputs would be generated.""" 462 463 weight_format: WeightFormatArgAny = Field( 464 "any", 465 alias="weight-format", 466 validation_alias=WEIGHT_FORMAT_ALIASES, 467 ) 468 """The weight format to use.""" 469 470 example: bool = False 471 """generate and run an example 472 473 1. downloads example model inputs 474 2. creates a `{model_id}_example` folder 475 3. writes input arguments to `{model_id}_example/bioimageio-cli.yaml` 476 4. executes a preview dry-run 477 5. executes prediction with example input 478 479 480 """ 481 482 def _example(self): 483 model_descr = ensure_description_is_model(self.descr) 484 input_ids = get_member_ids(model_descr.inputs) 485 example_inputs = ( 486 model_descr.sample_inputs 487 if isinstance(model_descr, v0_4.ModelDescr) 488 else [ 489 t 490 for ipt in model_descr.inputs 491 if (t := ipt.sample_tensor or ipt.test_tensor) 492 ] 493 ) 494 if not example_inputs: 495 raise ValueError(f"{self.descr_id} does not specify any example inputs.") 496 497 inputs001: List[str] = [] 498 example_path = Path(f"{self.descr_id}_example") 499 example_path.mkdir(exist_ok=True) 500 501 for t, src in zip(input_ids, example_inputs): 502 reader = get_reader(src) 503 dst = Path(f"{example_path}/{t}/001{reader.suffix}") 504 dst.parent.mkdir(parents=True, exist_ok=True) 505 inputs001.append(dst.as_posix()) 506 with dst.open("wb") as f: 507 shutil.copyfileobj(reader, f) 508 509 inputs = [inputs001] 510 output_pattern = f"{example_path}/outputs/{{output_id}}/{{sample_id}}.tif" 511 512 bioimageio_cli_path = example_path / YAML_FILE 513 stats_file = "dataset_statistics.json" 514 stats = (example_path / stats_file).as_posix() 515 cli_example_args = dict( 516 inputs=inputs, 517 outputs=output_pattern, 518 stats=stats_file, 519 blockwise=self.blockwise, 520 ) 521 assert is_yaml_value(cli_example_args), cli_example_args 522 write_yaml( 523 cli_example_args, 524 bioimageio_cli_path, 525 ) 526 527 yaml_file_content = None 528 529 # escaped double quotes 530 inputs_json = json.dumps(inputs) 531 inputs_escaped = inputs_json.replace('"', r"\"") 532 source_escaped = self.source.replace('"', r"\"") 533 534 def get_example_command(preview: bool, escape: bool = False): 535 q: str = '"' if escape else "" 536 537 return [ 538 "bioimageio", 539 "predict", 540 # --no-preview not supported for py=3.8 541 *(["--preview"] if preview else []), 542 "--overwrite", 543 *(["--blockwise"] if self.blockwise else []), 544 f"--stats={q}{stats}{q}", 545 f"--inputs={q}{inputs_escaped if escape else inputs_json}{q}", 546 f"--outputs={q}{output_pattern}{q}", 547 f"{q}{source_escaped if escape else self.source}{q}", 548 ] 549 550 if Path(YAML_FILE).exists(): 551 logger.info( 552 "temporarily removing '{}' to execute example prediction", YAML_FILE 553 ) 554 yaml_file_content = Path(YAML_FILE).read_bytes() 555 Path(YAML_FILE).unlink() 556 557 try: 558 _ = subprocess.run(get_example_command(True), check=True) 559 _ = subprocess.run(get_example_command(False), check=True) 560 finally: 561 if yaml_file_content is not None: 562 _ = Path(YAML_FILE).write_bytes(yaml_file_content) 563 logger.debug("restored '{}'", YAML_FILE) 564 565 print( 566 "🎉 Sucessfully ran example prediction!\n" 567 + "To predict the example input using the CLI example config file" 568 + f" {example_path / YAML_FILE}, execute `bioimageio predict` from {example_path}:\n" 569 + f"$ cd {str(example_path)}\n" 570 + f'$ bioimageio predict "{source_escaped}"\n\n' 571 + "Alternatively run the following command" 572 + " in the current workind directory, not the example folder:\n$ " 573 + " ".join(get_example_command(False, escape=True)) 574 + f"\n(note that a local '{JSON_FILE}' or '{YAML_FILE}' may interfere with this)" 575 ) 576 577 def run(self): 578 if self.example: 579 return self._example() 580 581 model_descr = ensure_description_is_model(self.descr) 582 583 input_ids = get_member_ids(model_descr.inputs) 584 output_ids = get_member_ids(model_descr.outputs) 585 586 minimum_input_ids = tuple( 587 str(ipt.id) if isinstance(ipt, v0_5.InputTensorDescr) else str(ipt.name) 588 for ipt in model_descr.inputs 589 if not isinstance(ipt, v0_5.InputTensorDescr) or not ipt.optional 590 ) 591 maximum_input_ids = tuple( 592 str(ipt.id) if isinstance(ipt, v0_5.InputTensorDescr) else str(ipt.name) 593 for ipt in model_descr.inputs 594 ) 595 596 def expand_inputs(i: int, ipt: Union[str, Sequence[str]]) -> Tuple[str, ...]: 597 if isinstance(ipt, str): 598 ipts = tuple( 599 ipt.format(model_id=self.descr_id, input_id=t) for t in input_ids 600 ) 601 else: 602 ipts = tuple( 603 p.format(model_id=self.descr_id, input_id=t) 604 for t, p in zip(input_ids, ipt) 605 ) 606 607 if len(set(ipts)) < len(ipts): 608 if len(minimum_input_ids) == len(maximum_input_ids): 609 n = len(minimum_input_ids) 610 else: 611 n = f"{len(minimum_input_ids)}-{len(maximum_input_ids)}" 612 613 raise ValueError( 614 f"[input sample #{i}] Include '{{input_id}}' in path pattern or explicitly specify {n} distinct input paths (got {ipt})" 615 ) 616 617 if len(ipts) < len(minimum_input_ids): 618 raise ValueError( 619 f"[input sample #{i}] Expected at least {len(minimum_input_ids)} inputs {minimum_input_ids}, got {ipts}" 620 ) 621 622 if len(ipts) > len(maximum_input_ids): 623 raise ValueError( 624 f"Expected at most {len(maximum_input_ids)} inputs {maximum_input_ids}, got {ipts}" 625 ) 626 627 return ipts 628 629 inputs = [expand_inputs(i, ipt) for i, ipt in enumerate(self.inputs, start=1)] 630 631 sample_paths_in = [ 632 {t: Path(p) for t, p in zip(input_ids, ipts)} for ipts in inputs 633 ] 634 635 sample_ids = _get_sample_ids(sample_paths_in) 636 637 def expand_outputs(): 638 if isinstance(self.outputs, str): 639 outputs = [ 640 tuple( 641 Path( 642 self.outputs.format( 643 model_id=self.descr_id, output_id=t, sample_id=s 644 ) 645 ) 646 for t in output_ids 647 ) 648 for s in sample_ids 649 ] 650 else: 651 outputs = [ 652 tuple( 653 Path(p.format(model_id=self.descr_id, output_id=t, sample_id=s)) 654 for t, p in zip(output_ids, self.outputs) 655 ) 656 for s in sample_ids 657 ] 658 659 for i, out in enumerate(outputs, start=1): 660 if len(set(out)) < len(out): 661 raise ValueError( 662 f"[output sample #{i}] Include '{{output_id}}' in path pattern or explicitly specify {len(output_ids)} distinct output paths (got {out})" 663 ) 664 665 if len(out) != len(output_ids): 666 raise ValueError( 667 f"[output sample #{i}] Expected {len(output_ids)} outputs {output_ids}, got {out}" 668 ) 669 670 return outputs 671 672 outputs = expand_outputs() 673 674 sample_paths_out = [ 675 {MemberId(t): Path(p) for t, p in zip(output_ids, out)} for out in outputs 676 ] 677 678 if not self.overwrite: 679 for sample_paths in sample_paths_out: 680 for p in sample_paths.values(): 681 if p.exists(): 682 raise FileExistsError( 683 f"{p} already exists. use --overwrite to (re-)write outputs anyway." 684 ) 685 if self.preview: 686 print("🛈 bioimageio prediction preview structure:") 687 pprint( 688 { 689 "{sample_id}": dict( 690 inputs={"{input_id}": "<input path>"}, 691 outputs={"{output_id}": "<output path>"}, 692 ) 693 } 694 ) 695 print("🔎 bioimageio prediction preview output:") 696 pprint( 697 { 698 s: dict( 699 inputs={t: p.as_posix() for t, p in sp_in.items()}, 700 outputs={t: p.as_posix() for t, p in sp_out.items()}, 701 ) 702 for s, sp_in, sp_out in zip( 703 sample_ids, sample_paths_in, sample_paths_out 704 ) 705 } 706 ) 707 return 708 709 def input_dataset(stat: Stat): 710 for s, sp_in in zip(sample_ids, sample_paths_in): 711 yield load_sample_for_model( 712 model=model_descr, 713 paths=sp_in, 714 stat=stat, 715 sample_id=s, 716 ) 717 718 stat: Dict[Measure, MeasureValue] = dict( 719 _get_stat( 720 model_descr, input_dataset({}), len(sample_ids), self.stats 721 ).items() 722 ) 723 724 pp = create_prediction_pipeline( 725 model_descr, 726 weight_format=None if self.weight_format == "any" else self.weight_format, 727 ) 728 predict_method = ( 729 pp.predict_sample_with_blocking 730 if self.blockwise 731 else pp.predict_sample_without_blocking 732 ) 733 734 for sample_in, sp_out in tqdm( 735 zip(input_dataset(dict(stat)), sample_paths_out), 736 total=len(inputs), 737 desc=f"predict with {self.descr_id}", 738 unit="sample", 739 ): 740 sample_out = predict_method(sample_in) 741 save_sample(sp_out, sample_out) 742 743 744class AddWeightsCmd(CmdBase, WithSource, WithSummaryLogging): 745 output: CliPositionalArg[Path] 746 """The path to write the updated model package to.""" 747 748 source_format: Optional[SupportedWeightsFormat] = Field(None, alias="source-format") 749 """Exclusively use these weights to convert to other formats.""" 750 751 target_format: Optional[SupportedWeightsFormat] = Field(None, alias="target-format") 752 """Exclusively add this weight format.""" 753 754 verbose: bool = False 755 """Log more (error) output.""" 756 757 tracing: bool = True 758 """Allow tracing when converting pytorch_state_dict to torchscript 759 (still uses scripting if possible).""" 760 761 def run(self): 762 model_descr = ensure_description_is_model(self.descr) 763 if isinstance(model_descr, v0_4.ModelDescr): 764 raise TypeError( 765 f"model format {model_descr.format_version} not supported." 766 + " Please update the model first." 767 ) 768 updated_model_descr = add_weights( 769 model_descr, 770 output_path=self.output, 771 source_format=self.source_format, 772 target_format=self.target_format, 773 verbose=self.verbose, 774 allow_tracing=self.tracing, 775 ) 776 self.log(updated_model_descr) 777 778 779JSON_FILE = "bioimageio-cli.json" 780YAML_FILE = "bioimageio-cli.yaml" 781 782 783class Bioimageio( 784 BaseSettings, 785 cli_implicit_flags=True, 786 cli_parse_args=True, 787 cli_prog_name="bioimageio", 788 cli_use_class_docs_for_groups=True, 789 use_attribute_docstrings=True, 790): 791 """bioimageio - CLI for bioimage.io resources 🦒""" 792 793 model_config = SettingsConfigDict( 794 json_file=JSON_FILE, 795 yaml_file=YAML_FILE, 796 ) 797 798 validate_format: CliSubCommand[ValidateFormatCmd] = Field(alias="validate-format") 799 "Check a resource's metadata format" 800 801 test: CliSubCommand[TestCmd] 802 "Test a bioimageio resource (beyond meta data formatting)" 803 804 package: CliSubCommand[PackageCmd] 805 "Package a resource" 806 807 predict: CliSubCommand[PredictCmd] 808 "Predict with a model resource" 809 810 update_format: CliSubCommand[UpdateFormatCmd] = Field(alias="update-format") 811 """Update the metadata format""" 812 813 update_hashes: CliSubCommand[UpdateHashesCmd] = Field(alias="update-hashes") 814 """Create a bioimageio.yaml description with updated file hashes.""" 815 816 add_weights: CliSubCommand[AddWeightsCmd] = Field(alias="add-weights") 817 """Add additional weights to the model descriptions converted from available 818 formats to improve deployability.""" 819 820 @classmethod 821 def settings_customise_sources( 822 cls, 823 settings_cls: Type[BaseSettings], 824 init_settings: PydanticBaseSettingsSource, 825 env_settings: PydanticBaseSettingsSource, 826 dotenv_settings: PydanticBaseSettingsSource, 827 file_secret_settings: PydanticBaseSettingsSource, 828 ) -> Tuple[PydanticBaseSettingsSource, ...]: 829 cli: CliSettingsSource[BaseSettings] = CliSettingsSource( 830 settings_cls, 831 cli_parse_args=True, 832 formatter_class=RawTextHelpFormatter, 833 ) 834 sys_args = pformat(sys.argv) 835 logger.info("starting CLI with arguments:\n{}", sys_args) 836 return ( 837 cli, 838 init_settings, 839 YamlConfigSettingsSource(settings_cls), 840 JsonConfigSettingsSource(settings_cls), 841 ) 842 843 @model_validator(mode="before") 844 @classmethod 845 def _log(cls, data: Any): 846 logger.info( 847 "loaded CLI input:\n{}", 848 pformat({k: v for k, v in data.items() if v is not None}), 849 ) 850 return data 851 852 def run(self): 853 logger.info( 854 "executing CLI command:\n{}", 855 pformat({k: v for k, v in self.model_dump().items() if v is not None}), 856 ) 857 cmd = ( 858 self.add_weights 859 or self.package 860 or self.predict 861 or self.test 862 or self.update_format 863 or self.update_hashes 864 or self.validate_format 865 ) 866 assert cmd is not None 867 cmd.run() 868 869 870assert isinstance(Bioimageio.__doc__, str) 871Bioimageio.__doc__ += f""" 872 873library versions: 874 bioimageio.core {__version__} 875 bioimageio.spec {bioimageio.spec.__version__} 876 877spec format versions: 878 model RDF {ModelDescr.implemented_format_version} 879 dataset RDF {DatasetDescr.implemented_format_version} 880 notebook RDF {NotebookDescr.implemented_format_version} 881 882""" 883 884 885def _get_sample_ids( 886 input_paths: Sequence[Mapping[MemberId, Path]], 887) -> Sequence[SampleId]: 888 """Get sample ids for given input paths, based on the common path per sample. 889 890 Falls back to sample01, samle02, etc...""" 891 892 matcher = SequenceMatcher() 893 894 def get_common_seq(seqs: Sequence[Sequence[str]]) -> Sequence[str]: 895 """extract a common sequence from multiple sequences 896 (order sensitive; strips whitespace and slashes) 897 """ 898 common = seqs[0] 899 900 for seq in seqs[1:]: 901 if not seq: 902 continue 903 matcher.set_seqs(common, seq) 904 i, _, size = matcher.find_longest_match() 905 common = common[i : i + size] 906 907 if isinstance(common, str): 908 common = common.strip().strip("/") 909 else: 910 common = [cs for c in common if (cs := c.strip().strip("/"))] 911 912 if not common: 913 raise ValueError(f"failed to find common sequence for {seqs}") 914 915 return common 916 917 def get_shorter_diff(seqs: Sequence[Sequence[str]]) -> List[Sequence[str]]: 918 """get a shorter sequence whose entries are still unique 919 (order sensitive, not minimal sequence) 920 """ 921 min_seq_len = min(len(s) for s in seqs) 922 # cut from the start 923 for start in range(min_seq_len - 1, -1, -1): 924 shortened = [s[start:] for s in seqs] 925 if len(set(shortened)) == len(seqs): 926 min_seq_len -= start 927 break 928 else: 929 seen: Set[Sequence[str]] = set() 930 dupes = [s for s in seqs if s in seen or seen.add(s)] 931 raise ValueError(f"Found duplicate entries {dupes}") 932 933 # cut from the end 934 for end in range(min_seq_len - 1, 1, -1): 935 shortened = [s[:end] for s in shortened] 936 if len(set(shortened)) == len(seqs): 937 break 938 939 return shortened 940 941 full_tensor_ids = [ 942 sorted( 943 p.resolve().with_suffix("").as_posix() for p in input_sample_paths.values() 944 ) 945 for input_sample_paths in input_paths 946 ] 947 try: 948 long_sample_ids = [get_common_seq(t) for t in full_tensor_ids] 949 sample_ids = get_shorter_diff(long_sample_ids) 950 except ValueError as e: 951 raise ValueError(f"failed to extract sample ids: {e}") 952 953 return sample_ids
!!! abstract "Usage Documentation" Models
A base class for creating Pydantic models.
Attributes:
- __class_vars__: The names of the class variables defined on the model.
- __private_attributes__: Metadata about the private attributes of the model.
- __signature__: The synthesized
__init__
[Signature
][inspect.Signature] of the model. - __pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
- __pydantic_core_schema__: The core schema of the model.
- __pydantic_custom_init__: Whether the model has a custom
__init__
function. - __pydantic_decorators__: Metadata containing the decorators defined on the model.
This replaces
Model.__validators__
andModel.__root_validators__
from Pydantic V1. - __pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
- __pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
- __pydantic_post_init__: The name of the post-init method for the model, if defined.
- __pydantic_root_model__: Whether the model is a [
RootModel
][pydantic.root_model.RootModel]. - __pydantic_serializer__: The
pydantic-core
SchemaSerializer
used to dump instances of the model. - __pydantic_validator__: The
pydantic-core
SchemaValidator
used to validate instances of the model. - __pydantic_fields__: A dictionary of field names and their corresponding [
FieldInfo
][pydantic.fields.FieldInfo] objects. - __pydantic_computed_fields__: A dictionary of computed field names and their corresponding [
ComputedFieldInfo
][pydantic.fields.ComputedFieldInfo] objects. - __pydantic_extra__: A dictionary containing extra values, if [
extra
][pydantic.config.ConfigDict.extra] is set to'allow'
. - __pydantic_fields_set__: The names of fields explicitly set during instantiation.
- __pydantic_private__: Values of private attributes set on the model instance.
!!! abstract "Usage Documentation" Models
A base class for creating Pydantic models.
Attributes:
- __class_vars__: The names of the class variables defined on the model.
- __private_attributes__: Metadata about the private attributes of the model.
- __signature__: The synthesized
__init__
[Signature
][inspect.Signature] of the model. - __pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
- __pydantic_core_schema__: The core schema of the model.
- __pydantic_custom_init__: Whether the model has a custom
__init__
function. - __pydantic_decorators__: Metadata containing the decorators defined on the model.
This replaces
Model.__validators__
andModel.__root_validators__
from Pydantic V1. - __pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
- __pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
- __pydantic_post_init__: The name of the post-init method for the model, if defined.
- __pydantic_root_model__: Whether the model is a [
RootModel
][pydantic.root_model.RootModel]. - __pydantic_serializer__: The
pydantic-core
SchemaSerializer
used to dump instances of the model. - __pydantic_validator__: The
pydantic-core
SchemaValidator
used to validate instances of the model. - __pydantic_fields__: A dictionary of field names and their corresponding [
FieldInfo
][pydantic.fields.FieldInfo] objects. - __pydantic_computed_fields__: A dictionary of computed field names and their corresponding [
ComputedFieldInfo
][pydantic.fields.ComputedFieldInfo] objects. - __pydantic_extra__: A dictionary containing extra values, if [
extra
][pydantic.config.ConfigDict.extra] is set to'allow'
. - __pydantic_fields_set__: The names of fields explicitly set during instantiation.
- __pydantic_private__: Values of private attributes set on the model instance.
103class WithSummaryLogging(ArgMixin): 104 summary: List[Union[Literal["display"], Path]] = Field( 105 default_factory=lambda: ["display"], 106 examples=[ 107 Path("summary.md"), 108 Path("bioimageio_summaries/"), 109 ["display", Path("summary.md")], 110 ], 111 ) 112 """Display the validation summary or save it as JSON, Markdown or HTML. 113 The format is chosen based on the suffix: `.json`, `.md`, `.html`. 114 If a folder is given (path w/o suffix) the summary is saved in all formats. 115 Choose/add `"display"` to render the validation summary to the terminal. 116 """ 117 118 def log(self, descr: Union[ResourceDescr, InvalidDescr]): 119 _ = descr.validation_summary.log(self.summary)
!!! abstract "Usage Documentation" Models
A base class for creating Pydantic models.
Attributes:
- __class_vars__: The names of the class variables defined on the model.
- __private_attributes__: Metadata about the private attributes of the model.
- __signature__: The synthesized
__init__
[Signature
][inspect.Signature] of the model. - __pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
- __pydantic_core_schema__: The core schema of the model.
- __pydantic_custom_init__: Whether the model has a custom
__init__
function. - __pydantic_decorators__: Metadata containing the decorators defined on the model.
This replaces
Model.__validators__
andModel.__root_validators__
from Pydantic V1. - __pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
- __pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
- __pydantic_post_init__: The name of the post-init method for the model, if defined.
- __pydantic_root_model__: Whether the model is a [
RootModel
][pydantic.root_model.RootModel]. - __pydantic_serializer__: The
pydantic-core
SchemaSerializer
used to dump instances of the model. - __pydantic_validator__: The
pydantic-core
SchemaValidator
used to validate instances of the model. - __pydantic_fields__: A dictionary of field names and their corresponding [
FieldInfo
][pydantic.fields.FieldInfo] objects. - __pydantic_computed_fields__: A dictionary of computed field names and their corresponding [
ComputedFieldInfo
][pydantic.fields.ComputedFieldInfo] objects. - __pydantic_extra__: A dictionary containing extra values, if [
extra
][pydantic.config.ConfigDict.extra] is set to'allow'
. - __pydantic_fields_set__: The names of fields explicitly set during instantiation.
- __pydantic_private__: Values of private attributes set on the model instance.
Display the validation summary or save it as JSON, Markdown or HTML.
The format is chosen based on the suffix: .json
, .md
, .html
.
If a folder is given (path w/o suffix) the summary is saved in all formats.
Choose/add "display"
to render the validation summary to the terminal.
122class WithSource(ArgMixin): 123 source: CliPositionalArg[str] 124 """Url/path to a (folder with a) bioimageio.yaml/rdf.yaml file 125 or a bioimage.io resource identifier, e.g. 'affable-shark'""" 126 127 @cached_property 128 def descr(self): 129 return load_description(self.source) 130 131 @property 132 def descr_id(self) -> str: 133 """a more user-friendly description id 134 (replacing legacy ids with their nicknames) 135 """ 136 if isinstance(self.descr, InvalidDescr): 137 return str(getattr(self.descr, "id", getattr(self.descr, "name"))) 138 139 nickname = None 140 if ( 141 isinstance(self.descr.config, v0_5.Config) 142 and (bio_config := self.descr.config.bioimageio) 143 and bio_config.model_extra is not None 144 ): 145 nickname = bio_config.model_extra.get("nickname") 146 147 return str(nickname or self.descr.id or self.descr.name)
!!! abstract "Usage Documentation" Models
A base class for creating Pydantic models.
Attributes:
- __class_vars__: The names of the class variables defined on the model.
- __private_attributes__: Metadata about the private attributes of the model.
- __signature__: The synthesized
__init__
[Signature
][inspect.Signature] of the model. - __pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
- __pydantic_core_schema__: The core schema of the model.
- __pydantic_custom_init__: Whether the model has a custom
__init__
function. - __pydantic_decorators__: Metadata containing the decorators defined on the model.
This replaces
Model.__validators__
andModel.__root_validators__
from Pydantic V1. - __pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
- __pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
- __pydantic_post_init__: The name of the post-init method for the model, if defined.
- __pydantic_root_model__: Whether the model is a [
RootModel
][pydantic.root_model.RootModel]. - __pydantic_serializer__: The
pydantic-core
SchemaSerializer
used to dump instances of the model. - __pydantic_validator__: The
pydantic-core
SchemaValidator
used to validate instances of the model. - __pydantic_fields__: A dictionary of field names and their corresponding [
FieldInfo
][pydantic.fields.FieldInfo] objects. - __pydantic_computed_fields__: A dictionary of computed field names and their corresponding [
ComputedFieldInfo
][pydantic.fields.ComputedFieldInfo] objects. - __pydantic_extra__: A dictionary containing extra values, if [
extra
][pydantic.config.ConfigDict.extra] is set to'allow'
. - __pydantic_fields_set__: The names of fields explicitly set during instantiation.
- __pydantic_private__: Values of private attributes set on the model instance.
Url/path to a (folder with a) bioimageio.yaml/rdf.yaml file or a bioimage.io resource identifier, e.g. 'affable-shark'
131 @property 132 def descr_id(self) -> str: 133 """a more user-friendly description id 134 (replacing legacy ids with their nicknames) 135 """ 136 if isinstance(self.descr, InvalidDescr): 137 return str(getattr(self.descr, "id", getattr(self.descr, "name"))) 138 139 nickname = None 140 if ( 141 isinstance(self.descr.config, v0_5.Config) 142 and (bio_config := self.descr.config.bioimageio) 143 and bio_config.model_extra is not None 144 ): 145 nickname = bio_config.model_extra.get("nickname") 146 147 return str(nickname or self.descr.id or self.descr.name)
a more user-friendly description id (replacing legacy ids with their nicknames)
150class ValidateFormatCmd(CmdBase, WithSource, WithSummaryLogging): 151 """Validate the meta data format of a bioimageio resource.""" 152 153 perform_io_checks: bool = Field( 154 settings.perform_io_checks, alias="perform-io-checks" 155 ) 156 """Wether or not to perform validations that requires downloading remote files. 157 Note: Default value is set by `BIOIMAGEIO_PERFORM_IO_CHECKS` environment variable. 158 """ 159 160 @cached_property 161 def descr(self): 162 return load_description(self.source, perform_io_checks=self.perform_io_checks) 163 164 def run(self): 165 self.log(self.descr) 166 sys.exit( 167 0 168 if self.descr.validation_summary.status in ("valid-format", "passed") 169 else 1 170 )
Validate the meta data format of a bioimageio resource.
Wether or not to perform validations that requires downloading remote files.
Note: Default value is set by BIOIMAGEIO_PERFORM_IO_CHECKS
environment variable.
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Inherited Members
173class TestCmd(CmdBase, WithSource, WithSummaryLogging): 174 """Test a bioimageio resource (beyond meta data formatting).""" 175 176 weight_format: WeightFormatArgAll = Field( 177 "all", 178 alias="weight-format", 179 validation_alias=WEIGHT_FORMAT_ALIASES, 180 ) 181 """The weight format to limit testing to. 182 183 (only relevant for model resources)""" 184 185 devices: Optional[List[str]] = None 186 """Device(s) to use for testing""" 187 188 runtime_env: Union[Literal["currently-active", "as-described"], Path] = Field( 189 "currently-active", alias="runtime-env" 190 ) 191 """The python environment to run the tests in 192 - `"currently-active"`: use active Python interpreter 193 - `"as-described"`: generate a conda environment YAML file based on the model 194 weights description. 195 - A path to a conda environment YAML. 196 Note: The `bioimageio.core` dependency will be added automatically if not present. 197 """ 198 199 determinism: Literal["seed_only", "full"] = "seed_only" 200 """Modes to improve reproducibility of test outputs.""" 201 202 stop_early: bool = Field( 203 False, alias="stop-early", validation_alias=AliasChoices("stop-early", "x") 204 ) 205 """Do not run further subtests after a failed one.""" 206 207 format_version: Union[FormatVersionPlaceholder, str] = Field( 208 "discover", alias="format-version" 209 ) 210 """The format version to use for testing. 211 - 'latest': Use the latest implemented format version for the given resource type (may trigger auto updating) 212 - 'discover': Use the format version as described in the resource description 213 - '0.4', '0.5', ...: Use the specified format version (may trigger auto updating) 214 """ 215 216 def run(self): 217 sys.exit( 218 test( 219 self.descr, 220 weight_format=self.weight_format, 221 devices=self.devices, 222 summary=self.summary, 223 runtime_env=self.runtime_env, 224 determinism=self.determinism, 225 format_version=self.format_version, 226 ) 227 )
Test a bioimageio resource (beyond meta data formatting).
The weight format to limit testing to.
(only relevant for model resources)
The python environment to run the tests in
"currently-active"
: use active Python interpreter"as-described"
: generate a conda environment YAML file based on the model weights description.- A path to a conda environment YAML.
Note: The
bioimageio.core
dependency will be added automatically if not present.
The format version to use for testing.
- 'latest': Use the latest implemented format version for the given resource type (may trigger auto updating)
- 'discover': Use the format version as described in the resource description
- '0.4', '0.5', ...: Use the specified format version (may trigger auto updating)
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Inherited Members
230class PackageCmd(CmdBase, WithSource, WithSummaryLogging): 231 """Save a resource's metadata with its associated files.""" 232 233 path: CliPositionalArg[Path] 234 """The path to write the (zipped) package to. 235 If it does not have a `.zip` suffix 236 this command will save the package as an unzipped folder instead.""" 237 238 weight_format: WeightFormatArgAll = Field( 239 "all", 240 alias="weight-format", 241 validation_alias=WEIGHT_FORMAT_ALIASES, 242 ) 243 """The weight format to include in the package (for model descriptions only).""" 244 245 def run(self): 246 if isinstance(self.descr, InvalidDescr): 247 self.log(self.descr) 248 raise ValueError(f"Invalid {self.descr.type} description.") 249 250 sys.exit( 251 package( 252 self.descr, 253 self.path, 254 weight_format=self.weight_format, 255 ) 256 )
Save a resource's metadata with its associated files.
The path to write the (zipped) package to.
If it does not have a .zip
suffix
this command will save the package as an unzipped folder instead.
The weight format to include in the package (for model descriptions only).
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Inherited Members
293class UpdateCmdBase(CmdBase, WithSource, ABC): 294 output: Union[Literal["display", "stdout"], Path] = "display" 295 """Output updated bioimageio.yaml to the terminal or write to a file. 296 Notes: 297 - `"display"`: Render to the terminal with syntax highlighting. 298 - `"stdout"`: Write to sys.stdout without syntax highligthing. 299 (More convenient for copying the updated bioimageio.yaml from the terminal.) 300 """ 301 302 diff: Union[bool, Path] = Field(True, alias="diff") 303 """Output a diff of original and updated bioimageio.yaml. 304 If a given path has an `.html` extension, a standalone HTML file is written, 305 otherwise the diff is saved in unified diff format (pure text). 306 """ 307 308 exclude_unset: bool = Field(True, alias="exclude-unset") 309 """Exclude fields that have not explicitly be set.""" 310 311 exclude_defaults: bool = Field(False, alias="exclude-defaults") 312 """Exclude fields that have the default value (even if set explicitly).""" 313 314 @cached_property 315 def updated(self) -> Union[ResourceDescr, InvalidDescr]: 316 raise NotImplementedError 317 318 def run(self): 319 original_yaml = open_bioimageio_yaml(self.source).unparsed_content 320 assert isinstance(original_yaml, str) 321 stream = StringIO() 322 323 save_bioimageio_yaml_only( 324 self.updated, 325 stream, 326 exclude_unset=self.exclude_unset, 327 exclude_defaults=self.exclude_defaults, 328 ) 329 updated_yaml = stream.getvalue() 330 331 diff = compare( 332 original_yaml.split("\n"), 333 updated_yaml.split("\n"), 334 diff_format=( 335 "html" 336 if isinstance(self.diff, Path) and self.diff.suffix == ".html" 337 else "unified" 338 ), 339 ) 340 341 if isinstance(self.diff, Path): 342 _ = self.diff.write_text(diff, encoding="utf-8") 343 elif self.diff: 344 console = rich.console.Console() 345 diff_md = f"## Diff\n\n````````diff\n{diff}\n````````" 346 console.print(rich.markdown.Markdown(diff_md)) 347 348 if isinstance(self.output, Path): 349 _ = self.output.write_text(updated_yaml, encoding="utf-8") 350 logger.info(f"written updated description to {self.output}") 351 elif self.output == "display": 352 updated_md = f"## Updated bioimageio.yaml\n\n```yaml\n{updated_yaml}\n```" 353 rich.console.Console().print(rich.markdown.Markdown(updated_md)) 354 elif self.output == "stdout": 355 print(updated_yaml) 356 else: 357 assert_never(self.output) 358 359 if isinstance(self.updated, InvalidDescr): 360 logger.warning("Update resulted in invalid description") 361 _ = self.updated.validation_summary.display()
!!! abstract "Usage Documentation" Models
A base class for creating Pydantic models.
Attributes:
- __class_vars__: The names of the class variables defined on the model.
- __private_attributes__: Metadata about the private attributes of the model.
- __signature__: The synthesized
__init__
[Signature
][inspect.Signature] of the model. - __pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
- __pydantic_core_schema__: The core schema of the model.
- __pydantic_custom_init__: Whether the model has a custom
__init__
function. - __pydantic_decorators__: Metadata containing the decorators defined on the model.
This replaces
Model.__validators__
andModel.__root_validators__
from Pydantic V1. - __pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
- __pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
- __pydantic_post_init__: The name of the post-init method for the model, if defined.
- __pydantic_root_model__: Whether the model is a [
RootModel
][pydantic.root_model.RootModel]. - __pydantic_serializer__: The
pydantic-core
SchemaSerializer
used to dump instances of the model. - __pydantic_validator__: The
pydantic-core
SchemaValidator
used to validate instances of the model. - __pydantic_fields__: A dictionary of field names and their corresponding [
FieldInfo
][pydantic.fields.FieldInfo] objects. - __pydantic_computed_fields__: A dictionary of computed field names and their corresponding [
ComputedFieldInfo
][pydantic.fields.ComputedFieldInfo] objects. - __pydantic_extra__: A dictionary containing extra values, if [
extra
][pydantic.config.ConfigDict.extra] is set to'allow'
. - __pydantic_fields_set__: The names of fields explicitly set during instantiation.
- __pydantic_private__: Values of private attributes set on the model instance.
Output updated bioimageio.yaml to the terminal or write to a file. Notes:
"display"
: Render to the terminal with syntax highlighting."stdout"
: Write to sys.stdout without syntax highligthing. (More convenient for copying the updated bioimageio.yaml from the terminal.)
Output a diff of original and updated bioimageio.yaml.
If a given path has an .html
extension, a standalone HTML file is written,
otherwise the diff is saved in unified diff format (pure text).
318 def run(self): 319 original_yaml = open_bioimageio_yaml(self.source).unparsed_content 320 assert isinstance(original_yaml, str) 321 stream = StringIO() 322 323 save_bioimageio_yaml_only( 324 self.updated, 325 stream, 326 exclude_unset=self.exclude_unset, 327 exclude_defaults=self.exclude_defaults, 328 ) 329 updated_yaml = stream.getvalue() 330 331 diff = compare( 332 original_yaml.split("\n"), 333 updated_yaml.split("\n"), 334 diff_format=( 335 "html" 336 if isinstance(self.diff, Path) and self.diff.suffix == ".html" 337 else "unified" 338 ), 339 ) 340 341 if isinstance(self.diff, Path): 342 _ = self.diff.write_text(diff, encoding="utf-8") 343 elif self.diff: 344 console = rich.console.Console() 345 diff_md = f"## Diff\n\n````````diff\n{diff}\n````````" 346 console.print(rich.markdown.Markdown(diff_md)) 347 348 if isinstance(self.output, Path): 349 _ = self.output.write_text(updated_yaml, encoding="utf-8") 350 logger.info(f"written updated description to {self.output}") 351 elif self.output == "display": 352 updated_md = f"## Updated bioimageio.yaml\n\n```yaml\n{updated_yaml}\n```" 353 rich.console.Console().print(rich.markdown.Markdown(updated_md)) 354 elif self.output == "stdout": 355 print(updated_yaml) 356 else: 357 assert_never(self.output) 358 359 if isinstance(self.updated, InvalidDescr): 360 logger.warning("Update resulted in invalid description") 361 _ = self.updated.validation_summary.display()
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Inherited Members
364class UpdateFormatCmd(UpdateCmdBase): 365 """Update the metadata format to the latest format version.""" 366 367 exclude_defaults: bool = Field(True, alias="exclude-defaults") 368 """Exclude fields that have the default value (even if set explicitly). 369 370 Note: 371 The update process sets most unset fields explicitly with their default value. 372 """ 373 374 perform_io_checks: bool = Field( 375 settings.perform_io_checks, alias="perform-io-checks" 376 ) 377 """Wether or not to attempt validation that may require file download. 378 If `True` file hash values are added if not present.""" 379 380 @cached_property 381 def updated(self): 382 return update_format( 383 self.source, 384 exclude_defaults=self.exclude_defaults, 385 perform_io_checks=self.perform_io_checks, 386 )
Update the metadata format to the latest format version.
Exclude fields that have the default value (even if set explicitly).
Note:
The update process sets most unset fields explicitly with their default value.
Wether or not to attempt validation that may require file download.
If True
file hash values are added if not present.
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Inherited Members
389class UpdateHashesCmd(UpdateCmdBase): 390 """Create a bioimageio.yaml description with updated file hashes.""" 391 392 @cached_property 393 def updated(self): 394 return update_hashes(self.source)
Create a bioimageio.yaml description with updated file hashes.
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Inherited Members
397class PredictCmd(CmdBase, WithSource): 398 """Run inference on your data with a bioimage.io model.""" 399 400 inputs: NotEmpty[List[Union[str, NotEmpty[List[str]]]]] = Field( 401 default_factory=lambda: ["{input_id}/001.tif"] 402 ) 403 """Model input sample paths (for each input tensor) 404 405 The input paths are expected to have shape... 406 - (n_samples,) or (n_samples,1) for models expecting a single input tensor 407 - (n_samples,) containing the substring '{input_id}', or 408 - (n_samples, n_model_inputs) to provide each input tensor path explicitly. 409 410 All substrings that are replaced by metadata from the model description: 411 - '{model_id}' 412 - '{input_id}' 413 414 Example inputs to process sample 'a' and 'b' 415 for a model expecting a 'raw' and a 'mask' input tensor: 416 --inputs="[[\\"a_raw.tif\\",\\"a_mask.tif\\"],[\\"b_raw.tif\\",\\"b_mask.tif\\"]]" 417 (Note that JSON double quotes need to be escaped.) 418 419 Alternatively a `bioimageio-cli.yaml` (or `bioimageio-cli.json`) file 420 may provide the arguments, e.g.: 421 ```yaml 422 inputs: 423 - [a_raw.tif, a_mask.tif] 424 - [b_raw.tif, b_mask.tif] 425 ``` 426 427 `.npy` and any file extension supported by imageio are supported. 428 Aavailable formats are listed at 429 https://imageio.readthedocs.io/en/stable/formats/index.html#all-formats. 430 Some formats have additional dependencies. 431 432 433 """ 434 435 outputs: Union[str, NotEmpty[Tuple[str, ...]]] = ( 436 "outputs_{model_id}/{output_id}/{sample_id}.tif" 437 ) 438 """Model output path pattern (per output tensor) 439 440 All substrings that are replaced: 441 - '{model_id}' (from model description) 442 - '{output_id}' (from model description) 443 - '{sample_id}' (extracted from input paths) 444 445 446 """ 447 448 overwrite: bool = False 449 """allow overwriting existing output files""" 450 451 blockwise: bool = False 452 """process inputs blockwise""" 453 454 stats: Path = Path("dataset_statistics.json") 455 """path to dataset statistics 456 (will be written if it does not exist, 457 but the model requires statistical dataset measures) 458 """ 459 460 preview: bool = False 461 """preview which files would be processed 462 and what outputs would be generated.""" 463 464 weight_format: WeightFormatArgAny = Field( 465 "any", 466 alias="weight-format", 467 validation_alias=WEIGHT_FORMAT_ALIASES, 468 ) 469 """The weight format to use.""" 470 471 example: bool = False 472 """generate and run an example 473 474 1. downloads example model inputs 475 2. creates a `{model_id}_example` folder 476 3. writes input arguments to `{model_id}_example/bioimageio-cli.yaml` 477 4. executes a preview dry-run 478 5. executes prediction with example input 479 480 481 """ 482 483 def _example(self): 484 model_descr = ensure_description_is_model(self.descr) 485 input_ids = get_member_ids(model_descr.inputs) 486 example_inputs = ( 487 model_descr.sample_inputs 488 if isinstance(model_descr, v0_4.ModelDescr) 489 else [ 490 t 491 for ipt in model_descr.inputs 492 if (t := ipt.sample_tensor or ipt.test_tensor) 493 ] 494 ) 495 if not example_inputs: 496 raise ValueError(f"{self.descr_id} does not specify any example inputs.") 497 498 inputs001: List[str] = [] 499 example_path = Path(f"{self.descr_id}_example") 500 example_path.mkdir(exist_ok=True) 501 502 for t, src in zip(input_ids, example_inputs): 503 reader = get_reader(src) 504 dst = Path(f"{example_path}/{t}/001{reader.suffix}") 505 dst.parent.mkdir(parents=True, exist_ok=True) 506 inputs001.append(dst.as_posix()) 507 with dst.open("wb") as f: 508 shutil.copyfileobj(reader, f) 509 510 inputs = [inputs001] 511 output_pattern = f"{example_path}/outputs/{{output_id}}/{{sample_id}}.tif" 512 513 bioimageio_cli_path = example_path / YAML_FILE 514 stats_file = "dataset_statistics.json" 515 stats = (example_path / stats_file).as_posix() 516 cli_example_args = dict( 517 inputs=inputs, 518 outputs=output_pattern, 519 stats=stats_file, 520 blockwise=self.blockwise, 521 ) 522 assert is_yaml_value(cli_example_args), cli_example_args 523 write_yaml( 524 cli_example_args, 525 bioimageio_cli_path, 526 ) 527 528 yaml_file_content = None 529 530 # escaped double quotes 531 inputs_json = json.dumps(inputs) 532 inputs_escaped = inputs_json.replace('"', r"\"") 533 source_escaped = self.source.replace('"', r"\"") 534 535 def get_example_command(preview: bool, escape: bool = False): 536 q: str = '"' if escape else "" 537 538 return [ 539 "bioimageio", 540 "predict", 541 # --no-preview not supported for py=3.8 542 *(["--preview"] if preview else []), 543 "--overwrite", 544 *(["--blockwise"] if self.blockwise else []), 545 f"--stats={q}{stats}{q}", 546 f"--inputs={q}{inputs_escaped if escape else inputs_json}{q}", 547 f"--outputs={q}{output_pattern}{q}", 548 f"{q}{source_escaped if escape else self.source}{q}", 549 ] 550 551 if Path(YAML_FILE).exists(): 552 logger.info( 553 "temporarily removing '{}' to execute example prediction", YAML_FILE 554 ) 555 yaml_file_content = Path(YAML_FILE).read_bytes() 556 Path(YAML_FILE).unlink() 557 558 try: 559 _ = subprocess.run(get_example_command(True), check=True) 560 _ = subprocess.run(get_example_command(False), check=True) 561 finally: 562 if yaml_file_content is not None: 563 _ = Path(YAML_FILE).write_bytes(yaml_file_content) 564 logger.debug("restored '{}'", YAML_FILE) 565 566 print( 567 "🎉 Sucessfully ran example prediction!\n" 568 + "To predict the example input using the CLI example config file" 569 + f" {example_path / YAML_FILE}, execute `bioimageio predict` from {example_path}:\n" 570 + f"$ cd {str(example_path)}\n" 571 + f'$ bioimageio predict "{source_escaped}"\n\n' 572 + "Alternatively run the following command" 573 + " in the current workind directory, not the example folder:\n$ " 574 + " ".join(get_example_command(False, escape=True)) 575 + f"\n(note that a local '{JSON_FILE}' or '{YAML_FILE}' may interfere with this)" 576 ) 577 578 def run(self): 579 if self.example: 580 return self._example() 581 582 model_descr = ensure_description_is_model(self.descr) 583 584 input_ids = get_member_ids(model_descr.inputs) 585 output_ids = get_member_ids(model_descr.outputs) 586 587 minimum_input_ids = tuple( 588 str(ipt.id) if isinstance(ipt, v0_5.InputTensorDescr) else str(ipt.name) 589 for ipt in model_descr.inputs 590 if not isinstance(ipt, v0_5.InputTensorDescr) or not ipt.optional 591 ) 592 maximum_input_ids = tuple( 593 str(ipt.id) if isinstance(ipt, v0_5.InputTensorDescr) else str(ipt.name) 594 for ipt in model_descr.inputs 595 ) 596 597 def expand_inputs(i: int, ipt: Union[str, Sequence[str]]) -> Tuple[str, ...]: 598 if isinstance(ipt, str): 599 ipts = tuple( 600 ipt.format(model_id=self.descr_id, input_id=t) for t in input_ids 601 ) 602 else: 603 ipts = tuple( 604 p.format(model_id=self.descr_id, input_id=t) 605 for t, p in zip(input_ids, ipt) 606 ) 607 608 if len(set(ipts)) < len(ipts): 609 if len(minimum_input_ids) == len(maximum_input_ids): 610 n = len(minimum_input_ids) 611 else: 612 n = f"{len(minimum_input_ids)}-{len(maximum_input_ids)}" 613 614 raise ValueError( 615 f"[input sample #{i}] Include '{{input_id}}' in path pattern or explicitly specify {n} distinct input paths (got {ipt})" 616 ) 617 618 if len(ipts) < len(minimum_input_ids): 619 raise ValueError( 620 f"[input sample #{i}] Expected at least {len(minimum_input_ids)} inputs {minimum_input_ids}, got {ipts}" 621 ) 622 623 if len(ipts) > len(maximum_input_ids): 624 raise ValueError( 625 f"Expected at most {len(maximum_input_ids)} inputs {maximum_input_ids}, got {ipts}" 626 ) 627 628 return ipts 629 630 inputs = [expand_inputs(i, ipt) for i, ipt in enumerate(self.inputs, start=1)] 631 632 sample_paths_in = [ 633 {t: Path(p) for t, p in zip(input_ids, ipts)} for ipts in inputs 634 ] 635 636 sample_ids = _get_sample_ids(sample_paths_in) 637 638 def expand_outputs(): 639 if isinstance(self.outputs, str): 640 outputs = [ 641 tuple( 642 Path( 643 self.outputs.format( 644 model_id=self.descr_id, output_id=t, sample_id=s 645 ) 646 ) 647 for t in output_ids 648 ) 649 for s in sample_ids 650 ] 651 else: 652 outputs = [ 653 tuple( 654 Path(p.format(model_id=self.descr_id, output_id=t, sample_id=s)) 655 for t, p in zip(output_ids, self.outputs) 656 ) 657 for s in sample_ids 658 ] 659 660 for i, out in enumerate(outputs, start=1): 661 if len(set(out)) < len(out): 662 raise ValueError( 663 f"[output sample #{i}] Include '{{output_id}}' in path pattern or explicitly specify {len(output_ids)} distinct output paths (got {out})" 664 ) 665 666 if len(out) != len(output_ids): 667 raise ValueError( 668 f"[output sample #{i}] Expected {len(output_ids)} outputs {output_ids}, got {out}" 669 ) 670 671 return outputs 672 673 outputs = expand_outputs() 674 675 sample_paths_out = [ 676 {MemberId(t): Path(p) for t, p in zip(output_ids, out)} for out in outputs 677 ] 678 679 if not self.overwrite: 680 for sample_paths in sample_paths_out: 681 for p in sample_paths.values(): 682 if p.exists(): 683 raise FileExistsError( 684 f"{p} already exists. use --overwrite to (re-)write outputs anyway." 685 ) 686 if self.preview: 687 print("🛈 bioimageio prediction preview structure:") 688 pprint( 689 { 690 "{sample_id}": dict( 691 inputs={"{input_id}": "<input path>"}, 692 outputs={"{output_id}": "<output path>"}, 693 ) 694 } 695 ) 696 print("🔎 bioimageio prediction preview output:") 697 pprint( 698 { 699 s: dict( 700 inputs={t: p.as_posix() for t, p in sp_in.items()}, 701 outputs={t: p.as_posix() for t, p in sp_out.items()}, 702 ) 703 for s, sp_in, sp_out in zip( 704 sample_ids, sample_paths_in, sample_paths_out 705 ) 706 } 707 ) 708 return 709 710 def input_dataset(stat: Stat): 711 for s, sp_in in zip(sample_ids, sample_paths_in): 712 yield load_sample_for_model( 713 model=model_descr, 714 paths=sp_in, 715 stat=stat, 716 sample_id=s, 717 ) 718 719 stat: Dict[Measure, MeasureValue] = dict( 720 _get_stat( 721 model_descr, input_dataset({}), len(sample_ids), self.stats 722 ).items() 723 ) 724 725 pp = create_prediction_pipeline( 726 model_descr, 727 weight_format=None if self.weight_format == "any" else self.weight_format, 728 ) 729 predict_method = ( 730 pp.predict_sample_with_blocking 731 if self.blockwise 732 else pp.predict_sample_without_blocking 733 ) 734 735 for sample_in, sp_out in tqdm( 736 zip(input_dataset(dict(stat)), sample_paths_out), 737 total=len(inputs), 738 desc=f"predict with {self.descr_id}", 739 unit="sample", 740 ): 741 sample_out = predict_method(sample_in) 742 save_sample(sp_out, sample_out)
Run inference on your data with a bioimage.io model.
Model input sample paths (for each input tensor)
The input paths are expected to have shape...
- (n_samples,) or (n_samples,1) for models expecting a single input tensor
- (n_samples,) containing the substring '{input_id}', or
- (n_samples, n_model_inputs) to provide each input tensor path explicitly.
All substrings that are replaced by metadata from the model description:
- '{model_id}'
- '{input_id}'
Example inputs to process sample 'a' and 'b' for a model expecting a 'raw' and a 'mask' input tensor: --inputs="[[\"a_raw.tif\",\"a_mask.tif\"],[\"b_raw.tif\",\"b_mask.tif\"]]" (Note that JSON double quotes need to be escaped.)
Alternatively a bioimageio-cli.yaml
(or bioimageio-cli.json
) file
may provide the arguments, e.g.:
inputs:
- [a_raw.tif, a_mask.tif]
- [b_raw.tif, b_mask.tif]
.npy
and any file extension supported by imageio are supported.
Aavailable formats are listed at
https://imageio.readthedocs.io/en/stable/formats/index.html#all-formats.
Some formats have additional dependencies.
Model output path pattern (per output tensor)
All substrings that are replaced:
- '{model_id}' (from model description)
- '{output_id}' (from model description)
- '{sample_id}' (extracted from input paths)
path to dataset statistics (will be written if it does not exist, but the model requires statistical dataset measures)
The weight format to use.
generate and run an example
- downloads example model inputs
- creates a
{model_id}_example
folder - writes input arguments to
{model_id}_example/bioimageio-cli.yaml
- executes a preview dry-run
- executes prediction with example input
578 def run(self): 579 if self.example: 580 return self._example() 581 582 model_descr = ensure_description_is_model(self.descr) 583 584 input_ids = get_member_ids(model_descr.inputs) 585 output_ids = get_member_ids(model_descr.outputs) 586 587 minimum_input_ids = tuple( 588 str(ipt.id) if isinstance(ipt, v0_5.InputTensorDescr) else str(ipt.name) 589 for ipt in model_descr.inputs 590 if not isinstance(ipt, v0_5.InputTensorDescr) or not ipt.optional 591 ) 592 maximum_input_ids = tuple( 593 str(ipt.id) if isinstance(ipt, v0_5.InputTensorDescr) else str(ipt.name) 594 for ipt in model_descr.inputs 595 ) 596 597 def expand_inputs(i: int, ipt: Union[str, Sequence[str]]) -> Tuple[str, ...]: 598 if isinstance(ipt, str): 599 ipts = tuple( 600 ipt.format(model_id=self.descr_id, input_id=t) for t in input_ids 601 ) 602 else: 603 ipts = tuple( 604 p.format(model_id=self.descr_id, input_id=t) 605 for t, p in zip(input_ids, ipt) 606 ) 607 608 if len(set(ipts)) < len(ipts): 609 if len(minimum_input_ids) == len(maximum_input_ids): 610 n = len(minimum_input_ids) 611 else: 612 n = f"{len(minimum_input_ids)}-{len(maximum_input_ids)}" 613 614 raise ValueError( 615 f"[input sample #{i}] Include '{{input_id}}' in path pattern or explicitly specify {n} distinct input paths (got {ipt})" 616 ) 617 618 if len(ipts) < len(minimum_input_ids): 619 raise ValueError( 620 f"[input sample #{i}] Expected at least {len(minimum_input_ids)} inputs {minimum_input_ids}, got {ipts}" 621 ) 622 623 if len(ipts) > len(maximum_input_ids): 624 raise ValueError( 625 f"Expected at most {len(maximum_input_ids)} inputs {maximum_input_ids}, got {ipts}" 626 ) 627 628 return ipts 629 630 inputs = [expand_inputs(i, ipt) for i, ipt in enumerate(self.inputs, start=1)] 631 632 sample_paths_in = [ 633 {t: Path(p) for t, p in zip(input_ids, ipts)} for ipts in inputs 634 ] 635 636 sample_ids = _get_sample_ids(sample_paths_in) 637 638 def expand_outputs(): 639 if isinstance(self.outputs, str): 640 outputs = [ 641 tuple( 642 Path( 643 self.outputs.format( 644 model_id=self.descr_id, output_id=t, sample_id=s 645 ) 646 ) 647 for t in output_ids 648 ) 649 for s in sample_ids 650 ] 651 else: 652 outputs = [ 653 tuple( 654 Path(p.format(model_id=self.descr_id, output_id=t, sample_id=s)) 655 for t, p in zip(output_ids, self.outputs) 656 ) 657 for s in sample_ids 658 ] 659 660 for i, out in enumerate(outputs, start=1): 661 if len(set(out)) < len(out): 662 raise ValueError( 663 f"[output sample #{i}] Include '{{output_id}}' in path pattern or explicitly specify {len(output_ids)} distinct output paths (got {out})" 664 ) 665 666 if len(out) != len(output_ids): 667 raise ValueError( 668 f"[output sample #{i}] Expected {len(output_ids)} outputs {output_ids}, got {out}" 669 ) 670 671 return outputs 672 673 outputs = expand_outputs() 674 675 sample_paths_out = [ 676 {MemberId(t): Path(p) for t, p in zip(output_ids, out)} for out in outputs 677 ] 678 679 if not self.overwrite: 680 for sample_paths in sample_paths_out: 681 for p in sample_paths.values(): 682 if p.exists(): 683 raise FileExistsError( 684 f"{p} already exists. use --overwrite to (re-)write outputs anyway." 685 ) 686 if self.preview: 687 print("🛈 bioimageio prediction preview structure:") 688 pprint( 689 { 690 "{sample_id}": dict( 691 inputs={"{input_id}": "<input path>"}, 692 outputs={"{output_id}": "<output path>"}, 693 ) 694 } 695 ) 696 print("🔎 bioimageio prediction preview output:") 697 pprint( 698 { 699 s: dict( 700 inputs={t: p.as_posix() for t, p in sp_in.items()}, 701 outputs={t: p.as_posix() for t, p in sp_out.items()}, 702 ) 703 for s, sp_in, sp_out in zip( 704 sample_ids, sample_paths_in, sample_paths_out 705 ) 706 } 707 ) 708 return 709 710 def input_dataset(stat: Stat): 711 for s, sp_in in zip(sample_ids, sample_paths_in): 712 yield load_sample_for_model( 713 model=model_descr, 714 paths=sp_in, 715 stat=stat, 716 sample_id=s, 717 ) 718 719 stat: Dict[Measure, MeasureValue] = dict( 720 _get_stat( 721 model_descr, input_dataset({}), len(sample_ids), self.stats 722 ).items() 723 ) 724 725 pp = create_prediction_pipeline( 726 model_descr, 727 weight_format=None if self.weight_format == "any" else self.weight_format, 728 ) 729 predict_method = ( 730 pp.predict_sample_with_blocking 731 if self.blockwise 732 else pp.predict_sample_without_blocking 733 ) 734 735 for sample_in, sp_out in tqdm( 736 zip(input_dataset(dict(stat)), sample_paths_out), 737 total=len(inputs), 738 desc=f"predict with {self.descr_id}", 739 unit="sample", 740 ): 741 sample_out = predict_method(sample_in) 742 save_sample(sp_out, sample_out)
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Inherited Members
745class AddWeightsCmd(CmdBase, WithSource, WithSummaryLogging): 746 output: CliPositionalArg[Path] 747 """The path to write the updated model package to.""" 748 749 source_format: Optional[SupportedWeightsFormat] = Field(None, alias="source-format") 750 """Exclusively use these weights to convert to other formats.""" 751 752 target_format: Optional[SupportedWeightsFormat] = Field(None, alias="target-format") 753 """Exclusively add this weight format.""" 754 755 verbose: bool = False 756 """Log more (error) output.""" 757 758 tracing: bool = True 759 """Allow tracing when converting pytorch_state_dict to torchscript 760 (still uses scripting if possible).""" 761 762 def run(self): 763 model_descr = ensure_description_is_model(self.descr) 764 if isinstance(model_descr, v0_4.ModelDescr): 765 raise TypeError( 766 f"model format {model_descr.format_version} not supported." 767 + " Please update the model first." 768 ) 769 updated_model_descr = add_weights( 770 model_descr, 771 output_path=self.output, 772 source_format=self.source_format, 773 target_format=self.target_format, 774 verbose=self.verbose, 775 allow_tracing=self.tracing, 776 ) 777 self.log(updated_model_descr)
!!! abstract "Usage Documentation" Models
A base class for creating Pydantic models.
Attributes:
- __class_vars__: The names of the class variables defined on the model.
- __private_attributes__: Metadata about the private attributes of the model.
- __signature__: The synthesized
__init__
[Signature
][inspect.Signature] of the model. - __pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
- __pydantic_core_schema__: The core schema of the model.
- __pydantic_custom_init__: Whether the model has a custom
__init__
function. - __pydantic_decorators__: Metadata containing the decorators defined on the model.
This replaces
Model.__validators__
andModel.__root_validators__
from Pydantic V1. - __pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
- __pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
- __pydantic_post_init__: The name of the post-init method for the model, if defined.
- __pydantic_root_model__: Whether the model is a [
RootModel
][pydantic.root_model.RootModel]. - __pydantic_serializer__: The
pydantic-core
SchemaSerializer
used to dump instances of the model. - __pydantic_validator__: The
pydantic-core
SchemaValidator
used to validate instances of the model. - __pydantic_fields__: A dictionary of field names and their corresponding [
FieldInfo
][pydantic.fields.FieldInfo] objects. - __pydantic_computed_fields__: A dictionary of computed field names and their corresponding [
ComputedFieldInfo
][pydantic.fields.ComputedFieldInfo] objects. - __pydantic_extra__: A dictionary containing extra values, if [
extra
][pydantic.config.ConfigDict.extra] is set to'allow'
. - __pydantic_fields_set__: The names of fields explicitly set during instantiation.
- __pydantic_private__: Values of private attributes set on the model instance.
The path to write the updated model package to.
Exclusively use these weights to convert to other formats.
Exclusively add this weight format.
Allow tracing when converting pytorch_state_dict to torchscript (still uses scripting if possible).
762 def run(self): 763 model_descr = ensure_description_is_model(self.descr) 764 if isinstance(model_descr, v0_4.ModelDescr): 765 raise TypeError( 766 f"model format {model_descr.format_version} not supported." 767 + " Please update the model first." 768 ) 769 updated_model_descr = add_weights( 770 model_descr, 771 output_path=self.output, 772 source_format=self.source_format, 773 target_format=self.target_format, 774 verbose=self.verbose, 775 allow_tracing=self.tracing, 776 ) 777 self.log(updated_model_descr)
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Inherited Members
784class Bioimageio( 785 BaseSettings, 786 cli_implicit_flags=True, 787 cli_parse_args=True, 788 cli_prog_name="bioimageio", 789 cli_use_class_docs_for_groups=True, 790 use_attribute_docstrings=True, 791): 792 """bioimageio - CLI for bioimage.io resources 🦒""" 793 794 model_config = SettingsConfigDict( 795 json_file=JSON_FILE, 796 yaml_file=YAML_FILE, 797 ) 798 799 validate_format: CliSubCommand[ValidateFormatCmd] = Field(alias="validate-format") 800 "Check a resource's metadata format" 801 802 test: CliSubCommand[TestCmd] 803 "Test a bioimageio resource (beyond meta data formatting)" 804 805 package: CliSubCommand[PackageCmd] 806 "Package a resource" 807 808 predict: CliSubCommand[PredictCmd] 809 "Predict with a model resource" 810 811 update_format: CliSubCommand[UpdateFormatCmd] = Field(alias="update-format") 812 """Update the metadata format""" 813 814 update_hashes: CliSubCommand[UpdateHashesCmd] = Field(alias="update-hashes") 815 """Create a bioimageio.yaml description with updated file hashes.""" 816 817 add_weights: CliSubCommand[AddWeightsCmd] = Field(alias="add-weights") 818 """Add additional weights to the model descriptions converted from available 819 formats to improve deployability.""" 820 821 @classmethod 822 def settings_customise_sources( 823 cls, 824 settings_cls: Type[BaseSettings], 825 init_settings: PydanticBaseSettingsSource, 826 env_settings: PydanticBaseSettingsSource, 827 dotenv_settings: PydanticBaseSettingsSource, 828 file_secret_settings: PydanticBaseSettingsSource, 829 ) -> Tuple[PydanticBaseSettingsSource, ...]: 830 cli: CliSettingsSource[BaseSettings] = CliSettingsSource( 831 settings_cls, 832 cli_parse_args=True, 833 formatter_class=RawTextHelpFormatter, 834 ) 835 sys_args = pformat(sys.argv) 836 logger.info("starting CLI with arguments:\n{}", sys_args) 837 return ( 838 cli, 839 init_settings, 840 YamlConfigSettingsSource(settings_cls), 841 JsonConfigSettingsSource(settings_cls), 842 ) 843 844 @model_validator(mode="before") 845 @classmethod 846 def _log(cls, data: Any): 847 logger.info( 848 "loaded CLI input:\n{}", 849 pformat({k: v for k, v in data.items() if v is not None}), 850 ) 851 return data 852 853 def run(self): 854 logger.info( 855 "executing CLI command:\n{}", 856 pformat({k: v for k, v in self.model_dump().items() if v is not None}), 857 ) 858 cmd = ( 859 self.add_weights 860 or self.package 861 or self.predict 862 or self.test 863 or self.update_format 864 or self.update_hashes 865 or self.validate_format 866 ) 867 assert cmd is not None 868 cmd.run()
bioimageio - CLI for bioimage.io resources 🦒
library versions: bioimageio.core 0.9.4 bioimageio.spec 0.5.5.6
spec format versions: model RDF 0.5.5 dataset RDF 0.3.0 notebook RDF 0.3.0
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Check a resource's metadata format
Test a bioimageio resource (beyond meta data formatting)
Package a resource
Predict with a model resource
Update the metadata format
Create a bioimageio.yaml description with updated file hashes.
Add additional weights to the model descriptions converted from available formats to improve deployability.
821 @classmethod 822 def settings_customise_sources( 823 cls, 824 settings_cls: Type[BaseSettings], 825 init_settings: PydanticBaseSettingsSource, 826 env_settings: PydanticBaseSettingsSource, 827 dotenv_settings: PydanticBaseSettingsSource, 828 file_secret_settings: PydanticBaseSettingsSource, 829 ) -> Tuple[PydanticBaseSettingsSource, ...]: 830 cli: CliSettingsSource[BaseSettings] = CliSettingsSource( 831 settings_cls, 832 cli_parse_args=True, 833 formatter_class=RawTextHelpFormatter, 834 ) 835 sys_args = pformat(sys.argv) 836 logger.info("starting CLI with arguments:\n{}", sys_args) 837 return ( 838 cli, 839 init_settings, 840 YamlConfigSettingsSource(settings_cls), 841 JsonConfigSettingsSource(settings_cls), 842 )
Define the sources and their order for loading the settings values.
Arguments:
- settings_cls: The Settings class.
- init_settings: The
InitSettingsSource
instance. - env_settings: The
EnvSettingsSource
instance. - dotenv_settings: The
DotEnvSettingsSource
instance. - file_secret_settings: The
SecretsSettingsSource
instance.
Returns:
A tuple containing the sources and their order for loading the settings values.
853 def run(self): 854 logger.info( 855 "executing CLI command:\n{}", 856 pformat({k: v for k, v in self.model_dump().items() if v is not None}), 857 ) 858 cmd = ( 859 self.add_weights 860 or self.package 861 or self.predict 862 or self.test 863 or self.update_format 864 or self.update_hashes 865 or self.validate_format 866 ) 867 assert cmd is not None 868 cmd.run()