diff --git a/bioimageio/core.html b/bioimageio/core.html index 58ed3651..45ec3f00 100644 --- a/bioimageio/core.html +++ b/bioimageio/core.html @@ -344,25 +344,25 @@

0.5.10

-
50def test_description(
-51    source: Union[ResourceDescr, PermissiveFileSource, BioimageioYamlContent],
-52    *,
-53    format_version: Union[Literal["discover", "latest"], str] = "discover",
-54    weight_format: Optional[WeightsFormat] = None,
-55    devices: Optional[List[str]] = None,
-56    decimal: int = 4,
-57    expected_type: Optional[str] = None,
-58) -> ValidationSummary:
-59    """Test a bioimage.io resource dynamically, e.g. prediction of test tensors for models"""
-60    rd = load_description_and_test(
-61        source,
-62        format_version=format_version,
-63        weight_format=weight_format,
-64        devices=devices,
-65        decimal=decimal,
-66        expected_type=expected_type,
-67    )
-68    return rd.validation_summary
+            
51def test_description(
+52    source: Union[ResourceDescr, PermissiveFileSource, BioimageioYamlContent],
+53    *,
+54    format_version: Union[Literal["discover", "latest"], str] = "discover",
+55    weight_format: Optional[WeightsFormat] = None,
+56    devices: Optional[List[str]] = None,
+57    decimal: int = 4,
+58    expected_type: Optional[str] = None,
+59) -> ValidationSummary:
+60    """Test a bioimage.io resource dynamically, e.g. prediction of test tensors for models"""
+61    rd = load_description_and_test(
+62        source,
+63        format_version=format_version,
+64        weight_format=weight_format,
+65        devices=devices,
+66        decimal=decimal,
+67        expected_type=expected_type,
+68    )
+69    return rd.validation_summary
 
diff --git a/bioimageio/core/_resource_tests.html b/bioimageio/core/_resource_tests.html index 6e293ac4..455485e6 100644 --- a/bioimageio/core/_resource_tests.html +++ b/bioimageio/core/_resource_tests.html @@ -65,431 +65,439 @@

  1import traceback
   2import warnings
-  3from typing import Dict, Hashable, List, Literal, Optional, Set, Tuple, Union
-  4
-  5import numpy as np
-  6from loguru import logger
-  7
-  8from bioimageio.core.sample import Sample
-  9from bioimageio.spec import (
- 10    InvalidDescr,
- 11    ResourceDescr,
- 12    build_description,
- 13    dump_description,
- 14    load_description,
- 15)
- 16from bioimageio.spec._internal.common_nodes import ResourceDescrBase
- 17from bioimageio.spec.common import BioimageioYamlContent, PermissiveFileSource
- 18from bioimageio.spec.model import v0_4, v0_5
- 19from bioimageio.spec.model.v0_5 import WeightsFormat
- 20from bioimageio.spec.summary import (
- 21    ErrorEntry,
- 22    InstalledPackage,
- 23    ValidationDetail,
- 24    ValidationSummary,
- 25)
- 26
- 27from ._prediction_pipeline import create_prediction_pipeline
- 28from .axis import AxisId, BatchSize
- 29from .digest_spec import get_test_inputs, get_test_outputs
- 30from .utils import VERSION
- 31
+  3from itertools import product
+  4from typing import Dict, Hashable, List, Literal, Optional, Set, Tuple, Union
+  5
+  6import numpy as np
+  7from loguru import logger
+  8
+  9from bioimageio.core.sample import Sample
+ 10from bioimageio.spec import (
+ 11    InvalidDescr,
+ 12    ResourceDescr,
+ 13    build_description,
+ 14    dump_description,
+ 15    load_description,
+ 16)
+ 17from bioimageio.spec._internal.common_nodes import ResourceDescrBase
+ 18from bioimageio.spec.common import BioimageioYamlContent, PermissiveFileSource
+ 19from bioimageio.spec.model import v0_4, v0_5
+ 20from bioimageio.spec.model.v0_5 import WeightsFormat
+ 21from bioimageio.spec.summary import (
+ 22    ErrorEntry,
+ 23    InstalledPackage,
+ 24    ValidationDetail,
+ 25    ValidationSummary,
+ 26)
+ 27
+ 28from ._prediction_pipeline import create_prediction_pipeline
+ 29from .axis import AxisId, BatchSize
+ 30from .digest_spec import get_test_inputs, get_test_outputs
+ 31from .utils import VERSION
  32
- 33def test_model(
- 34    source: Union[v0_5.ModelDescr, PermissiveFileSource],
- 35    weight_format: Optional[WeightsFormat] = None,
- 36    devices: Optional[List[str]] = None,
- 37    decimal: int = 4,
- 38) -> ValidationSummary:
- 39    """Test model inference"""
- 40    return test_description(
- 41        source,
- 42        weight_format=weight_format,
- 43        devices=devices,
- 44        decimal=decimal,
- 45        expected_type="model",
- 46    )
- 47
+ 33
+ 34def test_model(
+ 35    source: Union[v0_5.ModelDescr, PermissiveFileSource],
+ 36    weight_format: Optional[WeightsFormat] = None,
+ 37    devices: Optional[List[str]] = None,
+ 38    decimal: int = 4,
+ 39) -> ValidationSummary:
+ 40    """Test model inference"""
+ 41    return test_description(
+ 42        source,
+ 43        weight_format=weight_format,
+ 44        devices=devices,
+ 45        decimal=decimal,
+ 46        expected_type="model",
+ 47    )
  48
- 49def test_description(
- 50    source: Union[ResourceDescr, PermissiveFileSource, BioimageioYamlContent],
- 51    *,
- 52    format_version: Union[Literal["discover", "latest"], str] = "discover",
- 53    weight_format: Optional[WeightsFormat] = None,
- 54    devices: Optional[List[str]] = None,
- 55    decimal: int = 4,
- 56    expected_type: Optional[str] = None,
- 57) -> ValidationSummary:
- 58    """Test a bioimage.io resource dynamically, e.g. prediction of test tensors for models"""
- 59    rd = load_description_and_test(
- 60        source,
- 61        format_version=format_version,
- 62        weight_format=weight_format,
- 63        devices=devices,
- 64        decimal=decimal,
- 65        expected_type=expected_type,
- 66    )
- 67    return rd.validation_summary
- 68
+ 49
+ 50def test_description(
+ 51    source: Union[ResourceDescr, PermissiveFileSource, BioimageioYamlContent],
+ 52    *,
+ 53    format_version: Union[Literal["discover", "latest"], str] = "discover",
+ 54    weight_format: Optional[WeightsFormat] = None,
+ 55    devices: Optional[List[str]] = None,
+ 56    decimal: int = 4,
+ 57    expected_type: Optional[str] = None,
+ 58) -> ValidationSummary:
+ 59    """Test a bioimage.io resource dynamically, e.g. prediction of test tensors for models"""
+ 60    rd = load_description_and_test(
+ 61        source,
+ 62        format_version=format_version,
+ 63        weight_format=weight_format,
+ 64        devices=devices,
+ 65        decimal=decimal,
+ 66        expected_type=expected_type,
+ 67    )
+ 68    return rd.validation_summary
  69
- 70def load_description_and_test(
- 71    source: Union[ResourceDescr, PermissiveFileSource, BioimageioYamlContent],
- 72    *,
- 73    format_version: Union[Literal["discover", "latest"], str] = "discover",
- 74    weight_format: Optional[WeightsFormat] = None,
- 75    devices: Optional[List[str]] = None,
- 76    decimal: int = 4,
- 77    expected_type: Optional[str] = None,
- 78) -> Union[ResourceDescr, InvalidDescr]:
- 79    """Test RDF dynamically, e.g. model inference of test inputs"""
- 80    if (
- 81        isinstance(source, ResourceDescrBase)
- 82        and format_version != "discover"
- 83        and source.format_version != format_version
- 84    ):
- 85        warnings.warn(
- 86            f"deserializing source to ensure we validate and test using format {format_version}"
- 87        )
- 88        source = dump_description(source)
- 89
- 90    if isinstance(source, ResourceDescrBase):
- 91        rd = source
- 92    elif isinstance(source, dict):
- 93        rd = build_description(source, format_version=format_version)
- 94    else:
- 95        rd = load_description(source, format_version=format_version)
- 96
- 97    rd.validation_summary.env.append(
- 98        InstalledPackage(name="bioimageio.core", version=VERSION)
- 99    )
-100
-101    if expected_type is not None:
-102        _test_expected_resource_type(rd, expected_type)
-103
-104    if isinstance(rd, (v0_4.ModelDescr, v0_5.ModelDescr)):
-105        _test_model_inference(rd, weight_format, devices, decimal)
-106        if not isinstance(rd, v0_4.ModelDescr):
-107            _test_model_inference_parametrized(rd, weight_format, devices)
-108
-109    # TODO: add execution of jupyter notebooks
-110    # TODO: add more tests
-111
-112    return rd
-113
+ 70
+ 71def load_description_and_test(
+ 72    source: Union[ResourceDescr, PermissiveFileSource, BioimageioYamlContent],
+ 73    *,
+ 74    format_version: Union[Literal["discover", "latest"], str] = "discover",
+ 75    weight_format: Optional[WeightsFormat] = None,
+ 76    devices: Optional[List[str]] = None,
+ 77    decimal: int = 4,
+ 78    expected_type: Optional[str] = None,
+ 79) -> Union[ResourceDescr, InvalidDescr]:
+ 80    """Test RDF dynamically, e.g. model inference of test inputs"""
+ 81    if (
+ 82        isinstance(source, ResourceDescrBase)
+ 83        and format_version != "discover"
+ 84        and source.format_version != format_version
+ 85    ):
+ 86        warnings.warn(
+ 87            f"deserializing source to ensure we validate and test using format {format_version}"
+ 88        )
+ 89        source = dump_description(source)
+ 90
+ 91    if isinstance(source, ResourceDescrBase):
+ 92        rd = source
+ 93    elif isinstance(source, dict):
+ 94        rd = build_description(source, format_version=format_version)
+ 95    else:
+ 96        rd = load_description(source, format_version=format_version)
+ 97
+ 98    rd.validation_summary.env.append(
+ 99        InstalledPackage(name="bioimageio.core", version=VERSION)
+100    )
+101
+102    if expected_type is not None:
+103        _test_expected_resource_type(rd, expected_type)
+104
+105    if isinstance(rd, (v0_4.ModelDescr, v0_5.ModelDescr)):
+106        _test_model_inference(rd, weight_format, devices, decimal)
+107        if not isinstance(rd, v0_4.ModelDescr):
+108            _test_model_inference_parametrized(rd, weight_format, devices)
+109
+110    # TODO: add execution of jupyter notebooks
+111    # TODO: add more tests
+112
+113    return rd
 114
-115def _test_model_inference(
-116    model: Union[v0_4.ModelDescr, v0_5.ModelDescr],
-117    weight_format: Optional[WeightsFormat],
-118    devices: Optional[List[str]],
-119    decimal: int,
-120) -> None:
-121    test_name = "Reproduce test outputs from test inputs"
-122    logger.info("starting '{}'", test_name)
-123    error: Optional[str] = None
-124    tb: List[str] = []
-125    try:
-126        inputs = get_test_inputs(model)
-127        expected = get_test_outputs(model)
-128
-129        with create_prediction_pipeline(
-130            bioimageio_model=model, devices=devices, weight_format=weight_format
-131        ) as prediction_pipeline:
-132            results = prediction_pipeline.predict_sample_without_blocking(inputs)
-133
-134        if len(results.members) != len(expected.members):
-135            error = f"Expected {len(expected.members)} outputs, but got {len(results.members)}"
-136
-137        else:
-138            for m, exp in expected.members.items():
-139                res = results.members.get(m)
-140                if res is None:
-141                    error = "Output tensors for test case may not be None"
-142                    break
-143                try:
-144                    np.testing.assert_array_almost_equal(
-145                        res.data, exp.data, decimal=decimal
-146                    )
-147                except AssertionError as e:
-148                    error = f"Output and expected output disagree:\n {e}"
-149                    break
-150    except Exception as e:
-151        error = str(e)
-152        tb = traceback.format_tb(e.__traceback__)
-153
-154    model.validation_summary.add_detail(
-155        ValidationDetail(
-156            name=test_name,
-157            status="passed" if error is None else "failed",
-158            errors=(
-159                []
-160                if error is None
-161                else [
-162                    ErrorEntry(
-163                        loc=(
-164                            ("weights",)
-165                            if weight_format is None
-166                            else ("weights", weight_format)
-167                        ),
-168                        msg=error,
-169                        type="bioimageio.core",
-170                        traceback=tb,
-171                    )
-172                ]
-173            ),
-174        )
-175    )
-176
+115
+116def _test_model_inference(
+117    model: Union[v0_4.ModelDescr, v0_5.ModelDescr],
+118    weight_format: Optional[WeightsFormat],
+119    devices: Optional[List[str]],
+120    decimal: int,
+121) -> None:
+122    test_name = "Reproduce test outputs from test inputs"
+123    logger.info("starting '{}'", test_name)
+124    error: Optional[str] = None
+125    tb: List[str] = []
+126    try:
+127        inputs = get_test_inputs(model)
+128        expected = get_test_outputs(model)
+129
+130        with create_prediction_pipeline(
+131            bioimageio_model=model, devices=devices, weight_format=weight_format
+132        ) as prediction_pipeline:
+133            results = prediction_pipeline.predict_sample_without_blocking(inputs)
+134
+135        if len(results.members) != len(expected.members):
+136            error = f"Expected {len(expected.members)} outputs, but got {len(results.members)}"
+137
+138        else:
+139            for m, exp in expected.members.items():
+140                res = results.members.get(m)
+141                if res is None:
+142                    error = "Output tensors for test case may not be None"
+143                    break
+144                try:
+145                    np.testing.assert_array_almost_equal(
+146                        res.data, exp.data, decimal=decimal
+147                    )
+148                except AssertionError as e:
+149                    error = f"Output and expected output disagree:\n {e}"
+150                    break
+151    except Exception as e:
+152        error = str(e)
+153        tb = traceback.format_tb(e.__traceback__)
+154
+155    model.validation_summary.add_detail(
+156        ValidationDetail(
+157            name=test_name,
+158            status="passed" if error is None else "failed",
+159            errors=(
+160                []
+161                if error is None
+162                else [
+163                    ErrorEntry(
+164                        loc=(
+165                            ("weights",)
+166                            if weight_format is None
+167                            else ("weights", weight_format)
+168                        ),
+169                        msg=error,
+170                        type="bioimageio.core",
+171                        traceback=tb,
+172                    )
+173                ]
+174            ),
+175        )
+176    )
 177
-178def _test_model_inference_parametrized(
-179    model: v0_5.ModelDescr,
-180    weight_format: Optional[WeightsFormat],
-181    devices: Optional[List[str]],
-182    test_cases: Set[Tuple[v0_5.ParameterizedSize.N, BatchSize]] = {
-183        (0, 2),
-184        (1, 3),
-185        (2, 1),
-186        (3, 2),
-187    },
-188) -> None:
-189    if not test_cases:
-190        return
-191
-192    logger.info(
-193        "Testing inference with {} different input tensor sizes", len(test_cases)
-194    )
-195
-196    if not any(
-197        isinstance(a.size, v0_5.ParameterizedSize)
-198        for ipt in model.inputs
-199        for a in ipt.axes
-200    ):
-201        # no parameterized sizes => set n=0
-202        test_cases = {(0, b) for _n, b in test_cases}
-203
-204    if not any(isinstance(a, v0_5.BatchAxis) for ipt in model.inputs for a in ipt.axes):
-205        # no batch axis => set b=1
-206        test_cases = {(n, 1) for n, _b in test_cases}
-207
-208    def generate_test_cases():
-209        tested: Set[Hashable] = set()
-210
-211        def get_ns(n: int):
-212            return {
-213                (t.id, a.id): n
-214                for t in model.inputs
-215                for a in t.axes
-216                if isinstance(a.size, v0_5.ParameterizedSize)
-217            }
+178
+179def _test_model_inference_parametrized(
+180    model: v0_5.ModelDescr,
+181    weight_format: Optional[WeightsFormat],
+182    devices: Optional[List[str]],
+183) -> None:
+184    if not any(
+185        isinstance(a.size, v0_5.ParameterizedSize)
+186        for ipt in model.inputs
+187        for a in ipt.axes
+188    ):
+189        # no parameterized sizes => set n=0
+190        ns: Set[v0_5.ParameterizedSize.N] = {0}
+191    else:
+192        ns = {0, 1, 2}
+193
+194    given_batch_sizes = {
+195        a.size
+196        for ipt in model.inputs
+197        for a in ipt.axes
+198        if isinstance(a, v0_5.BatchAxis)
+199    }
+200    if given_batch_sizes:
+201        batch_sizes = {gbs for gbs in given_batch_sizes if gbs is not None}
+202        if not batch_sizes:
+203            # only arbitrary batch sizes
+204            batch_sizes = {1, 2}
+205    else:
+206        # no batch axis
+207        batch_sizes = {1}
+208
+209    test_cases: Set[Tuple[v0_5.ParameterizedSize.N, BatchSize]] = {
+210        (n, b) for n, b in product(sorted(ns), sorted(batch_sizes))
+211    }
+212    logger.info(
+213        "Testing inference with {} different input tensor sizes", len(test_cases)
+214    )
+215
+216    def generate_test_cases():
+217        tested: Set[Hashable] = set()
 218
-219        for n, batch_size in sorted(test_cases):
-220            input_target_sizes, expected_output_sizes = model.get_axis_sizes(
-221                get_ns(n), batch_size=batch_size
-222            )
-223            hashable_target_size = tuple(
-224                (k, input_target_sizes[k]) for k in sorted(input_target_sizes)
-225            )
-226            if hashable_target_size in tested:
-227                continue
-228            else:
-229                tested.add(hashable_target_size)
-230
-231            resized_test_inputs = Sample(
-232                members={
-233                    t.id: test_inputs.members[t.id].resize_to(
-234                        {
-235                            aid: s
-236                            for (tid, aid), s in input_target_sizes.items()
-237                            if tid == t.id
-238                        },
-239                    )
-240                    for t in model.inputs
-241                },
-242                stat=test_inputs.stat,
-243                id=test_inputs.id,
-244            )
-245            expected_output_shapes = {
-246                t.id: {
-247                    aid: s
-248                    for (tid, aid), s in expected_output_sizes.items()
-249                    if tid == t.id
-250                }
-251                for t in model.outputs
-252            }
-253            yield n, batch_size, resized_test_inputs, expected_output_shapes
-254
-255    try:
-256        test_inputs = get_test_inputs(model)
-257
-258        with create_prediction_pipeline(
-259            bioimageio_model=model, devices=devices, weight_format=weight_format
-260        ) as prediction_pipeline:
-261            for n, batch_size, inputs, exptected_output_shape in generate_test_cases():
-262                error: Optional[str] = None
-263                result = prediction_pipeline.predict_sample_without_blocking(inputs)
-264                if len(result.members) != len(exptected_output_shape):
-265                    error = (
-266                        f"Expected {len(exptected_output_shape)} outputs,"
-267                        + f" but got {len(result.members)}"
-268                    )
-269
-270                else:
-271                    for m, exp in exptected_output_shape.items():
-272                        res = result.members.get(m)
-273                        if res is None:
-274                            error = "Output tensors may not be None for test case"
-275                            break
-276
-277                        diff: Dict[AxisId, int] = {}
-278                        for a, s in res.sizes.items():
-279                            if isinstance((e_aid := exp[AxisId(a)]), int):
-280                                if s != e_aid:
-281                                    diff[AxisId(a)] = s
-282                            elif (
-283                                s < e_aid.min or e_aid.max is not None and s > e_aid.max
-284                            ):
-285                                diff[AxisId(a)] = s
-286                        if diff:
-287                            error = (
-288                                f"(n={n}) Expected output shape {exp},"
-289                                + f" but got {res.sizes} (diff: {diff})"
-290                            )
-291                            break
-292
-293                model.validation_summary.add_detail(
-294                    ValidationDetail(
-295                        name="Run inference for inputs with batch_size:"
-296                        + f" {batch_size} and size parameter n: {n}",
-297                        status="passed" if error is None else "failed",
-298                        errors=(
-299                            []
-300                            if error is None
-301                            else [
-302                                ErrorEntry(
-303                                    loc=(
-304                                        ("weights",)
-305                                        if weight_format is None
-306                                        else ("weights", weight_format)
-307                                    ),
-308                                    msg=error,
-309                                    type="bioimageio.core",
-310                                )
-311                            ]
-312                        ),
-313                    )
-314                )
-315    except Exception as e:
-316        error = str(e)
-317        tb = traceback.format_tb(e.__traceback__)
-318        model.validation_summary.add_detail(
-319            ValidationDetail(
-320                name="Run inference for parametrized inputs",
-321                status="failed",
-322                errors=[
-323                    ErrorEntry(
-324                        loc=(
-325                            ("weights",)
-326                            if weight_format is None
-327                            else ("weights", weight_format)
-328                        ),
-329                        msg=error,
-330                        type="bioimageio.core",
-331                        traceback=tb,
-332                    )
-333                ],
-334            )
-335        )
-336
-337
-338def _test_expected_resource_type(
-339    rd: Union[InvalidDescr, ResourceDescr], expected_type: str
-340):
-341    has_expected_type = rd.type == expected_type
-342    rd.validation_summary.details.append(
-343        ValidationDetail(
-344            name="Has expected resource type",
-345            status="passed" if has_expected_type else "failed",
-346            errors=(
-347                []
-348                if has_expected_type
-349                else [
-350                    ErrorEntry(
-351                        loc=("type",),
-352                        type="type",
-353                        msg=f"expected type {expected_type}, found {rd.type}",
-354                    )
-355                ]
-356            ),
-357        )
-358    )
-359
-360
-361# def debug_model(
-362#     model_rdf: Union[RawResourceDescr, ResourceDescr, URI, Path, str],
-363#     *,
-364#     weight_format: Optional[WeightsFormat] = None,
-365#     devices: Optional[List[str]] = None,
-366# ):
-367#     """Run the model test and return dict with inputs, results, expected results and intermediates.
+219        def get_ns(n: int):
+220            return {
+221                (t.id, a.id): n
+222                for t in model.inputs
+223                for a in t.axes
+224                if isinstance(a.size, v0_5.ParameterizedSize)
+225            }
+226
+227        for n, batch_size in sorted(test_cases):
+228            input_target_sizes, expected_output_sizes = model.get_axis_sizes(
+229                get_ns(n), batch_size=batch_size
+230            )
+231            hashable_target_size = tuple(
+232                (k, input_target_sizes[k]) for k in sorted(input_target_sizes)
+233            )
+234            if hashable_target_size in tested:
+235                continue
+236            else:
+237                tested.add(hashable_target_size)
+238
+239            resized_test_inputs = Sample(
+240                members={
+241                    t.id: test_inputs.members[t.id].resize_to(
+242                        {
+243                            aid: s
+244                            for (tid, aid), s in input_target_sizes.items()
+245                            if tid == t.id
+246                        },
+247                    )
+248                    for t in model.inputs
+249                },
+250                stat=test_inputs.stat,
+251                id=test_inputs.id,
+252            )
+253            expected_output_shapes = {
+254                t.id: {
+255                    aid: s
+256                    for (tid, aid), s in expected_output_sizes.items()
+257                    if tid == t.id
+258                }
+259                for t in model.outputs
+260            }
+261            yield n, batch_size, resized_test_inputs, expected_output_shapes
+262
+263    try:
+264        test_inputs = get_test_inputs(model)
+265
+266        with create_prediction_pipeline(
+267            bioimageio_model=model, devices=devices, weight_format=weight_format
+268        ) as prediction_pipeline:
+269            for n, batch_size, inputs, exptected_output_shape in generate_test_cases():
+270                error: Optional[str] = None
+271                result = prediction_pipeline.predict_sample_without_blocking(inputs)
+272                if len(result.members) != len(exptected_output_shape):
+273                    error = (
+274                        f"Expected {len(exptected_output_shape)} outputs,"
+275                        + f" but got {len(result.members)}"
+276                    )
+277
+278                else:
+279                    for m, exp in exptected_output_shape.items():
+280                        res = result.members.get(m)
+281                        if res is None:
+282                            error = "Output tensors may not be None for test case"
+283                            break
+284
+285                        diff: Dict[AxisId, int] = {}
+286                        for a, s in res.sizes.items():
+287                            if isinstance((e_aid := exp[AxisId(a)]), int):
+288                                if s != e_aid:
+289                                    diff[AxisId(a)] = s
+290                            elif (
+291                                s < e_aid.min or e_aid.max is not None and s > e_aid.max
+292                            ):
+293                                diff[AxisId(a)] = s
+294                        if diff:
+295                            error = (
+296                                f"(n={n}) Expected output shape {exp},"
+297                                + f" but got {res.sizes} (diff: {diff})"
+298                            )
+299                            break
+300
+301                model.validation_summary.add_detail(
+302                    ValidationDetail(
+303                        name="Run inference for inputs with batch_size:"
+304                        + f" {batch_size} and size parameter n: {n}",
+305                        status="passed" if error is None else "failed",
+306                        errors=(
+307                            []
+308                            if error is None
+309                            else [
+310                                ErrorEntry(
+311                                    loc=(
+312                                        ("weights",)
+313                                        if weight_format is None
+314                                        else ("weights", weight_format)
+315                                    ),
+316                                    msg=error,
+317                                    type="bioimageio.core",
+318                                )
+319                            ]
+320                        ),
+321                    )
+322                )
+323    except Exception as e:
+324        error = str(e)
+325        tb = traceback.format_tb(e.__traceback__)
+326        model.validation_summary.add_detail(
+327            ValidationDetail(
+328                name="Run inference for parametrized inputs",
+329                status="failed",
+330                errors=[
+331                    ErrorEntry(
+332                        loc=(
+333                            ("weights",)
+334                            if weight_format is None
+335                            else ("weights", weight_format)
+336                        ),
+337                        msg=error,
+338                        type="bioimageio.core",
+339                        traceback=tb,
+340                    )
+341                ],
+342            )
+343        )
+344
+345
+346def _test_expected_resource_type(
+347    rd: Union[InvalidDescr, ResourceDescr], expected_type: str
+348):
+349    has_expected_type = rd.type == expected_type
+350    rd.validation_summary.details.append(
+351        ValidationDetail(
+352            name="Has expected resource type",
+353            status="passed" if has_expected_type else "failed",
+354            errors=(
+355                []
+356                if has_expected_type
+357                else [
+358                    ErrorEntry(
+359                        loc=("type",),
+360                        type="type",
+361                        msg=f"expected type {expected_type}, found {rd.type}",
+362                    )
+363                ]
+364            ),
+365        )
+366    )
+367
 368
-369#     Returns dict with tensors "inputs", "inputs_processed", "outputs_raw", "outputs", "expected" and "diff".
-370#     """
-371#     inputs_raw: Optional = None
-372#     inputs_processed: Optional = None
-373#     outputs_raw: Optional = None
-374#     outputs: Optional = None
-375#     expected: Optional = None
-376#     diff: Optional = None
-377
-378#     model = load_description(
-379#         model_rdf, weights_priority_order=None if weight_format is None else [weight_format]
-380#     )
-381#     if not isinstance(model, Model):
-382#         raise ValueError(f"Not a bioimageio.model: {model_rdf}")
-383
-384#     prediction_pipeline = create_prediction_pipeline(
-385#         bioimageio_model=model, devices=devices, weight_format=weight_format
-386#     )
-387#     inputs = [
-388#         xr.DataArray(load_array(str(in_path)), dims=input_spec.axes)
-389#         for in_path, input_spec in zip(model.test_inputs, model.inputs)
-390#     ]
-391#     input_dict = {input_spec.name: input for input_spec, input in zip(model.inputs, inputs)}
-392
-393#     # keep track of the non-processed inputs
-394#     inputs_raw = [deepcopy(input) for input in inputs]
-395
-396#     computed_measures = {}
-397
-398#     prediction_pipeline.apply_preprocessing(input_dict, computed_measures)
-399#     inputs_processed = list(input_dict.values())
-400#     outputs_raw = prediction_pipeline.predict(*inputs_processed)
-401#     output_dict = {output_spec.name: deepcopy(output) for output_spec, output in zip(model.outputs, outputs_raw)}
-402#     prediction_pipeline.apply_postprocessing(output_dict, computed_measures)
-403#     outputs = list(output_dict.values())
-404
-405#     if isinstance(outputs, (np.ndarray, xr.DataArray)):
-406#         outputs = [outputs]
-407
-408#     expected = [
-409#         xr.DataArray(load_array(str(out_path)), dims=output_spec.axes)
-410#         for out_path, output_spec in zip(model.test_outputs, model.outputs)
-411#     ]
-412#     if len(outputs) != len(expected):
-413#         error = f"Number of outputs and number of expected outputs disagree: {len(outputs)} != {len(expected)}"
-414#         print(error)
-415#     else:
-416#         diff = []
-417#         for res, exp in zip(outputs, expected):
-418#             diff.append(res - exp)
-419
-420#     return {
-421#         "inputs": inputs_raw,
-422#         "inputs_processed": inputs_processed,
-423#         "outputs_raw": outputs_raw,
-424#         "outputs": outputs,
-425#         "expected": expected,
-426#         "diff": diff,
-427#     }
+369# def debug_model(
+370#     model_rdf: Union[RawResourceDescr, ResourceDescr, URI, Path, str],
+371#     *,
+372#     weight_format: Optional[WeightsFormat] = None,
+373#     devices: Optional[List[str]] = None,
+374# ):
+375#     """Run the model test and return dict with inputs, results, expected results and intermediates.
+376
+377#     Returns dict with tensors "inputs", "inputs_processed", "outputs_raw", "outputs", "expected" and "diff".
+378#     """
+379#     inputs_raw: Optional = None
+380#     inputs_processed: Optional = None
+381#     outputs_raw: Optional = None
+382#     outputs: Optional = None
+383#     expected: Optional = None
+384#     diff: Optional = None
+385
+386#     model = load_description(
+387#         model_rdf, weights_priority_order=None if weight_format is None else [weight_format]
+388#     )
+389#     if not isinstance(model, Model):
+390#         raise ValueError(f"Not a bioimageio.model: {model_rdf}")
+391
+392#     prediction_pipeline = create_prediction_pipeline(
+393#         bioimageio_model=model, devices=devices, weight_format=weight_format
+394#     )
+395#     inputs = [
+396#         xr.DataArray(load_array(str(in_path)), dims=input_spec.axes)
+397#         for in_path, input_spec in zip(model.test_inputs, model.inputs)
+398#     ]
+399#     input_dict = {input_spec.name: input for input_spec, input in zip(model.inputs, inputs)}
+400
+401#     # keep track of the non-processed inputs
+402#     inputs_raw = [deepcopy(input) for input in inputs]
+403
+404#     computed_measures = {}
+405
+406#     prediction_pipeline.apply_preprocessing(input_dict, computed_measures)
+407#     inputs_processed = list(input_dict.values())
+408#     outputs_raw = prediction_pipeline.predict(*inputs_processed)
+409#     output_dict = {output_spec.name: deepcopy(output) for output_spec, output in zip(model.outputs, outputs_raw)}
+410#     prediction_pipeline.apply_postprocessing(output_dict, computed_measures)
+411#     outputs = list(output_dict.values())
+412
+413#     if isinstance(outputs, (np.ndarray, xr.DataArray)):
+414#         outputs = [outputs]
+415
+416#     expected = [
+417#         xr.DataArray(load_array(str(out_path)), dims=output_spec.axes)
+418#         for out_path, output_spec in zip(model.test_outputs, model.outputs)
+419#     ]
+420#     if len(outputs) != len(expected):
+421#         error = f"Number of outputs and number of expected outputs disagree: {len(outputs)} != {len(expected)}"
+422#         print(error)
+423#     else:
+424#         diff = []
+425#         for res, exp in zip(outputs, expected):
+426#             diff.append(res - exp)
+427
+428#     return {
+429#         "inputs": inputs_raw,
+430#         "inputs_processed": inputs_processed,
+431#         "outputs_raw": outputs_raw,
+432#         "outputs": outputs,
+433#         "expected": expected,
+434#         "diff": diff,
+435#     }
 
@@ -505,20 +513,20 @@

-
34def test_model(
-35    source: Union[v0_5.ModelDescr, PermissiveFileSource],
-36    weight_format: Optional[WeightsFormat] = None,
-37    devices: Optional[List[str]] = None,
-38    decimal: int = 4,
-39) -> ValidationSummary:
-40    """Test model inference"""
-41    return test_description(
-42        source,
-43        weight_format=weight_format,
-44        devices=devices,
-45        decimal=decimal,
-46        expected_type="model",
-47    )
+            
35def test_model(
+36    source: Union[v0_5.ModelDescr, PermissiveFileSource],
+37    weight_format: Optional[WeightsFormat] = None,
+38    devices: Optional[List[str]] = None,
+39    decimal: int = 4,
+40) -> ValidationSummary:
+41    """Test model inference"""
+42    return test_description(
+43        source,
+44        weight_format=weight_format,
+45        devices=devices,
+46        decimal=decimal,
+47        expected_type="model",
+48    )
 
@@ -538,25 +546,25 @@

-
50def test_description(
-51    source: Union[ResourceDescr, PermissiveFileSource, BioimageioYamlContent],
-52    *,
-53    format_version: Union[Literal["discover", "latest"], str] = "discover",
-54    weight_format: Optional[WeightsFormat] = None,
-55    devices: Optional[List[str]] = None,
-56    decimal: int = 4,
-57    expected_type: Optional[str] = None,
-58) -> ValidationSummary:
-59    """Test a bioimage.io resource dynamically, e.g. prediction of test tensors for models"""
-60    rd = load_description_and_test(
-61        source,
-62        format_version=format_version,
-63        weight_format=weight_format,
-64        devices=devices,
-65        decimal=decimal,
-66        expected_type=expected_type,
-67    )
-68    return rd.validation_summary
+            
51def test_description(
+52    source: Union[ResourceDescr, PermissiveFileSource, BioimageioYamlContent],
+53    *,
+54    format_version: Union[Literal["discover", "latest"], str] = "discover",
+55    weight_format: Optional[WeightsFormat] = None,
+56    devices: Optional[List[str]] = None,
+57    decimal: int = 4,
+58    expected_type: Optional[str] = None,
+59) -> ValidationSummary:
+60    """Test a bioimage.io resource dynamically, e.g. prediction of test tensors for models"""
+61    rd = load_description_and_test(
+62        source,
+63        format_version=format_version,
+64        weight_format=weight_format,
+65        devices=devices,
+66        decimal=decimal,
+67        expected_type=expected_type,
+68    )
+69    return rd.validation_summary
 
@@ -576,49 +584,49 @@

-
 71def load_description_and_test(
- 72    source: Union[ResourceDescr, PermissiveFileSource, BioimageioYamlContent],
- 73    *,
- 74    format_version: Union[Literal["discover", "latest"], str] = "discover",
- 75    weight_format: Optional[WeightsFormat] = None,
- 76    devices: Optional[List[str]] = None,
- 77    decimal: int = 4,
- 78    expected_type: Optional[str] = None,
- 79) -> Union[ResourceDescr, InvalidDescr]:
- 80    """Test RDF dynamically, e.g. model inference of test inputs"""
- 81    if (
- 82        isinstance(source, ResourceDescrBase)
- 83        and format_version != "discover"
- 84        and source.format_version != format_version
- 85    ):
- 86        warnings.warn(
- 87            f"deserializing source to ensure we validate and test using format {format_version}"
- 88        )
- 89        source = dump_description(source)
- 90
- 91    if isinstance(source, ResourceDescrBase):
- 92        rd = source
- 93    elif isinstance(source, dict):
- 94        rd = build_description(source, format_version=format_version)
- 95    else:
- 96        rd = load_description(source, format_version=format_version)
- 97
- 98    rd.validation_summary.env.append(
- 99        InstalledPackage(name="bioimageio.core", version=VERSION)
-100    )
-101
-102    if expected_type is not None:
-103        _test_expected_resource_type(rd, expected_type)
-104
-105    if isinstance(rd, (v0_4.ModelDescr, v0_5.ModelDescr)):
-106        _test_model_inference(rd, weight_format, devices, decimal)
-107        if not isinstance(rd, v0_4.ModelDescr):
-108            _test_model_inference_parametrized(rd, weight_format, devices)
-109
-110    # TODO: add execution of jupyter notebooks
-111    # TODO: add more tests
-112
-113    return rd
+            
 72def load_description_and_test(
+ 73    source: Union[ResourceDescr, PermissiveFileSource, BioimageioYamlContent],
+ 74    *,
+ 75    format_version: Union[Literal["discover", "latest"], str] = "discover",
+ 76    weight_format: Optional[WeightsFormat] = None,
+ 77    devices: Optional[List[str]] = None,
+ 78    decimal: int = 4,
+ 79    expected_type: Optional[str] = None,
+ 80) -> Union[ResourceDescr, InvalidDescr]:
+ 81    """Test RDF dynamically, e.g. model inference of test inputs"""
+ 82    if (
+ 83        isinstance(source, ResourceDescrBase)
+ 84        and format_version != "discover"
+ 85        and source.format_version != format_version
+ 86    ):
+ 87        warnings.warn(
+ 88            f"deserializing source to ensure we validate and test using format {format_version}"
+ 89        )
+ 90        source = dump_description(source)
+ 91
+ 92    if isinstance(source, ResourceDescrBase):
+ 93        rd = source
+ 94    elif isinstance(source, dict):
+ 95        rd = build_description(source, format_version=format_version)
+ 96    else:
+ 97        rd = load_description(source, format_version=format_version)
+ 98
+ 99    rd.validation_summary.env.append(
+100        InstalledPackage(name="bioimageio.core", version=VERSION)
+101    )
+102
+103    if expected_type is not None:
+104        _test_expected_resource_type(rd, expected_type)
+105
+106    if isinstance(rd, (v0_4.ModelDescr, v0_5.ModelDescr)):
+107        _test_model_inference(rd, weight_format, devices, decimal)
+108        if not isinstance(rd, v0_4.ModelDescr):
+109            _test_model_inference_parametrized(rd, weight_format, devices)
+110
+111    # TODO: add execution of jupyter notebooks
+112    # TODO: add more tests
+113
+114    return rd
 
diff --git a/bioimageio/core/model_adapters/_pytorch_model_adapter.html b/bioimageio/core/model_adapters/_pytorch_model_adapter.html index 2a4bb597..7bd063be 100644 --- a/bioimageio/core/model_adapters/_pytorch_model_adapter.html +++ b/bioimageio/core/model_adapters/_pytorch_model_adapter.html @@ -122,7 +122,7 @@

42 43 self._primary_device = self._devices[0] 44 state: Any = torch.load( - 45 download(weights.source).path, + 45 download(weights).path, 46 map_location=self._primary_device, # pyright: ignore[reportUnknownArgumentType] 47 ) 48 self._network.load_state_dict(state) @@ -269,7 +269,7 @@

43 44 self._primary_device = self._devices[0] 45 state: Any = torch.load( - 46 download(weights.source).path, + 46 download(weights).path, 47 map_location=self._primary_device, # pyright: ignore[reportUnknownArgumentType] 48 ) 49 self._network.load_state_dict(state) @@ -431,7 +431,7 @@

43 44 self._primary_device = self._devices[0] 45 state: Any = torch.load( -46 download(weights.source).path, +46 download(weights).path, 47 map_location=self._primary_device, # pyright: ignore[reportUnknownArgumentType] 48 ) 49 self._network.load_state_dict(state) diff --git a/bioimageio/core/model_adapters/_tensorflow_model_adapter.html b/bioimageio/core/model_adapters/_tensorflow_model_adapter.html index 90a16eba..d7826876 100644 --- a/bioimageio/core/model_adapters/_tensorflow_model_adapter.html +++ b/bioimageio/core/model_adapters/_tensorflow_model_adapter.html @@ -105,11 +105,11 @@

-
  1import warnings
-  2import zipfile
-  3from typing import List, Literal, Optional, Sequence, Union
-  4
-  5import numpy as np
+                        
  1import zipfile
+  2from typing import List, Literal, Optional, Sequence, Union
+  3
+  4import numpy as np
+  5from loguru import logger
   6
   7from bioimageio.spec.common import FileSource
   8from bioimageio.spec.model import v0_4, v0_5
@@ -153,19 +153,19 @@ 

46 ) 47 model_tf_version = weights.tensorflow_version 48 if model_tf_version is None: - 49 warnings.warn( + 49 logger.warning( 50 "The model does not specify the tensorflow version." 51 + f"Cannot check if it is compatible with intalled tensorflow {tf_version}." 52 ) 53 elif model_tf_version > tf_version: - 54 warnings.warn( + 54 logger.warning( 55 f"The model specifies a newer tensorflow version than installed: {model_tf_version} > {tf_version}." 56 ) 57 elif (model_tf_version.major, model_tf_version.minor) != ( 58 tf_version.major, 59 tf_version.minor, 60 ): - 61 warnings.warn( + 61 logger.warning( 62 "The tensorflow version specified by the model does not match the installed: " 63 + f"{model_tf_version} != {tf_version}." 64 ) @@ -177,7 +177,7 @@

70 71 # TODO tf device management 72 if devices is not None: - 73 warnings.warn( + 73 logger.warning( 74 f"Device management is not implemented for tensorflow yet, ignoring the devices {devices}" 75 ) 76 @@ -205,179 +205,181 @@

98 weight_file = self.require_unzipped(weight_file) 99 assert tf is not None 100 if self.use_keras_api: -101 return tf.keras.models.load_model( -102 weight_file, compile=False -103 ) # pyright: ignore[reportUnknownVariableType] -104 else: -105 # NOTE in tf1 the model needs to be loaded inside of the session, so we cannot preload the model -106 return str(weight_file) -107 -108 # TODO currently we relaod the model every time. it would be better to keep the graph and session -109 # alive in between of forward passes (but then the sessions need to be properly opened / closed) -110 def _forward_tf( # pyright: ignore[reportUnknownParameterType] -111 self, *input_tensors: Optional[Tensor] -112 ): -113 assert tf is not None -114 input_keys = [ -115 ipt.name if isinstance(ipt, v0_4.InputTensorDescr) else ipt.id -116 for ipt in self.model_description.inputs -117 ] -118 output_keys = [ -119 out.name if isinstance(out, v0_4.OutputTensorDescr) else out.id -120 for out in self.model_description.outputs -121 ] -122 # TODO read from spec -123 tag = ( # pyright: ignore[reportUnknownVariableType] -124 tf.saved_model.tag_constants.SERVING -125 ) -126 signature_key = ( # pyright: ignore[reportUnknownVariableType] -127 tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY -128 ) -129 -130 graph = tf.Graph() # pyright: ignore[reportUnknownVariableType] -131 with graph.as_default(): -132 with tf.Session( -133 graph=graph -134 ) as sess: # pyright: ignore[reportUnknownVariableType] -135 # load the model and the signature -136 graph_def = tf.saved_model.loader.load( # pyright: ignore[reportUnknownVariableType] -137 sess, [tag], self._network -138 ) -139 signature = ( # pyright: ignore[reportUnknownVariableType] -140 graph_def.signature_def -141 ) -142 -143 # get the tensors into the graph -144 in_names = [ # pyright: ignore[reportUnknownVariableType] -145 signature[signature_key].inputs[key].name for key in input_keys -146 ] -147 out_names = [ # pyright: ignore[reportUnknownVariableType] -148 signature[signature_key].outputs[key].name for key in output_keys -149 ] -150 in_tensors = [ # pyright: ignore[reportUnknownVariableType] -151 graph.get_tensor_by_name(name) -152 for name in in_names # pyright: ignore[reportUnknownVariableType] -153 ] -154 out_tensors = [ # pyright: ignore[reportUnknownVariableType] -155 graph.get_tensor_by_name(name) -156 for name in out_names # pyright: ignore[reportUnknownVariableType] +101 try: +102 return tf.keras.layers.TFSMLayer( +103 weight_file, call_endpoint="serve" +104 ) # pyright: ignore[reportUnknownVariableType] +105 except Exception as e: +106 try: +107 return tf.keras.layers.TFSMLayer( +108 weight_file, call_endpoint="serving_default" +109 ) # pyright: ignore[reportUnknownVariableType] +110 except Exception as ee: +111 logger.opt(exception=ee).info( +112 "keras.layers.TFSMLayer error for alternative call_endpoint='serving_default'" +113 ) +114 raise e +115 else: +116 # NOTE in tf1 the model needs to be loaded inside of the session, so we cannot preload the model +117 return str(weight_file) +118 +119 # TODO currently we relaod the model every time. it would be better to keep the graph and session +120 # alive in between of forward passes (but then the sessions need to be properly opened / closed) +121 def _forward_tf( # pyright: ignore[reportUnknownParameterType] +122 self, *input_tensors: Optional[Tensor] +123 ): +124 assert tf is not None +125 input_keys = [ +126 ipt.name if isinstance(ipt, v0_4.InputTensorDescr) else ipt.id +127 for ipt in self.model_description.inputs +128 ] +129 output_keys = [ +130 out.name if isinstance(out, v0_4.OutputTensorDescr) else out.id +131 for out in self.model_description.outputs +132 ] +133 # TODO read from spec +134 tag = ( # pyright: ignore[reportUnknownVariableType] +135 tf.saved_model.tag_constants.SERVING +136 ) +137 signature_key = ( # pyright: ignore[reportUnknownVariableType] +138 tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY +139 ) +140 +141 graph = tf.Graph() # pyright: ignore[reportUnknownVariableType] +142 with graph.as_default(): +143 with tf.Session( +144 graph=graph +145 ) as sess: # pyright: ignore[reportUnknownVariableType] +146 # load the model and the signature +147 graph_def = tf.saved_model.loader.load( # pyright: ignore[reportUnknownVariableType] +148 sess, [tag], self._network +149 ) +150 signature = ( # pyright: ignore[reportUnknownVariableType] +151 graph_def.signature_def +152 ) +153 +154 # get the tensors into the graph +155 in_names = [ # pyright: ignore[reportUnknownVariableType] +156 signature[signature_key].inputs[key].name for key in input_keys 157 ] -158 -159 # run prediction -160 res = sess.run( # pyright: ignore[reportUnknownVariableType] -161 dict( -162 zip( -163 out_names, # pyright: ignore[reportUnknownArgumentType] -164 out_tensors, # pyright: ignore[reportUnknownArgumentType] -165 ) -166 ), -167 dict( -168 zip( -169 in_tensors, # pyright: ignore[reportUnknownArgumentType] -170 input_tensors, -171 ) -172 ), -173 ) -174 # from dict to list of tensors -175 res = [ # pyright: ignore[reportUnknownVariableType] -176 res[out] -177 for out in out_names # pyright: ignore[reportUnknownVariableType] -178 ] -179 -180 return res # pyright: ignore[reportUnknownVariableType] -181 -182 def _forward_keras( # pyright: ignore[reportUnknownParameterType] -183 self, *input_tensors: Optional[Tensor] -184 ): -185 assert self.use_keras_api -186 assert not isinstance(self._network, str) -187 assert tf is not None -188 tf_tensor = [ # pyright: ignore[reportUnknownVariableType] -189 None if ipt is None else tf.convert_to_tensor(ipt) for ipt in input_tensors -190 ] -191 -192 try: -193 result = ( # pyright: ignore[reportUnknownVariableType] -194 self._network.forward(*tf_tensor) -195 ) -196 except AttributeError: -197 result = ( # pyright: ignore[reportUnknownVariableType] -198 self._network.predict(*tf_tensor) -199 ) -200 -201 if not isinstance(result, (tuple, list)): -202 result = [result] # pyright: ignore[reportUnknownVariableType] -203 -204 return [ # pyright: ignore[reportUnknownVariableType] -205 ( -206 None -207 if r is None -208 else r if isinstance(r, np.ndarray) else tf.make_ndarray(r) -209 ) -210 for r in result # pyright: ignore[reportUnknownVariableType] -211 ] -212 -213 def forward(self, *input_tensors: Optional[Tensor]) -> List[Optional[Tensor]]: -214 data = [None if ipt is None else ipt.data for ipt in input_tensors] -215 if self.use_keras_api: -216 result = self._forward_keras( # pyright: ignore[reportUnknownVariableType] -217 *data -218 ) -219 else: -220 result = self._forward_tf( # pyright: ignore[reportUnknownVariableType] -221 *data -222 ) -223 -224 return [ -225 None if r is None else Tensor(r, dims=axes) -226 for r, axes in zip( # pyright: ignore[reportUnknownVariableType] -227 result, # pyright: ignore[reportUnknownArgumentType] -228 self._internal_output_axes, -229 ) -230 ] -231 -232 def unload(self) -> None: -233 warnings.warn( -234 "Device management is not implemented for keras yet, cannot unload model" -235 ) -236 -237 -238class TensorflowModelAdapter(TensorflowModelAdapterBase): -239 weight_format = "tensorflow_saved_model_bundle" -240 -241 def __init__( -242 self, -243 *, -244 model_description: Union[v0_4.ModelDescr, v0_5.ModelDescr], -245 devices: Optional[Sequence[str]] = None, -246 ): -247 if model_description.weights.tensorflow_saved_model_bundle is None: -248 raise ValueError("missing tensorflow_saved_model_bundle weights") -249 -250 super().__init__( -251 devices=devices, -252 weights=model_description.weights.tensorflow_saved_model_bundle, -253 model_description=model_description, -254 ) -255 -256 -257class KerasModelAdapter(TensorflowModelAdapterBase): -258 weight_format = "keras_hdf5" -259 -260 def __init__( -261 self, -262 *, -263 model_description: Union[v0_4.ModelDescr, v0_5.ModelDescr], -264 devices: Optional[Sequence[str]] = None, -265 ): -266 if model_description.weights.keras_hdf5 is None: -267 raise ValueError("missing keras_hdf5 weights") -268 -269 super().__init__( -270 model_description=model_description, -271 devices=devices, -272 weights=model_description.weights.keras_hdf5, -273 ) +158 out_names = [ # pyright: ignore[reportUnknownVariableType] +159 signature[signature_key].outputs[key].name for key in output_keys +160 ] +161 in_tensors = [ # pyright: ignore[reportUnknownVariableType] +162 graph.get_tensor_by_name(name) +163 for name in in_names # pyright: ignore[reportUnknownVariableType] +164 ] +165 out_tensors = [ # pyright: ignore[reportUnknownVariableType] +166 graph.get_tensor_by_name(name) +167 for name in out_names # pyright: ignore[reportUnknownVariableType] +168 ] +169 +170 # run prediction +171 res = sess.run( # pyright: ignore[reportUnknownVariableType] +172 dict( +173 zip( +174 out_names, # pyright: ignore[reportUnknownArgumentType] +175 out_tensors, # pyright: ignore[reportUnknownArgumentType] +176 ) +177 ), +178 dict( +179 zip( +180 in_tensors, # pyright: ignore[reportUnknownArgumentType] +181 input_tensors, +182 ) +183 ), +184 ) +185 # from dict to list of tensors +186 res = [ # pyright: ignore[reportUnknownVariableType] +187 res[out] +188 for out in out_names # pyright: ignore[reportUnknownVariableType] +189 ] +190 +191 return res # pyright: ignore[reportUnknownVariableType] +192 +193 def _forward_keras( # pyright: ignore[reportUnknownParameterType] +194 self, *input_tensors: Optional[Tensor] +195 ): +196 assert self.use_keras_api +197 assert not isinstance(self._network, str) +198 assert tf is not None +199 tf_tensor = [ # pyright: ignore[reportUnknownVariableType] +200 None if ipt is None else tf.convert_to_tensor(ipt) for ipt in input_tensors +201 ] +202 +203 result = self._network(*tf_tensor) # pyright: ignore[reportUnknownVariableType] +204 +205 assert isinstance(result, dict) +206 +207 # TODO: Use RDF's `outputs[i].id` here +208 result = list(result.values()) +209 +210 return [ # pyright: ignore[reportUnknownVariableType] +211 (None if r is None else r if isinstance(r, np.ndarray) else r.numpy()) +212 for r in result # pyright: ignore[reportUnknownVariableType] +213 ] +214 +215 def forward(self, *input_tensors: Optional[Tensor]) -> List[Optional[Tensor]]: +216 data = [None if ipt is None else ipt.data for ipt in input_tensors] +217 if self.use_keras_api: +218 result = self._forward_keras( # pyright: ignore[reportUnknownVariableType] +219 *data +220 ) +221 else: +222 result = self._forward_tf( # pyright: ignore[reportUnknownVariableType] +223 *data +224 ) +225 +226 return [ +227 None if r is None else Tensor(r, dims=axes) +228 for r, axes in zip( # pyright: ignore[reportUnknownVariableType] +229 result, # pyright: ignore[reportUnknownArgumentType] +230 self._internal_output_axes, +231 ) +232 ] +233 +234 def unload(self) -> None: +235 logger.warning( +236 "Device management is not implemented for keras yet, cannot unload model" +237 ) +238 +239 +240class TensorflowModelAdapter(TensorflowModelAdapterBase): +241 weight_format = "tensorflow_saved_model_bundle" +242 +243 def __init__( +244 self, +245 *, +246 model_description: Union[v0_4.ModelDescr, v0_5.ModelDescr], +247 devices: Optional[Sequence[str]] = None, +248 ): +249 if model_description.weights.tensorflow_saved_model_bundle is None: +250 raise ValueError("missing tensorflow_saved_model_bundle weights") +251 +252 super().__init__( +253 devices=devices, +254 weights=model_description.weights.tensorflow_saved_model_bundle, +255 model_description=model_description, +256 ) +257 +258 +259class KerasModelAdapter(TensorflowModelAdapterBase): +260 weight_format = "keras_hdf5" +261 +262 def __init__( +263 self, +264 *, +265 model_description: Union[v0_4.ModelDescr, v0_5.ModelDescr], +266 devices: Optional[Sequence[str]] = None, +267 ): +268 if model_description.weights.keras_hdf5 is None: +269 raise ValueError("missing keras_hdf5 weights") +270 +271 super().__init__( +272 model_description=model_description, +273 devices=devices, +274 weights=model_description.weights.keras_hdf5, +275 )

@@ -418,19 +420,19 @@

47 ) 48 model_tf_version = weights.tensorflow_version 49 if model_tf_version is None: - 50 warnings.warn( + 50 logger.warning( 51 "The model does not specify the tensorflow version." 52 + f"Cannot check if it is compatible with intalled tensorflow {tf_version}." 53 ) 54 elif model_tf_version > tf_version: - 55 warnings.warn( + 55 logger.warning( 56 f"The model specifies a newer tensorflow version than installed: {model_tf_version} > {tf_version}." 57 ) 58 elif (model_tf_version.major, model_tf_version.minor) != ( 59 tf_version.major, 60 tf_version.minor, 61 ): - 62 warnings.warn( + 62 logger.warning( 63 "The tensorflow version specified by the model does not match the installed: " 64 + f"{model_tf_version} != {tf_version}." 65 ) @@ -442,7 +444,7 @@

71 72 # TODO tf device management 73 if devices is not None: - 74 warnings.warn( + 74 logger.warning( 75 f"Device management is not implemented for tensorflow yet, ignoring the devices {devices}" 76 ) 77 @@ -470,141 +472,143 @@

99 weight_file = self.require_unzipped(weight_file) 100 assert tf is not None 101 if self.use_keras_api: -102 return tf.keras.models.load_model( -103 weight_file, compile=False -104 ) # pyright: ignore[reportUnknownVariableType] -105 else: -106 # NOTE in tf1 the model needs to be loaded inside of the session, so we cannot preload the model -107 return str(weight_file) -108 -109 # TODO currently we relaod the model every time. it would be better to keep the graph and session -110 # alive in between of forward passes (but then the sessions need to be properly opened / closed) -111 def _forward_tf( # pyright: ignore[reportUnknownParameterType] -112 self, *input_tensors: Optional[Tensor] -113 ): -114 assert tf is not None -115 input_keys = [ -116 ipt.name if isinstance(ipt, v0_4.InputTensorDescr) else ipt.id -117 for ipt in self.model_description.inputs -118 ] -119 output_keys = [ -120 out.name if isinstance(out, v0_4.OutputTensorDescr) else out.id -121 for out in self.model_description.outputs -122 ] -123 # TODO read from spec -124 tag = ( # pyright: ignore[reportUnknownVariableType] -125 tf.saved_model.tag_constants.SERVING -126 ) -127 signature_key = ( # pyright: ignore[reportUnknownVariableType] -128 tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY -129 ) -130 -131 graph = tf.Graph() # pyright: ignore[reportUnknownVariableType] -132 with graph.as_default(): -133 with tf.Session( -134 graph=graph -135 ) as sess: # pyright: ignore[reportUnknownVariableType] -136 # load the model and the signature -137 graph_def = tf.saved_model.loader.load( # pyright: ignore[reportUnknownVariableType] -138 sess, [tag], self._network -139 ) -140 signature = ( # pyright: ignore[reportUnknownVariableType] -141 graph_def.signature_def -142 ) -143 -144 # get the tensors into the graph -145 in_names = [ # pyright: ignore[reportUnknownVariableType] -146 signature[signature_key].inputs[key].name for key in input_keys -147 ] -148 out_names = [ # pyright: ignore[reportUnknownVariableType] -149 signature[signature_key].outputs[key].name for key in output_keys -150 ] -151 in_tensors = [ # pyright: ignore[reportUnknownVariableType] -152 graph.get_tensor_by_name(name) -153 for name in in_names # pyright: ignore[reportUnknownVariableType] -154 ] -155 out_tensors = [ # pyright: ignore[reportUnknownVariableType] -156 graph.get_tensor_by_name(name) -157 for name in out_names # pyright: ignore[reportUnknownVariableType] +102 try: +103 return tf.keras.layers.TFSMLayer( +104 weight_file, call_endpoint="serve" +105 ) # pyright: ignore[reportUnknownVariableType] +106 except Exception as e: +107 try: +108 return tf.keras.layers.TFSMLayer( +109 weight_file, call_endpoint="serving_default" +110 ) # pyright: ignore[reportUnknownVariableType] +111 except Exception as ee: +112 logger.opt(exception=ee).info( +113 "keras.layers.TFSMLayer error for alternative call_endpoint='serving_default'" +114 ) +115 raise e +116 else: +117 # NOTE in tf1 the model needs to be loaded inside of the session, so we cannot preload the model +118 return str(weight_file) +119 +120 # TODO currently we relaod the model every time. it would be better to keep the graph and session +121 # alive in between of forward passes (but then the sessions need to be properly opened / closed) +122 def _forward_tf( # pyright: ignore[reportUnknownParameterType] +123 self, *input_tensors: Optional[Tensor] +124 ): +125 assert tf is not None +126 input_keys = [ +127 ipt.name if isinstance(ipt, v0_4.InputTensorDescr) else ipt.id +128 for ipt in self.model_description.inputs +129 ] +130 output_keys = [ +131 out.name if isinstance(out, v0_4.OutputTensorDescr) else out.id +132 for out in self.model_description.outputs +133 ] +134 # TODO read from spec +135 tag = ( # pyright: ignore[reportUnknownVariableType] +136 tf.saved_model.tag_constants.SERVING +137 ) +138 signature_key = ( # pyright: ignore[reportUnknownVariableType] +139 tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY +140 ) +141 +142 graph = tf.Graph() # pyright: ignore[reportUnknownVariableType] +143 with graph.as_default(): +144 with tf.Session( +145 graph=graph +146 ) as sess: # pyright: ignore[reportUnknownVariableType] +147 # load the model and the signature +148 graph_def = tf.saved_model.loader.load( # pyright: ignore[reportUnknownVariableType] +149 sess, [tag], self._network +150 ) +151 signature = ( # pyright: ignore[reportUnknownVariableType] +152 graph_def.signature_def +153 ) +154 +155 # get the tensors into the graph +156 in_names = [ # pyright: ignore[reportUnknownVariableType] +157 signature[signature_key].inputs[key].name for key in input_keys 158 ] -159 -160 # run prediction -161 res = sess.run( # pyright: ignore[reportUnknownVariableType] -162 dict( -163 zip( -164 out_names, # pyright: ignore[reportUnknownArgumentType] -165 out_tensors, # pyright: ignore[reportUnknownArgumentType] -166 ) -167 ), -168 dict( -169 zip( -170 in_tensors, # pyright: ignore[reportUnknownArgumentType] -171 input_tensors, -172 ) -173 ), -174 ) -175 # from dict to list of tensors -176 res = [ # pyright: ignore[reportUnknownVariableType] -177 res[out] -178 for out in out_names # pyright: ignore[reportUnknownVariableType] -179 ] -180 -181 return res # pyright: ignore[reportUnknownVariableType] -182 -183 def _forward_keras( # pyright: ignore[reportUnknownParameterType] -184 self, *input_tensors: Optional[Tensor] -185 ): -186 assert self.use_keras_api -187 assert not isinstance(self._network, str) -188 assert tf is not None -189 tf_tensor = [ # pyright: ignore[reportUnknownVariableType] -190 None if ipt is None else tf.convert_to_tensor(ipt) for ipt in input_tensors -191 ] -192 -193 try: -194 result = ( # pyright: ignore[reportUnknownVariableType] -195 self._network.forward(*tf_tensor) -196 ) -197 except AttributeError: -198 result = ( # pyright: ignore[reportUnknownVariableType] -199 self._network.predict(*tf_tensor) -200 ) -201 -202 if not isinstance(result, (tuple, list)): -203 result = [result] # pyright: ignore[reportUnknownVariableType] -204 -205 return [ # pyright: ignore[reportUnknownVariableType] -206 ( -207 None -208 if r is None -209 else r if isinstance(r, np.ndarray) else tf.make_ndarray(r) -210 ) -211 for r in result # pyright: ignore[reportUnknownVariableType] -212 ] -213 -214 def forward(self, *input_tensors: Optional[Tensor]) -> List[Optional[Tensor]]: -215 data = [None if ipt is None else ipt.data for ipt in input_tensors] -216 if self.use_keras_api: -217 result = self._forward_keras( # pyright: ignore[reportUnknownVariableType] -218 *data -219 ) -220 else: -221 result = self._forward_tf( # pyright: ignore[reportUnknownVariableType] -222 *data -223 ) -224 -225 return [ -226 None if r is None else Tensor(r, dims=axes) -227 for r, axes in zip( # pyright: ignore[reportUnknownVariableType] -228 result, # pyright: ignore[reportUnknownArgumentType] -229 self._internal_output_axes, -230 ) -231 ] -232 -233 def unload(self) -> None: -234 warnings.warn( -235 "Device management is not implemented for keras yet, cannot unload model" -236 ) +159 out_names = [ # pyright: ignore[reportUnknownVariableType] +160 signature[signature_key].outputs[key].name for key in output_keys +161 ] +162 in_tensors = [ # pyright: ignore[reportUnknownVariableType] +163 graph.get_tensor_by_name(name) +164 for name in in_names # pyright: ignore[reportUnknownVariableType] +165 ] +166 out_tensors = [ # pyright: ignore[reportUnknownVariableType] +167 graph.get_tensor_by_name(name) +168 for name in out_names # pyright: ignore[reportUnknownVariableType] +169 ] +170 +171 # run prediction +172 res = sess.run( # pyright: ignore[reportUnknownVariableType] +173 dict( +174 zip( +175 out_names, # pyright: ignore[reportUnknownArgumentType] +176 out_tensors, # pyright: ignore[reportUnknownArgumentType] +177 ) +178 ), +179 dict( +180 zip( +181 in_tensors, # pyright: ignore[reportUnknownArgumentType] +182 input_tensors, +183 ) +184 ), +185 ) +186 # from dict to list of tensors +187 res = [ # pyright: ignore[reportUnknownVariableType] +188 res[out] +189 for out in out_names # pyright: ignore[reportUnknownVariableType] +190 ] +191 +192 return res # pyright: ignore[reportUnknownVariableType] +193 +194 def _forward_keras( # pyright: ignore[reportUnknownParameterType] +195 self, *input_tensors: Optional[Tensor] +196 ): +197 assert self.use_keras_api +198 assert not isinstance(self._network, str) +199 assert tf is not None +200 tf_tensor = [ # pyright: ignore[reportUnknownVariableType] +201 None if ipt is None else tf.convert_to_tensor(ipt) for ipt in input_tensors +202 ] +203 +204 result = self._network(*tf_tensor) # pyright: ignore[reportUnknownVariableType] +205 +206 assert isinstance(result, dict) +207 +208 # TODO: Use RDF's `outputs[i].id` here +209 result = list(result.values()) +210 +211 return [ # pyright: ignore[reportUnknownVariableType] +212 (None if r is None else r if isinstance(r, np.ndarray) else r.numpy()) +213 for r in result # pyright: ignore[reportUnknownVariableType] +214 ] +215 +216 def forward(self, *input_tensors: Optional[Tensor]) -> List[Optional[Tensor]]: +217 data = [None if ipt is None else ipt.data for ipt in input_tensors] +218 if self.use_keras_api: +219 result = self._forward_keras( # pyright: ignore[reportUnknownVariableType] +220 *data +221 ) +222 else: +223 result = self._forward_tf( # pyright: ignore[reportUnknownVariableType] +224 *data +225 ) +226 +227 return [ +228 None if r is None else Tensor(r, dims=axes) +229 for r, axes in zip( # pyright: ignore[reportUnknownVariableType] +230 result, # pyright: ignore[reportUnknownArgumentType] +231 self._internal_output_axes, +232 ) +233 ] +234 +235 def unload(self) -> None: +236 logger.warning( +237 "Device management is not implemented for keras yet, cannot unload model" +238 )

@@ -658,19 +662,19 @@

47 ) 48 model_tf_version = weights.tensorflow_version 49 if model_tf_version is None: -50 warnings.warn( +50 logger.warning( 51 "The model does not specify the tensorflow version." 52 + f"Cannot check if it is compatible with intalled tensorflow {tf_version}." 53 ) 54 elif model_tf_version > tf_version: -55 warnings.warn( +55 logger.warning( 56 f"The model specifies a newer tensorflow version than installed: {model_tf_version} > {tf_version}." 57 ) 58 elif (model_tf_version.major, model_tf_version.minor) != ( 59 tf_version.major, 60 tf_version.minor, 61 ): -62 warnings.warn( +62 logger.warning( 63 "The tensorflow version specified by the model does not match the installed: " 64 + f"{model_tf_version} != {tf_version}." 65 ) @@ -682,7 +686,7 @@

71 72 # TODO tf device management 73 if devices is not None: -74 warnings.warn( +74 logger.warning( 75 f"Device management is not implemented for tensorflow yet, ignoring the devices {devices}" 76 ) 77 @@ -769,24 +773,24 @@

-
214    def forward(self, *input_tensors: Optional[Tensor]) -> List[Optional[Tensor]]:
-215        data = [None if ipt is None else ipt.data for ipt in input_tensors]
-216        if self.use_keras_api:
-217            result = self._forward_keras(  # pyright: ignore[reportUnknownVariableType]
-218                *data
-219            )
-220        else:
-221            result = self._forward_tf(  # pyright: ignore[reportUnknownVariableType]
-222                *data
-223            )
-224
-225        return [
-226            None if r is None else Tensor(r, dims=axes)
-227            for r, axes in zip(  # pyright: ignore[reportUnknownVariableType]
-228                result,  # pyright: ignore[reportUnknownArgumentType]
-229                self._internal_output_axes,
-230            )
-231        ]
+            
216    def forward(self, *input_tensors: Optional[Tensor]) -> List[Optional[Tensor]]:
+217        data = [None if ipt is None else ipt.data for ipt in input_tensors]
+218        if self.use_keras_api:
+219            result = self._forward_keras(  # pyright: ignore[reportUnknownVariableType]
+220                *data
+221            )
+222        else:
+223            result = self._forward_tf(  # pyright: ignore[reportUnknownVariableType]
+224                *data
+225            )
+226
+227        return [
+228            None if r is None else Tensor(r, dims=axes)
+229            for r, axes in zip(  # pyright: ignore[reportUnknownVariableType]
+230                result,  # pyright: ignore[reportUnknownArgumentType]
+231                self._internal_output_axes,
+232            )
+233        ]
 
@@ -806,10 +810,10 @@

-
233    def unload(self) -> None:
-234        warnings.warn(
-235            "Device management is not implemented for keras yet, cannot unload model"
-236        )
+            
235    def unload(self) -> None:
+236        logger.warning(
+237            "Device management is not implemented for keras yet, cannot unload model"
+238        )
 
@@ -841,23 +845,23 @@
Inherited Members
-
239class TensorflowModelAdapter(TensorflowModelAdapterBase):
-240    weight_format = "tensorflow_saved_model_bundle"
-241
-242    def __init__(
-243        self,
-244        *,
-245        model_description: Union[v0_4.ModelDescr, v0_5.ModelDescr],
-246        devices: Optional[Sequence[str]] = None,
-247    ):
-248        if model_description.weights.tensorflow_saved_model_bundle is None:
-249            raise ValueError("missing tensorflow_saved_model_bundle weights")
-250
-251        super().__init__(
-252            devices=devices,
-253            weights=model_description.weights.tensorflow_saved_model_bundle,
-254            model_description=model_description,
-255        )
+            
241class TensorflowModelAdapter(TensorflowModelAdapterBase):
+242    weight_format = "tensorflow_saved_model_bundle"
+243
+244    def __init__(
+245        self,
+246        *,
+247        model_description: Union[v0_4.ModelDescr, v0_5.ModelDescr],
+248        devices: Optional[Sequence[str]] = None,
+249    ):
+250        if model_description.weights.tensorflow_saved_model_bundle is None:
+251            raise ValueError("missing tensorflow_saved_model_bundle weights")
+252
+253        super().__init__(
+254            devices=devices,
+255            weights=model_description.weights.tensorflow_saved_model_bundle,
+256            model_description=model_description,
+257        )
 
@@ -889,20 +893,20 @@
Inherited Members
-
242    def __init__(
-243        self,
-244        *,
-245        model_description: Union[v0_4.ModelDescr, v0_5.ModelDescr],
-246        devices: Optional[Sequence[str]] = None,
-247    ):
-248        if model_description.weights.tensorflow_saved_model_bundle is None:
-249            raise ValueError("missing tensorflow_saved_model_bundle weights")
-250
-251        super().__init__(
-252            devices=devices,
-253            weights=model_description.weights.tensorflow_saved_model_bundle,
-254            model_description=model_description,
-255        )
+            
244    def __init__(
+245        self,
+246        *,
+247        model_description: Union[v0_4.ModelDescr, v0_5.ModelDescr],
+248        devices: Optional[Sequence[str]] = None,
+249    ):
+250        if model_description.weights.tensorflow_saved_model_bundle is None:
+251            raise ValueError("missing tensorflow_saved_model_bundle weights")
+252
+253        super().__init__(
+254            devices=devices,
+255            weights=model_description.weights.tensorflow_saved_model_bundle,
+256            model_description=model_description,
+257        )
 
@@ -951,23 +955,23 @@
Inherited Members
-
258class KerasModelAdapter(TensorflowModelAdapterBase):
-259    weight_format = "keras_hdf5"
-260
-261    def __init__(
-262        self,
-263        *,
-264        model_description: Union[v0_4.ModelDescr, v0_5.ModelDescr],
-265        devices: Optional[Sequence[str]] = None,
-266    ):
-267        if model_description.weights.keras_hdf5 is None:
-268            raise ValueError("missing keras_hdf5 weights")
-269
-270        super().__init__(
-271            model_description=model_description,
-272            devices=devices,
-273            weights=model_description.weights.keras_hdf5,
-274        )
+            
260class KerasModelAdapter(TensorflowModelAdapterBase):
+261    weight_format = "keras_hdf5"
+262
+263    def __init__(
+264        self,
+265        *,
+266        model_description: Union[v0_4.ModelDescr, v0_5.ModelDescr],
+267        devices: Optional[Sequence[str]] = None,
+268    ):
+269        if model_description.weights.keras_hdf5 is None:
+270            raise ValueError("missing keras_hdf5 weights")
+271
+272        super().__init__(
+273            model_description=model_description,
+274            devices=devices,
+275            weights=model_description.weights.keras_hdf5,
+276        )
 
@@ -999,20 +1003,20 @@
Inherited Members
-
261    def __init__(
-262        self,
-263        *,
-264        model_description: Union[v0_4.ModelDescr, v0_5.ModelDescr],
-265        devices: Optional[Sequence[str]] = None,
-266    ):
-267        if model_description.weights.keras_hdf5 is None:
-268            raise ValueError("missing keras_hdf5 weights")
-269
-270        super().__init__(
-271            model_description=model_description,
-272            devices=devices,
-273            weights=model_description.weights.keras_hdf5,
-274        )
+            
263    def __init__(
+264        self,
+265        *,
+266        model_description: Union[v0_4.ModelDescr, v0_5.ModelDescr],
+267        devices: Optional[Sequence[str]] = None,
+268    ):
+269        if model_description.weights.keras_hdf5 is None:
+270            raise ValueError("missing keras_hdf5 weights")
+271
+272        super().__init__(
+273            model_description=model_description,
+274            devices=devices,
+275            weights=model_description.weights.keras_hdf5,
+276        )