zouinkhim commited on
Commit
c0aeb30
·
verified ·
1 Parent(s): 9123be4

Add PyTorch original: model_state_dict.pt + model_def.py + torch_source.json

Browse files
Files changed (4) hide show
  1. README.md +18 -3
  2. model_def.py +118 -0
  3. model_state_dict.pt +3 -0
  4. torch_source.json +12 -0
README.md CHANGED
@@ -38,9 +38,9 @@ Endoplasmic reticulum (er) from FIB-SEM at 8 nm, 3D residual U-Net (InstanceNorm
38
 
39
  | File | Format | Usage |
40
  |---|---|---|
41
- | `model.pt2` | torch.export | `torch.export.load("model.pt2")` (trainable via `CellmapModel.train()`) |
42
  | `model.ts` | TorchScript | `torch.jit.load("model.ts")` |
43
- | `model.onnx` (+ `model.onnx.data`) | ONNX | `onnxruntime.InferenceSession("model.onnx")` |
44
  | `metadata.json` | JSON | Model metadata |
45
 
46
  ## Usage
@@ -67,7 +67,7 @@ Or download from this repo and load directly:
67
  from huggingface_hub import snapshot_download
68
  from cellmap_models.model_export.cellmap_model import CellmapModel
69
 
70
- path = snapshot_download(repo_id="cellmap/er-resunet-setup-67")
71
  model = CellmapModel(path)
72
  ```
73
 
@@ -79,3 +79,18 @@ Marwan Zouinkhi
79
 
80
  - [cellmap-models](https://github.com/janelia-cellmap/cellmap-models)
81
  - [CellMap Project](https://www.janelia.org/project-team/cellmap)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  | File | Format | Usage |
40
  |---|---|---|
41
+ | `model.pt` | PyTorch pickle | `torch.load("model.pt")` |
42
  | `model.ts` | TorchScript | `torch.jit.load("model.ts")` |
43
+ | `model.onnx` | ONNX | `onnxruntime.InferenceSession("model.onnx")` |
44
  | `metadata.json` | JSON | Model metadata |
45
 
46
  ## Usage
 
67
  from huggingface_hub import snapshot_download
68
  from cellmap_models.model_export.cellmap_model import CellmapModel
69
 
70
+ path = snapshot_download(repo_id="er_resunet_setup_67")
71
  model = CellmapModel(path)
72
  ```
73
 
 
79
 
80
  - [cellmap-models](https://github.com/janelia-cellmap/cellmap-models)
81
  - [CellMap Project](https://www.janelia.org/project-team/cellmap)
82
+
83
+ ## PyTorch original (for finetuning / re-export)
84
+
85
+ `model_state_dict.pt` (plain tensors, `weights_only=True`) + the module code (`model_def.py`); `torch_source.json`
86
+ names the class and constructor arguments.
87
+
88
+ ```python
89
+ import json, importlib.util, torch
90
+ src = json.load(open("torch_source.json"))
91
+ spec = importlib.util.spec_from_file_location("model_def", src["module"]); mod = importlib.util.module_from_spec(spec)
92
+ spec.loader.exec_module(mod)
93
+ model = getattr(mod, src["class"])(**src["kwargs"])
94
+ model.load_state_dict(torch.load("model_state_dict.pt", weights_only=True))
95
+ model.eval()
96
+ ```
model_def.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Custom 3D residual U-Net (~10.7M params), valid-padding throughout.
2
+
3
+ Valid (unpadded) convolutions are used instead of same-padding because this setup is intended to run
4
+ under blockwise inference (daisy/gunpowder tiling adjacent crops across a large volume). Same-padding
5
+ convs synthesize zero-context at every block's border, which produces seam artifacts at block
6
+ boundaries; valid convs give every output voxel genuine surrounding context, matching the convention
7
+ already used by fly_organelles.model.StandardUnet and fish_trainer.model.Unet3d.
8
+
9
+ Verified (base_channels=40, factor=2, blocks_per_level=1): 10,702,493 params,
10
+ input (148,148,148) -> output (60,60,60).
11
+ """
12
+
13
+ import torch
14
+ import torch.nn as nn
15
+ import torch.nn.functional as F
16
+
17
+
18
+ def center_crop_to(x, target):
19
+ diffs = [x.shape[i] - target.shape[i] for i in (2, 3, 4)]
20
+ starts = [d // 2 for d in diffs]
21
+ return x[
22
+ :,
23
+ :,
24
+ starts[0] : starts[0] + target.shape[2],
25
+ starts[1] : starts[1] + target.shape[3],
26
+ starts[2] : starts[2] + target.shape[4],
27
+ ]
28
+
29
+
30
+ class ResBlock(nn.Module):
31
+ """Two valid 3x3x3 convs + InstanceNorm + ReLU, with a crop-and-add identity skip
32
+ (1x1x1 conv on the skip path when channel count changes; identity is center-cropped
33
+ to match the main path's spatially-shrunk output before the residual add)."""
34
+
35
+ def __init__(self, c_in, c_out):
36
+ super().__init__()
37
+ self.conv1 = nn.Conv3d(c_in, c_out, 3, padding=0)
38
+ self.norm1 = nn.InstanceNorm3d(c_out, affine=True)
39
+ self.conv2 = nn.Conv3d(c_out, c_out, 3, padding=0)
40
+ self.norm2 = nn.InstanceNorm3d(c_out, affine=True)
41
+ self.act = nn.ReLU(inplace=True)
42
+ self.skip = nn.Conv3d(c_in, c_out, 1) if c_in != c_out else nn.Identity()
43
+
44
+ def forward(self, x):
45
+ idn = self.skip(x)
46
+ h = self.act(self.norm1(self.conv1(x)))
47
+ h = self.norm2(self.conv2(h))
48
+ idn = center_crop_to(idn, h)
49
+ return self.act(h + idn)
50
+
51
+
52
+ class Down(nn.Module):
53
+ """Exact 2x downsample (kernel==stride, divides evenly, no cropping needed)."""
54
+
55
+ def __init__(self, c_in, c_out):
56
+ super().__init__()
57
+ self.conv = nn.Conv3d(c_in, c_out, 2, stride=2)
58
+
59
+ def forward(self, x):
60
+ return self.conv(x)
61
+
62
+
63
+ class Up(nn.Module):
64
+ """Non-learnable nearest-neighbor upsample (exact 2x) + 1x1x1 channel-reduce conv.
65
+ Deliberately non-learnable to match this codebase's own constant_upsample=True choice in
66
+ StandardUnet (avoids checkerboard/seam artifacts from learned transposed convs)."""
67
+
68
+ def __init__(self, c_in, c_out):
69
+ super().__init__()
70
+ self.reduce = nn.Conv3d(c_in, c_out, 1)
71
+
72
+ def forward(self, x):
73
+ x = F.interpolate(x, scale_factor=2, mode="nearest")
74
+ return self.reduce(x)
75
+
76
+
77
+ class ResUNet3DValid(nn.Module):
78
+ def __init__(self, out_channels, base_channels=40, factor=2, blocks_per_level=1, in_channels=1):
79
+ super().__init__()
80
+ C = [base_channels * (factor**i) for i in range(4)] # 3 down/up levels + bottleneck
81
+ self.C = C
82
+
83
+ self.stem = ResBlock(in_channels, C[0])
84
+
85
+ self.downs = nn.ModuleList()
86
+ self.enc_blocks = nn.ModuleList()
87
+ for i in range(3):
88
+ self.downs.append(Down(C[i], C[i + 1]))
89
+ self.enc_blocks.append(nn.ModuleList([ResBlock(C[i + 1], C[i + 1]) for _ in range(blocks_per_level)]))
90
+
91
+ self.ups = nn.ModuleList()
92
+ self.dec_blocks = nn.ModuleList()
93
+ for i in reversed(range(3)):
94
+ self.ups.append(Up(C[i + 1], C[i]))
95
+ blocks = [ResBlock(C[i] * 2, C[i])] + [ResBlock(C[i], C[i]) for _ in range(blocks_per_level - 1)]
96
+ self.dec_blocks.append(nn.ModuleList(blocks))
97
+
98
+ self.final_conv = nn.Conv3d(C[0], out_channels, 1)
99
+
100
+ def forward(self, input):
101
+ skips = []
102
+ x = self.stem(input)
103
+ skips.append(x)
104
+ for down, enc in zip(self.downs, self.enc_blocks):
105
+ x = down(x)
106
+ for blk in enc:
107
+ x = blk(x)
108
+ skips.append(x)
109
+ skips.pop() # bottleneck itself is not a skip target
110
+
111
+ for up, dec, skip in zip(self.ups, self.dec_blocks, reversed(skips)):
112
+ x = up(x)
113
+ skip_c = center_crop_to(skip, x)
114
+ x = torch.cat([x, skip_c], dim=1)
115
+ for blk in dec:
116
+ x = blk(x)
117
+
118
+ return self.final_conv(x)
model_state_dict.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0905b254f38ef5d3ae46335b92bf96025695162d3c7b909a2a5842f2826d9c57
3
+ size 42832173
torch_source.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "module": "model_def.py",
3
+ "class": "ResUNet3DValid",
4
+ "kwargs": {
5
+ "out_channels": 1,
6
+ "base_channels": 40,
7
+ "factor": 2,
8
+ "blocks_per_level": 1
9
+ },
10
+ "state_dict": "model_state_dict.pt",
11
+ "torch_version": "2.9.1+cu128"
12
+ }