Download model.py from EvanZhongg/StrandSeg-Lite: direct link, hf CLI and curl.
- Browser
- Download file 3.68 kB
-
https://huggingface.co/EvanZhongg/StrandSeg-Lite/resolve/main/model.py
- Command line
-
hf download hf://EvanZhongg/StrandSeg-Lite/model.py
-
curl -L -o model.py https://huggingface.co/EvanZhongg/StrandSeg-Lite/resolve/main/model.py
3.68 kB
| """Standalone MobileNetV3-FPN string segmentation model.""" | |
| from __future__ import annotations | |
| from pathlib import Path | |
| from typing import Any | |
| import torch | |
| from torch import nn | |
| import torch.nn.functional as F | |
| CHECKPOINT_FORMATS = {"yoyo_string_semantic_transfer_v1", "yoyo_string_semantic_unet_v1"} | |
| def _group_count(channels: int) -> int: | |
| for groups in (8, 4, 2, 1): | |
| if channels % groups == 0: | |
| return groups | |
| return 1 | |
| class _FPNRefine(nn.Module): | |
| def __init__(self, channels: int): | |
| super().__init__() | |
| self.layers = nn.Sequential( | |
| nn.Conv2d(channels, channels, 3, padding=1, groups=channels, bias=False), | |
| nn.GroupNorm(_group_count(channels), channels), | |
| nn.SiLU(inplace=True), | |
| nn.Conv2d(channels, channels, 1, bias=False), | |
| nn.GroupNorm(_group_count(channels), channels), | |
| nn.SiLU(inplace=True), | |
| ) | |
| def forward(self, value: torch.Tensor) -> torch.Tensor: | |
| return self.layers(value) | |
| class MobileNetV3FPNString(nn.Module): | |
| """MobileNetV3-Large encoder with a high-resolution FPN decoder.""" | |
| _FEATURE_INDICES = (1, 3, 6, 12, 16) | |
| _FEATURE_CHANNELS = (16, 24, 40, 112, 960) | |
| def __init__(self, decoder_channels: int = 32): | |
| super().__init__() | |
| from torchvision.models import mobilenet_v3_large | |
| self.encoder = mobilenet_v3_large(weights=None).features | |
| self.lateral = nn.ModuleList( | |
| nn.Sequential( | |
| nn.Conv2d(input_channels, decoder_channels, 1, bias=False), | |
| nn.GroupNorm(_group_count(decoder_channels), decoder_channels), | |
| ) | |
| for input_channels in self._FEATURE_CHANNELS | |
| ) | |
| self.refine = nn.ModuleList(_FPNRefine(decoder_channels) for _ in range(4)) | |
| self.classifier = nn.Sequential( | |
| _FPNRefine(decoder_channels), | |
| nn.Conv2d(decoder_channels, 1, 1), | |
| ) | |
| def forward(self, value: torch.Tensor) -> torch.Tensor: | |
| input_size = value.shape[-2:] | |
| features = [] | |
| selected = set(self._FEATURE_INDICES) | |
| for index, layer in enumerate(self.encoder): | |
| value = layer(value) | |
| if index in selected: | |
| features.append(value) | |
| pyramid = self.lateral[-1](features[-1]) | |
| for level in range(len(features) - 2, -1, -1): | |
| pyramid = F.interpolate(pyramid, size=features[level].shape[-2:], mode="bilinear", align_corners=False) | |
| pyramid = self.refine[level](pyramid + self.lateral[level](features[level])) | |
| logits = self.classifier(pyramid) | |
| return F.interpolate(logits, size=input_size, mode="bilinear", align_corners=False) | |
| def load_model(checkpoint: str | Path, device: str | torch.device = "cpu") -> tuple[nn.Module, dict[str, Any]]: | |
| """Load a StrandSeg-Lite checkpoint without importing the training repo.""" | |
| target = torch.device(device) | |
| payload = torch.load(Path(checkpoint), map_location=target, weights_only=True) | |
| if not isinstance(payload, dict) or payload.get("format") not in CHECKPOINT_FORMATS: | |
| raise ValueError(f"Unsupported StrandSeg-Lite checkpoint: {checkpoint}") | |
| config = dict(payload.get("model_config") or {}) | |
| architecture = str(config.get("architecture", "mobilenet_v3_fpn")).lower() | |
| if architecture != "mobilenet_v3_fpn": | |
| raise ValueError(f"This package supports mobilenet_v3_fpn, got {architecture!r}") | |
| model = MobileNetV3FPNString(decoder_channels=max(16, int(config.get("base_channels", 16)) * 2)) | |
| model.load_state_dict(payload["state_dict"]) | |
| model.to(target).eval() | |
| return model, payload | |