22
33from __future__ import annotations
44
5+ import importlib
56import logging
67from pathlib import Path
8+ from types import ModuleType
79from typing import Any
810
911import numpy as np
10- import torch
1112
1213from dlclivegui .services .inference .base import (
1314 PoseBackend ,
1617 PoseSource ,
1718)
1819
19- from .skeleton import POET_KEYPOINT_NAMES , POET_SKELETON_EDGES , POET_SKELETON_ID
20+ from .skeleton import (
21+ POET_KEYPOINT_NAMES ,
22+ POET_SKELETON_EDGES ,
23+ POET_SKELETON_ID ,
24+ )
2025
2126logger = logging .getLogger (__name__ )
2227
2328
29+ def _require_torch () -> ModuleType :
30+ """Import PyTorch when POET inference is actually requested."""
31+ try :
32+ torch = importlib .import_module ("torch" )
33+ except ImportError as exc :
34+ raise RuntimeError (
35+ "POET requires PyTorch. Install the POET optional dependencies before using this backend."
36+ ) from exc
37+
38+ required_attributes = (
39+ "Tensor" ,
40+ "autocast" ,
41+ "cuda" ,
42+ "device" ,
43+ "from_numpy" ,
44+ "inference_mode" ,
45+ "load" ,
46+ "tensor" ,
47+ )
48+ missing = [name for name in required_attributes if not hasattr (torch , name )]
49+
50+ if missing :
51+ raise RuntimeError (
52+ "The installed 'torch' module is not a complete PyTorch "
53+ f"installation. Missing attributes: { ', ' .join (missing )} ."
54+ )
55+
56+ return torch
57+
58+
2459class POETBackend (PoseBackend ):
2560 """POET pose-inference backend using COCO-17 keypoints."""
2661
@@ -47,15 +82,15 @@ def __init__(
4782
4883 self ._model : Any | None = None
4984 self ._postprocessor : Any | None = None
50- self ._device : torch .device | None = None
51-
52- self ._mean = torch .tensor ([0.485 , 0.456 , 0.406 ]).view (1 , 3 , 1 , 1 )
53- self ._std = torch .tensor ([0.229 , 0.224 , 0.225 ]).view (1 , 3 , 1 , 1 )
85+ self ._device : Any | None = None
86+ self ._mean : Any | None = None
87+ self ._std : Any | None = None
5488
5589 @staticmethod
5690 def _resolve_torch_device (
5791 device : str | None ,
58- ) -> torch .device :
92+ ) -> Any :
93+ torch = _require_torch ()
5994 requested = device .strip ().lower () if device else "auto"
6095
6196 if requested in {"auto" , "best" }:
@@ -80,33 +115,42 @@ def init_inference(
80115 self ,
81116 init_frame : np .ndarray ,
82117 ) -> None :
118+ torch = _require_torch ()
119+
120+ self ._mean = torch .tensor ([0.485 , 0.456 , 0.406 ]).view (1 , 3 , 1 , 1 )
121+ self ._std = torch .tensor ([0.229 , 0.224 , 0.225 ]).view (1 , 3 , 1 , 1 )
122+
83123 (
84124 self ._model ,
85125 self ._postprocessor ,
86126 self ._device ,
87127 ) = self ._build_model ()
88128
89129 # Warm up without emitting a result. PoseProcessor emits the
90- # initial frame's result after init_inference() returns.
130+ # initial frame result after init_inference() returns.
91131 self .get_pose (init_frame )
92132
93133 def _build_model (
94134 self ,
95- ) -> tuple [Any , Any , torch .device ]:
135+ ) -> tuple [Any , Any , Any ]:
136+ torch = _require_torch ()
137+
96138 try :
97139 from poet_live import POET , PostProcess
98- from poet_live .models .backbone import Backbone , Joiner
140+ from poet_live .models .backbone import (
141+ Backbone ,
142+ Joiner ,
143+ )
99144 from poet_live .models .position_encoding import (
100145 PositionEmbeddingSine ,
101146 )
102147 from poet_live .models .transformer import Transformer
103148 except ImportError as exc :
104149 raise RuntimeError (
105- "POET is not installed. Install the POET dependencies before using this pose backend."
150+ "POET is not installed. Install the POET optional dependencies before using this backend."
106151 ) from exc
107152
108153 device = self ._resolve_torch_device (self ._requested_device )
109-
110154 hidden_dimension = 256
111155
112156 backbone = Backbone (
@@ -151,31 +195,45 @@ def _build_model(
151195 model .to (device ).eval ()
152196 postprocessor = PostProcess ().to (device )
153197
198+ if self ._mean is None or self ._std is None :
199+ raise RuntimeError ("POET normalization tensors are not initialized." )
200+
154201 self ._mean = self ._mean .to (device )
155202 self ._std = self ._std .to (device )
156203
157204 return model , postprocessor , device
158205
159- @torch .no_grad ()
160206 def get_pose (
161207 self ,
162208 frame : np .ndarray ,
163209 frame_time : float | None = None ,
164210 ) -> np .ndarray | None :
165211 del frame_time
166212
213+ torch = _require_torch ()
214+
215+ with torch .inference_mode ():
216+ return self ._infer (frame , torch )
217+
218+ def _infer (
219+ self ,
220+ frame : np .ndarray ,
221+ torch : ModuleType ,
222+ ) -> np .ndarray | None :
167223 model = self ._model
168224 postprocessor = self ._postprocessor
169225 device = self ._device
226+ mean = self ._mean
227+ std = self ._std
170228
171- if model is None or postprocessor is None or device is None :
229+ if model is None or postprocessor is None or device is None or mean is None or std is None :
172230 raise RuntimeError ("POET backend is not initialized." )
173231
174232 rgb = frame [..., ::- 1 ].copy ()
175233 height , width = rgb .shape [:2 ]
176234
177235 image = torch .from_numpy (rgb ).to (device ).permute (2 , 0 , 1 ).float ().unsqueeze (0 ) / 255.0
178- image = (image - self . _mean ) / self . _std
236+ image = (image - mean ) / std
179237
180238 use_amp = self ._use_amp and device .type == "cuda"
181239
@@ -243,3 +301,5 @@ def close(self) -> None:
243301 self ._model = None
244302 self ._postprocessor = None
245303 self ._device = None
304+ self ._mean = None
305+ self ._std = None
0 commit comments