Skip to content

Commit 504d845

Browse files
Merge pull request #229 from bioimage-io/update-build-model
Update build model interface
2 parents f520119 + ab4725a commit 504d845

File tree

3 files changed

+53
-33
lines changed

3 files changed

+53
-33
lines changed

bioimageio/core/build_spec/build_model.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ def _get_input_tensor(path, name, step, min_shape, data_range, axes, preprocessi
217217
data_range = _get_data_range(data_range, test_in.dtype)
218218
kwargs = {}
219219
if preprocessing is not None:
220-
kwargs["preprocessing"] = [{"name": k, "kwargs": v} for k, v in preprocessing.items()]
220+
kwargs["preprocessing"] = preprocessing
221221

222222
inputs = model_spec.raw_nodes.InputTensor(
223223
name="input" if name is None else name,
@@ -245,7 +245,7 @@ def _get_output_tensor(path, name, reference_tensor, scale, offset, axes, data_r
245245
data_range = _get_data_range(data_range, test_out.dtype)
246246
kwargs = {}
247247
if postprocessing is not None:
248-
kwargs["postprocessing"] = [{"name": k, "kwargs": v} for k, v in postprocessing.items()]
248+
kwargs["postprocessing"] = postprocessing
249249
if halo is not None:
250250
kwargs["halo"] = halo
251251

@@ -260,9 +260,16 @@ def _get_output_tensor(path, name, reference_tensor, scale, offset, axes, data_r
260260
return outputs
261261

262262

263-
# TODO The citation entry should be improved so that we can properly derive doi vs. url
264-
def _build_cite(cite: Dict[str, str]):
265-
citation_list = [spec.rdf.raw_nodes.CiteEntry(text=k, url=v) for k, v in cite.items()]
263+
def _build_cite(cite: List[Dict[str, str]]):
264+
citation_list = []
265+
for entry in cite:
266+
if "doi" in entry:
267+
spec_entry = spec.rdf.raw_nodes.CiteEntry(text=entry["text"], doi=entry["doi"])
268+
elif "url" in entry:
269+
spec_entry = spec.rdf.raw_nodes.CiteEntry(text=entry["text"], url=entry["url"])
270+
else:
271+
raise ValueError(f"Expect one of doi or url in citation enrty {entry}")
272+
citation_list.append(spec_entry)
266273
return citation_list
267274

268275

@@ -346,7 +353,7 @@ def _get_deepimagej_config(
346353
if any(preproc is not None for preproc in preprocessing):
347354
assert len(preprocessing) == 1
348355
preprocess_ij = [
349-
_get_deepimagej_macro(name, kwargs, export_folder) for name, kwargs in preprocessing[0].items()
356+
_get_deepimagej_macro(preproc["name"], preproc["kwargs"], export_folder) for preproc in preprocessing[0]
350357
]
351358
attachments = [preproc["kwargs"] for preproc in preprocess_ij]
352359
else:
@@ -356,7 +363,7 @@ def _get_deepimagej_config(
356363
if any(postproc is not None for postproc in postprocessing):
357364
assert len(postprocessing) == 1
358365
postprocess_ij = [
359-
_get_deepimagej_macro(name, kwargs, export_folder) for name, kwargs in postprocessing[0].items()
366+
_get_deepimagej_macro(postproc["name"], postproc["kwargs"], export_folder) for postproc in postprocessing[0]
360367
]
361368
if attachments is None:
362369
attachments = [postproc["kwargs"] for postproc in postprocess_ij]
@@ -595,7 +602,7 @@ def build_model(
595602
authors: List[Dict[str, str]],
596603
tags: List[Union[str, Path]],
597604
documentation: Union[str, Path],
598-
cite: Dict[str, str],
605+
cite: List[Dict[str, str]],
599606
output_path: Union[str, Path],
600607
# model specific optional
601608
architecture: Optional[str] = None,
@@ -614,8 +621,8 @@ def build_model(
614621
output_offset: Optional[List[List[int]]] = None,
615622
output_data_range: Optional[List[List[Union[int, str]]]] = None,
616623
halo: Optional[List[List[int]]] = None,
617-
preprocessing: Optional[List[Dict[str, Dict[str, Union[int, float, str]]]]] = None,
618-
postprocessing: Optional[List[Dict[str, Dict[str, Union[int, float, str]]]]] = None,
624+
preprocessing: Optional[List[List[Dict[str, Dict[str, Union[int, float, str]]]]]] = None,
625+
postprocessing: Optional[List[List[Dict[str, Dict[str, Union[int, float, str]]]]]] = None,
619626
pixel_sizes: Optional[List[Dict[str, float]]] = None,
620627
# general optional
621628
maintainers: Optional[List[Dict[str, str]]] = None,
@@ -625,7 +632,7 @@ def build_model(
625632
attachments: Optional[Dict[str, Union[str, List[str]]]] = None,
626633
packaged_by: Optional[List[str]] = None,
627634
run_mode: Optional[str] = None,
628-
parent: Optional[Tuple[str, str]] = None,
635+
parent: Optional[Dict[str, str]] = None,
629636
config: Optional[Dict[str, Any]] = None,
630637
dependencies: Optional[Union[Path, str]] = None,
631638
links: Optional[List[str]] = None,
@@ -655,7 +662,7 @@ def build_model(
655662
tags=["segmentation", "light sheet data"],
656663
license="CC-BY-4.0",
657664
documentation="./documentation.md",
658-
cite={"Architecture": "https://my_architecture.com"},
665+
cite=[{"text": "Ronneberger et al. U-Net", "doi": "10.1007/978-3-319-24574-4_28"}],
659666
output_path="my-model.zip"
660667
)
661668
```
@@ -671,7 +678,7 @@ def build_model(
671678
authors: the authors of this model.
672679
tags: list of tags for this model.
673680
documentation: relative file path to markdown documentation for this model.
674-
cite: citations for this model.
681+
cite: references for this model.
675682
output_path: where to save the zipped model package.
676683
architecture: the file with the source code for the model architecture and the corresponding class.
677684
Only required for models with pytorch_state_dict weight format.
@@ -701,7 +708,7 @@ def build_model(
701708
attachments: list of additional files to package with the model.
702709
packaged_by: list of authors that have packaged this model.
703710
run_mode: custom run mode for this model.
704-
parent: id of the parent model from which this model is derived and sha256 of the corresponding weight file.
711+
parent: id of the parent model from which this model is derived and sha256 of the corresponding rdf file.
705712
config: custom configuration for this model.
706713
dependencies: relative path to file with dependencies for this model.
707714
root: optional root path for relative paths. This can be helpful when building a spec from another model spec.
@@ -882,7 +889,7 @@ def build_model(
882889
kwargs["maintainers"] = [model_spec.raw_nodes.Maintainer(**m) for m in maintainers]
883890
if parent is not None:
884891
assert len(parent) == 2
885-
kwargs["parent"] = {"uri": parent[0], "sha256": parent[1]}
892+
kwargs["parent"] = parent
886893

887894
try:
888895
model = model_spec.raw_nodes.Model(

example/bioimageio-core-usage.ipynb

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,12 @@
6868
"# the model can be loaded using different representations:\n",
6969
"\n",
7070
"# the doi of the zenodo entry corresponding to the model\n",
71-
"rdf_doi = \"10.5072/zenodo.934248\"\n",
71+
"rdf_doi = \"10.5281/zenodo.6287342\"\n",
7272
"\n",
7373
"# the url of the yaml file containing the model resource description\n",
74-
"rdf_url = \"https://sandbox.zenodo.org/record/934248/files/rdf.yaml\"\n",
74+
"rdf_url = \"https://zenodo.org/record/6287342/files/rdf.yaml\"\n",
7575
"\n",
76+
"# FIXME the model currently does not show up on the website\n",
7677
"# filepath to the downloaded model (either zipped package or yaml)\n",
7778
"# (go to https://bioimage.io/#/?id=10.5072%2Fzenodo.881940, select the download icon and select \"ilastik\")\n",
7879
"rdf_path = \"/home/pape/Downloads/dsb-nuclei-boundarymodelnew_pytorch_state_dict.zip\""
@@ -383,24 +384,22 @@
383384
"np.save(new_output_path, new_output)\n",
384385
"\n",
385386
"# add thresholding as post-processing procedure to our model\n",
386-
"preprocessing = [\n",
387-
" {prep.name: prep.kwargs for prep in model_resource.inputs[0].preprocessing}\n",
388-
"]\n",
389-
"postprocessing = [{\"binarize\": {\"threshold\": threshold}}]\n",
387+
"preprocessing = [[{\"name\": prep.name, \"kwargs\": prep.kwargs} for prep in inp.preprocessing] for inp in model_resource.inputs]\n",
388+
"postprocessing = [[{\"name\": \"binarize\", \"kwargs\": {\"threshold\": threshold}}]]\n",
390389
"\n",
391390
"# get the model architecture\n",
392391
"# note that this is only necessary for pytorch state dict models\n",
393392
"model_source = get_architecture_source(rdf_doi)\n",
394393
"\n",
395394
"# we use the `parent` field to indicate that the new model is created based on\n",
396395
"# the nucleus segmentation model we have obtained from bioimage.io\n",
397-
"# this field is optional and only needs to be given for models that are created based on\n",
398-
"# other models from bioimage.io\n",
396+
"# this field is optional and only needs to be given for models that are created based on other models from bioimage.io\n",
399397
"# the parent is specified via it's doi and the hash of its rdf file\n",
400-
"rdf_file = os.path.join(model_resource.root_path, \"rdf.yaml\")\n",
398+
"model_root_folder = os.path.split(model_resource.weights[\"pytorch_state_dict\"].source)[0]\n",
399+
"rdf_file = os.path.join(model_root_folder, \"rdf.yaml\")\n",
401400
"with open(rdf_file, \"rb\") as f:\n",
402-
" weight_hash = hashlib.sha256(f.read()).hexdigest()\n",
403-
"parent = (rdf_doi, weight_hash)\n",
401+
" rdf_hash = hashlib.sha256(f.read()).hexdigest()\n",
402+
"parent = {\"uri\": rdf_doi, \"sha256\": rdf_hash}\n",
404403
"\n",
405404
"# the name of the new model and where to save the zipped model package\n",
406405
"name = \"new-model1\"\n",
@@ -410,7 +409,7 @@
410409
"# all this additional information is passed as plain python types and will be converted into the bioimageio representation internally \n",
411410
"# for more informantion, check out the function signature\n",
412411
"# https://github.com/bioimage-io/core-bioimage-io-python/blob/main/bioimageio/core/build_spec/build_model.py#L252\n",
413-
"cite = {cite_entry.text: cite_entry.url for cite_entry in model_resource.cite}\n",
412+
"cite = [{\"text\": cite_entry.text, \"url\": cite_entry.url} for cite_entry in model_resource.cite]\n",
414413
"\n",
415414
"# the axes descriptions for the inputs / outputs\n",
416415
"input_axes = [\"bcyx\"]\n",
@@ -516,9 +515,9 @@
516515
],
517516
"metadata": {
518517
"kernelspec": {
519-
"display_name": "Python 3",
518+
"display_name": "Python 3 (ipykernel)",
520519
"language": "python",
521-
"name": "python3.bkp"
520+
"name": "python3"
522521
},
523522
"language_info": {
524523
"codemirror_mode": {
@@ -530,7 +529,7 @@
530529
"name": "python",
531530
"nbconvert_exporter": "python",
532531
"pygments_lexer": "ipython3",
533-
"version": "3.7.11"
532+
"version": "3.9.7"
534533
}
535534
},
536535
"nbformat": 4,

tests/build_spec/test_build_spec.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,17 @@ def _test_build_spec(
2525
assert isinstance(model_spec, spec.model.raw_nodes.Model)
2626
weight_source = model_spec.weights[weight_type].source
2727

28-
cite = {entry.text: entry.doi if entry.url is missing else entry.url for entry in model_spec.cite}
28+
cite = []
29+
for entry in model_spec.cite:
30+
entry_ = {"text": entry.text}
31+
has_url = entry.url is not missing
32+
has_doi = entry.doi is not missing
33+
assert has_url != has_doi
34+
if has_doi:
35+
entry_["doi"] = entry.doi
36+
else:
37+
entry_["url"] = entry.url
38+
cite.append(entry_)
2939

3040
weight_spec = model_spec.weights[weight_type]
3141
dep_file = None if weight_spec.dependencies is missing else resolve_source(weight_spec.dependencies.file, root)
@@ -52,11 +62,15 @@ def _test_build_spec(
5262
input_axes = [input_.axes for input_ in model_spec.inputs]
5363
output_axes = [output.axes for output in model_spec.outputs]
5464
preprocessing = [
55-
None if input_.preprocessing == missing else {preproc.name: preproc.kwargs for preproc in input_.preprocessing}
65+
None
66+
if input_.preprocessing is missing
67+
else [{"name": preproc.name, "kwargs": preproc.kwargs} for preproc in input_.preprocessing]
5668
for input_ in model_spec.inputs
5769
]
5870
postprocessing = [
59-
None if output.postprocessing == missing else {preproc.name: preproc.kwargs for preproc in output.preprocessing}
71+
None
72+
if output.postprocessing is missing
73+
else [{"name": preproc.name, "kwargs": preproc.kwargs} for preproc in output.preprocessing]
6074
for output in model_spec.outputs
6175
]
6276

0 commit comments

Comments
 (0)