@@ -38,6 +38,41 @@ def test_model(
3838 return {"error" : f"Expected RDF type Model, got { type (model )} instead." , "traceback" : None }
3939
4040
41+ def _validate_input_shape (shape : Tuple [int , ...], shape_spec ) -> bool :
42+ if isinstance (shape_spec , list ):
43+ if shape != tuple (shape_spec ):
44+ return False
45+ elif isinstance (shape_spec , ParametrizedInputShape ):
46+ assert len (shape_spec .min ) == len (shape_spec .step )
47+ if len (shape ) != len (shape_spec .min ):
48+ return False
49+ min_shape = shape_spec .min
50+ step = shape_spec .step
51+ # check if the shape is valid for all dimension by seeing if it can be reached with an integer number of steps
52+ # NOTE we allow that the valid shape is reached using a different number of steps for each axis here
53+ # this is usually valid because dimensions are independent in neural networks
54+ is_valid = [(sh - minsh ) % st == 0 if st > 0 else sh == minsh for sh , st , minsh in zip (shape , step , min_shape )]
55+ return all (is_valid )
56+ else :
57+ raise TypeError (f"Encountered unexpected shape description of type { type (shape_spec )} " )
58+
59+ return True
60+
61+
62+ def _validate_output_shape (shape : Tuple [int , ...], shape_spec , input_shapes ) -> bool :
63+ if isinstance (shape_spec , list ):
64+ return shape == tuple (shape_spec )
65+ elif isinstance (shape_spec , ImplicitOutputShape ):
66+ ipt_shape = numpy .array (input_shapes [shape_spec .reference_tensor ])
67+ scale = numpy .array (shape_spec .scale )
68+ offset = numpy .array (shape_spec .offset )
69+ exp_shape = numpy .round_ (ipt_shape * scale ) + 2 * offset
70+
71+ return shape == tuple (exp_shape )
72+ else :
73+ raise TypeError (f"Encountered unexpected shape description of type { type (shape_spec )} " )
74+
75+
4176def test_resource (
4277 model_rdf : Union [RawResourceDescription , ResourceDescription , URI , Path , str ],
4378 * ,
@@ -64,65 +99,22 @@ def test_resource(
6499 inputs = [np .load (str (in_path )) for in_path in model .test_inputs ]
65100 expected = [np .load (str (out_path )) for out_path in model .test_outputs ]
66101
67- # check if test data shapes match their description
68- input_shapes = [ipt .shape for ipt in inputs ]
69- output_shapes = [out .shape for out in expected ]
70-
71- def input_shape_is_valid (shape : Tuple [int , ...], shape_spec ) -> bool :
72- if isinstance (shape_spec , list ):
73- if shape != tuple (shape_spec ):
74- return False
75- elif isinstance (shape_spec , ParametrizedInputShape ):
76- assert len (shape_spec .min ) == len (shape_spec .step )
77- if len (shape ) != len (shape_spec .min ):
78- return False
79-
80- valid_shape = numpy .array (shape_spec .min )
81- step = numpy .array (shape_spec .step )
82- if (step == 0 ).all ():
83- return shape == tuple (valid_shape )
84-
85- shape = numpy .array (shape )
86- while (shape <= valid_shape ).all ():
87- if (shape == valid_shape ).all ():
88- break
89-
90- shape += step
91- else :
92- return False
93-
94- else :
95- raise TypeError (f"Encountered unexpected shape description of type { type (shape_spec )} " )
96-
97- return True
98-
99102 assert len (inputs ) == len (model .inputs ) # should be checked by validation
100103 input_shapes = {}
101104 for idx , (ipt , ipt_spec ) in enumerate (zip (inputs , model .inputs )):
102- if not input_shape_is_valid ( ipt , ipt_spec .shape ):
105+ if not _validate_input_shape ( tuple ( ipt . shape ) , ipt_spec .shape ):
103106 raise ValidationError (
104- f"Shape of test input { idx } '{ ipt_spec .name } ' does not match "
105- f"input shape description: { ipt_spec .shape } "
107+ f"Shape { tuple ( ipt . shape ) } of test input { idx } '{ ipt_spec .name } ' does not match "
108+ f"input shape description: { ipt_spec .shape } . "
106109 )
107110 input_shapes [ipt_spec .name ] = ipt .shape
108111
109- def output_shape_is_valid (shape : Tuple [int , ...], shape_spec ) -> bool :
110- if isinstance (shape_spec , list ):
111- return shape == tuple (shape_spec )
112- elif isinstance (shape_spec , ImplicitOutputShape ):
113- ipt_shape = numpy .array (input_shapes [shape_spec .reference_tensor ])
114- scale = numpy .array (shape_spec .scale )
115- offset = numpy .array (shape_spec .offset )
116- exp_shape = numpy .round_ (ipt_shape * scale ) + 2 * offset
117-
118- return shape == tuple (exp_shape )
119-
120112 assert len (expected ) == len (model .outputs ) # should be checked by validation
121113 for idx , (out , out_spec ) in enumerate (zip (expected , model .outputs )):
122- if not output_shape_is_valid ( out , out_spec .shape ):
114+ if not _validate_output_shape ( tuple ( out . shape ) , out_spec .shape , input_shapes ):
123115 error = (error or "" ) + (
124- f"Shape of test output { idx } '{ out_spec .name } ' does not match "
125- f"output shape description: { out_spec .shape } .\n "
116+ f"Shape { tuple ( out . shape ) } of test output { idx } '{ out_spec .name } ' does not match "
117+ f"output shape description: { out_spec .shape } ."
126118 )
127119
128120 with create_prediction_pipeline (
0 commit comments