55import torch
66from PIL import Image
77
8+ from anomavision .inference .model .backends .hailo_backend import HailoAnomalyRuntime
89from anomavision .quantize .model .backends .hef import graphs as hailo_graphs
910from anomavision .quantize .model .backends .hef .exporter import (
1011 _write_calibration_manifest ,
@@ -18,7 +19,6 @@ def __init__(self, backbone, device):
1819
1920 def forward (self , image , layer_indices = None ):
2021 batch = image .shape [0 ]
21- # Four-by-four patch grid with four channels for compact graph tests.
2222 features = torch .nn .functional .adaptive_avg_pool2d (image , (4 , 4 ))
2323 features = features .mean (dim = 1 , keepdim = True ).repeat (1 , 4 , 1 , 1 )
2424 return features .permute (0 , 2 , 3 , 1 ).reshape (batch , 16 , 4 ), 4 , 4
@@ -31,12 +31,8 @@ def _patch_fake_extractor(monkeypatch):
3131def test_padim_graph_contains_distance_and_reduction (monkeypatch ):
3232 _patch_fake_extractor (monkeypatch )
3333 graph = hailo_graphs .PadimEndToEndGraph (
34- backbone = "resnet18" ,
35- layer_indices = [0 , 1 ],
36- channel_indices = torch .arange (4 ),
37- mean = torch .zeros (16 , 4 ),
38- cov_inv = torch .eye (4 ).repeat (16 , 1 , 1 ),
39- input_size = (32 , 32 ),
34+ backbone = "resnet18" , layer_indices = [0 , 1 ], channel_indices = torch .arange (4 ),
35+ mean = torch .zeros (16 , 4 ), cov_inv = torch .eye (4 ).repeat (16 , 1 , 1 ), input_size = (32 , 32 )
4036 ).eval ()
4137 image_scores , score_map = graph (torch .ones (1 , 3 , 32 , 32 ))
4238 assert image_scores .shape == (1 ,)
@@ -48,11 +44,8 @@ def test_padim_graph_contains_distance_and_reduction(monkeypatch):
4844def test_patchcore_graph_contains_memory_distance_and_reduction (monkeypatch ):
4945 _patch_fake_extractor (monkeypatch )
5046 graph = hailo_graphs .PatchCoreEndToEndGraph (
51- backbone = "resnet18" ,
52- layer_indices = [0 , 1 ],
53- memory_bank = torch .zeros (8 , 4 ),
54- patch_grid = 4 ,
55- input_size = (32 , 32 ),
47+ backbone = "resnet18" , layer_indices = [0 , 1 ], memory_bank = torch .zeros (8 , 4 ),
48+ patch_grid = 4 , input_size = (32 , 32 )
5649 ).eval ()
5750 image_scores , score_map = graph (torch .ones (1 , 3 , 32 , 32 ))
5851 assert image_scores .shape == (1 ,)
@@ -61,20 +54,10 @@ def test_patchcore_graph_contains_memory_distance_and_reduction(monkeypatch):
6154 assert torch .isfinite (score_map ).all ()
6255
6356
64- def test_export_writes_end_to_end_metadata_and_calibration_manifest (
65- tmp_path , monkeypatch
66- ):
57+ def test_export_writes_end_to_end_metadata_and_calibration_manifest (tmp_path , monkeypatch ):
6758 _patch_fake_extractor (monkeypatch )
6859 artifact = tmp_path / "patchcore.pt"
69- torch .save (
70- {
71- "backbone" : "resnet18" ,
72- "layer_indices" : [0 , 1 ],
73- "memory_bank" : torch .zeros (8 , 4 ),
74- "patch_grid" : 4 ,
75- },
76- artifact ,
77- )
60+ torch .save ({"backbone" : "resnet18" , "layer_indices" : [0 , 1 ], "memory_bank" : torch .zeros (8 , 4 ), "patch_grid" : 4 }, artifact )
7861 calibration = tmp_path / "calibration"
7962 calibration .mkdir ()
8063 Image .fromarray (np .zeros ((32 , 32 , 3 ), dtype = np .uint8 )).save (calibration / "one.png" )
@@ -83,9 +66,35 @@ def test_export_writes_end_to_end_metadata_and_calibration_manifest(
8366 assert onnx_path .exists ()
8467 manifest = _write_calibration_manifest (calibration , output , (32 , 32 ))
8568 assert manifest .exists ()
69+ calibration_array = np .load (output / "calibration_npy" / "sample_0000.npy" )
70+ expected = - (np .asarray ([0.485 , 0.456 , 0.406 ]) / np .asarray ([0.229 , 0.224 , 0.225 ]))
71+ np .testing .assert_allclose (calibration_array [0 , 0 ], expected , atol = 1e-6 )
8672 assert onnx_path .name .endswith ("_end_to_end.onnx" )
8773
8874
75+ def test_hailo_preprocessed_tensor_only_transposes ():
76+ runtime = HailoAnomalyRuntime .__new__ (HailoAnomalyRuntime )
77+ runtime .input_size = (32 , 32 )
78+ runtime .input_dtype = np .float32
79+ runtime .mean = np .asarray ([0.485 , 0.456 , 0.406 ], dtype = np .float32 ).reshape (1 , 1 , 3 )
80+ runtime .std = np .asarray ([0.229 , 0.224 , 0.225 ], dtype = np .float32 ).reshape (1 , 1 , 3 )
81+ nchw = np .random .default_rng (42 ).normal (size = (1 , 3 , 32 , 32 )).astype (np .float32 )
82+ prepared = runtime ._prepare_input (nchw )
83+ np .testing .assert_allclose (prepared , np .transpose (nchw [0 ], (1 , 2 , 0 )))
84+
85+
86+ def test_hailo_raw_image_is_normalized_once ():
87+ runtime = HailoAnomalyRuntime .__new__ (HailoAnomalyRuntime )
88+ runtime .input_size = (32 , 32 )
89+ runtime .input_dtype = np .float32
90+ runtime .mean = np .asarray ([0.485 , 0.456 , 0.406 ], dtype = np .float32 ).reshape (1 , 1 , 3 )
91+ runtime .std = np .asarray ([0.229 , 0.224 , 0.225 ], dtype = np .float32 ).reshape (1 , 1 , 3 )
92+ raw = np .full ((32 , 32 , 3 ), 255 , dtype = np .uint8 )
93+ prepared = runtime ._prepare_input (raw )
94+ expected = (1.0 - runtime .mean ) / runtime .std
95+ np .testing .assert_allclose (prepared , expected , atol = 1e-6 )
96+
97+
8998def test_export_rejects_partial_artifact (tmp_path ):
9099 artifact = tmp_path / "bad.pt"
91100 torch .save ({"backbone" : "resnet18" }, artifact )
0 commit comments