Skip to content

Commit d11a46e

Browse files
committed
Lazy-load PyTorch in POET backend
Refactors the POET processor to import and validate PyTorch only when POET inference is used, avoiding hard import failures at module load time. It adds explicit runtime errors for missing/incomplete torch installs, moves normalization tensor setup into initialization, replaces the decorator with an inference-mode context, and resets normalization state on shutdown for safer lifecycle handling.
1 parent f9ba466 commit d11a46e

1 file changed

Lines changed: 75 additions & 15 deletions

File tree

‎dlclivegui/services/inference/models/poet/poet_processor.py‎

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
from __future__ import annotations
44

5+
import importlib
56
import logging
67
from pathlib import Path
8+
from types import ModuleType
79
from typing import Any
810

911
import numpy as np
10-
import torch
1112

1213
from dlclivegui.services.inference.base import (
1314
PoseBackend,
@@ -16,11 +17,45 @@
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

2126
logger = 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+
2459
class 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

Comments
 (0)