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 @@
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 +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 @@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
@@ -505,20 +513,20 @@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# }
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 ) +@@ -538,25 +546,25 @@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 )
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 +@@ -576,49 +584,49 @@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
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 +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 @@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 rd42 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 @@
-
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 @@@@ -658,19 +662,19 @@1import warnings - 2import zipfile - 3from typing import List, Literal, Optional, Sequence, Union - 4 - 5import numpy as np +@@ -418,19 +420,19 @@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 )
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 )
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 ] +@@ -806,10 +810,10 @@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 ]
233 def unload(self) -> None: -234 warnings.warn( -235 "Device management is not implemented for keras yet, cannot unload model" -236 ) +@@ -841,23 +845,23 @@235 def unload(self) -> None: +236 logger.warning( +237 "Device management is not implemented for keras yet, cannot unload model" +238 )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 ) +@@ -889,20 +893,20 @@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 )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 ) +@@ -951,23 +955,23 @@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 )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 ) +@@ -999,20 +1003,20 @@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 )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 )