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