Download model.py from DriftFox/voronoi-microstructure-resnet: direct link, hf CLI and curl.
- Browser
- Download file 3.87 kB
-
https://huggingface.co/DriftFox/voronoi-microstructure-resnet/resolve/main/model.py
- Command line
-
hf download hf://DriftFox/voronoi-microstructure-resnet/model.py
-
curl -L -o model.py https://huggingface.co/DriftFox/voronoi-microstructure-resnet/resolve/main/model.py
3.87 kB
| """Load synthetic-pretrained Voronoi microstructure ResNets without pickle.""" | |
| from pathlib import Path | |
| import torch | |
| from torch import nn | |
| from torchvision import models | |
| from safetensors.torch import load_file | |
| TARGET_NAMES = [ | |
| 'log_grains', 'mean_ecd', 'area_weighted_ecd', 'ecd_cv', | |
| 'hall_petch_mean_ecd', 'phase_area_fraction', 'phase_grain_fraction', | |
| 'log_stretch', 'log_defects', 'defect_area_fraction', | |
| 'mean_defect_radius', 'boundary_fraction', 'interior_grain_fraction', | |
| ] | |
| VARIANTS = { | |
| 'resnet18_seed1019': 'resnet18', | |
| 'resnet18_seed2026': 'resnet18', | |
| 'resnet34_seed1019': 'resnet34', | |
| 'resnet50_seed1019': 'resnet50', | |
| } | |
| class VoronoiResNet(nn.Module): | |
| def __init__(self, architecture): | |
| super().__init__() | |
| if architecture not in {'resnet18', 'resnet34', 'resnet50'}: | |
| raise ValueError('Expected resnet18, resnet34, or resnet50') | |
| self.encoder = getattr(models, architecture)(weights=None) | |
| dimension = self.encoder.fc.in_features | |
| self.encoder.fc = nn.Identity() | |
| self.head = nn.Sequential(nn.Linear(dimension, 128), nn.SiLU(), nn.Linear(128, 26)) | |
| self.register_buffer('mean', torch.tensor([.485, .456, .406])[None, :, None, None]) | |
| self.register_buffer('scale', torch.tensor([.229, .224, .225])[None, :, None, None]) | |
| self.register_buffer('target_mean', torch.zeros(13)) | |
| self.register_buffer('target_scale', torch.ones(13)) | |
| def forward(self, images): | |
| """Return normalized means, clamped log variances, and encoder embeddings.""" | |
| if images.ndim != 4 or images.shape[1:] != (1, 256, 256): | |
| raise ValueError('Expected floating point images shaped [N, 1, 256, 256]') | |
| if not images.is_floating_point(): | |
| raise ValueError('Convert image pixels to floating point in [0, 1] first') | |
| z = self.encoder((images.repeat(1, 3, 1, 1) - self.mean) / self.scale) | |
| p = self.head(z) | |
| return p[:, :13], p[:, 13:].clamp(-4, 3), z | |
| def predict(self, images, tta=True): | |
| """Return parameter estimates, standard deviations, and mean embeddings. | |
| Log-transformed targets stay in their named log domain. Uncertainty is | |
| estimated by the synthetic model and is not calibrated on real images. | |
| """ | |
| if self.training: | |
| raise ValueError('Call model.eval() before inference') | |
| means, variances, embeddings = [], [], [] | |
| views = ((flip, rotation) for flip in (False, True) for rotation in range(4)) if tta else [(False, 0)] | |
| for flip, rotation in views: | |
| x = images.flip(-1) if flip else images | |
| p, lv, z = self(torch.rot90(x, rotation, (-2, -1))) | |
| means.append(p.float()) | |
| variances.append(lv.float().exp()) | |
| embeddings.append(z.float()) | |
| p = torch.stack(means) | |
| parameters = p.mean(0) * self.target_scale + self.target_mean | |
| variance = torch.stack(variances).mean(0) + p.var(0, unbiased=False) | |
| uncertainty = variance.sqrt() * self.target_scale | |
| return parameters, uncertainty, torch.stack(embeddings).mean(0) | |
| def load_model(directory, variant='resnet18_seed1019', device='cpu'): | |
| """Load one variant from a downloaded repository directory.""" | |
| model = VoronoiResNet(VARIANTS[variant]) | |
| model.load_state_dict(load_file(str(Path(directory) / f'{variant}.safetensors')), strict=True) | |
| return model.to(device).eval() | |
| if __name__ == '__main__': | |
| torch.set_num_threads(2) | |
| model = load_model(Path(__file__).resolve().parent) | |
| p, u, z = model.predict(torch.full((1, 1, 256, 256), .6), tta=False) | |
| assert p.shape == u.shape == (1, 13) and z.shape == (1, 512) | |
| assert torch.isfinite(p).all() and torch.isfinite(u).all() and (u > 0).all() | |
| print('Load and inference check passed') | |