Download model_def.py from cellmap/er-resunet-setup-67: direct link, hf CLI and curl.
- Browser
- Download file 4.39 kB
-
https://huggingface.co/cellmap/er-resunet-setup-67/resolve/main/model_def.py
- Command line
-
hf download hf://cellmap/er-resunet-setup-67/model_def.py
-
curl -L -o model_def.py https://huggingface.co/cellmap/er-resunet-setup-67/resolve/main/model_def.py
4.39 kB
| """Custom 3D residual U-Net (~10.7M params), valid-padding throughout. | |
| Valid (unpadded) convolutions are used instead of same-padding because this setup is intended to run | |
| under blockwise inference (daisy/gunpowder tiling adjacent crops across a large volume). Same-padding | |
| convs synthesize zero-context at every block's border, which produces seam artifacts at block | |
| boundaries; valid convs give every output voxel genuine surrounding context, matching the convention | |
| already used by fly_organelles.model.StandardUnet and fish_trainer.model.Unet3d. | |
| Verified (base_channels=40, factor=2, blocks_per_level=1): 10,702,493 params, | |
| input (148,148,148) -> output (60,60,60). | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| def center_crop_to(x, target): | |
| diffs = [x.shape[i] - target.shape[i] for i in (2, 3, 4)] | |
| starts = [d // 2 for d in diffs] | |
| return x[ | |
| :, | |
| :, | |
| starts[0] : starts[0] + target.shape[2], | |
| starts[1] : starts[1] + target.shape[3], | |
| starts[2] : starts[2] + target.shape[4], | |
| ] | |
| class ResBlock(nn.Module): | |
| """Two valid 3x3x3 convs + InstanceNorm + ReLU, with a crop-and-add identity skip | |
| (1x1x1 conv on the skip path when channel count changes; identity is center-cropped | |
| to match the main path's spatially-shrunk output before the residual add).""" | |
| def __init__(self, c_in, c_out): | |
| super().__init__() | |
| self.conv1 = nn.Conv3d(c_in, c_out, 3, padding=0) | |
| self.norm1 = nn.InstanceNorm3d(c_out, affine=True) | |
| self.conv2 = nn.Conv3d(c_out, c_out, 3, padding=0) | |
| self.norm2 = nn.InstanceNorm3d(c_out, affine=True) | |
| self.act = nn.ReLU(inplace=True) | |
| self.skip = nn.Conv3d(c_in, c_out, 1) if c_in != c_out else nn.Identity() | |
| def forward(self, x): | |
| idn = self.skip(x) | |
| h = self.act(self.norm1(self.conv1(x))) | |
| h = self.norm2(self.conv2(h)) | |
| idn = center_crop_to(idn, h) | |
| return self.act(h + idn) | |
| class Down(nn.Module): | |
| """Exact 2x downsample (kernel==stride, divides evenly, no cropping needed).""" | |
| def __init__(self, c_in, c_out): | |
| super().__init__() | |
| self.conv = nn.Conv3d(c_in, c_out, 2, stride=2) | |
| def forward(self, x): | |
| return self.conv(x) | |
| class Up(nn.Module): | |
| """Non-learnable nearest-neighbor upsample (exact 2x) + 1x1x1 channel-reduce conv. | |
| Deliberately non-learnable to match this codebase's own constant_upsample=True choice in | |
| StandardUnet (avoids checkerboard/seam artifacts from learned transposed convs).""" | |
| def __init__(self, c_in, c_out): | |
| super().__init__() | |
| self.reduce = nn.Conv3d(c_in, c_out, 1) | |
| def forward(self, x): | |
| x = F.interpolate(x, scale_factor=2, mode="nearest") | |
| return self.reduce(x) | |
| class ResUNet3DValid(nn.Module): | |
| def __init__(self, out_channels, base_channels=40, factor=2, blocks_per_level=1, in_channels=1): | |
| super().__init__() | |
| C = [base_channels * (factor**i) for i in range(4)] # 3 down/up levels + bottleneck | |
| self.C = C | |
| self.stem = ResBlock(in_channels, C[0]) | |
| self.downs = nn.ModuleList() | |
| self.enc_blocks = nn.ModuleList() | |
| for i in range(3): | |
| self.downs.append(Down(C[i], C[i + 1])) | |
| self.enc_blocks.append(nn.ModuleList([ResBlock(C[i + 1], C[i + 1]) for _ in range(blocks_per_level)])) | |
| self.ups = nn.ModuleList() | |
| self.dec_blocks = nn.ModuleList() | |
| for i in reversed(range(3)): | |
| self.ups.append(Up(C[i + 1], C[i])) | |
| blocks = [ResBlock(C[i] * 2, C[i])] + [ResBlock(C[i], C[i]) for _ in range(blocks_per_level - 1)] | |
| self.dec_blocks.append(nn.ModuleList(blocks)) | |
| self.final_conv = nn.Conv3d(C[0], out_channels, 1) | |
| def forward(self, input): | |
| skips = [] | |
| x = self.stem(input) | |
| skips.append(x) | |
| for down, enc in zip(self.downs, self.enc_blocks): | |
| x = down(x) | |
| for blk in enc: | |
| x = blk(x) | |
| skips.append(x) | |
| skips.pop() # bottleneck itself is not a skip target | |
| for up, dec, skip in zip(self.ups, self.dec_blocks, reversed(skips)): | |
| x = up(x) | |
| skip_c = center_crop_to(skip, x) | |
| x = torch.cat([x, skip_c], dim=1) | |
| for blk in dec: | |
| x = blk(x) | |
| return self.final_conv(x) | |