milkzheng commited on
Commit
e72f0d1
·
verified ·
1 Parent(s): b2be135

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. README.md +31 -31
  2. config.json +0 -10
  3. hf_model.py +2 -85
README.md CHANGED
@@ -7,46 +7,46 @@ tags:
7
  pipeline_tag: image-segmentation
8
  ---
9
 
10
- # MetSeg Macro-structure Segmentation (DPT)
11
 
12
- Pathology macro-structure segmentation. DPT backbone with a custom HF wrapper
13
- bundled via `trust_remote_code`.
 
14
 
15
  ## Usage
16
 
 
 
 
 
 
 
17
  ```python
18
- from PIL import Image
19
  import torch
 
 
20
  from transformers import AutoModel
21
 
22
  model = AutoModel.from_pretrained("RendeiroLab/MetPredict-lung-structure-segmentation", trust_remote_code=True).eval()
23
-
24
- # Preprocess: PIL / ndarray / tensor — single image or list/batch.
25
- # Applies ImageNet normalization (mean/std stored in config.json).
26
- img = Image.open("tile.png")
27
- pixel_values = model.preprocess(img) # → (1, 3, H, W) on model device
28
-
29
- with torch.no_grad():
 
 
 
 
 
 
 
 
 
30
  out = model(pixel_values)
31
- logits = out.logits # (1, n_classes, H, W)
32
- pred = logits.argmax(dim=1) # (1, H, W)
33
  ```
34
 
35
- `preprocess` accepts:
36
- - `PIL.Image` (RGB) — single or list
37
- - `numpy.ndarray` shape `(H, W, 3)` or `(B, H, W, 3)`, uint8 or float
38
- - `torch.Tensor` shape `(3, H, W)` or `(B, 3, H, W)`, uint8 or float
39
-
40
- No resize is applied. H and W must be divisible by the backbone patch size
41
- (typically 14 or 16). Tile or pad upstream as needed.
42
-
43
- If you already have a normalized tensor, you can call the model directly with
44
- `pixel_values=...`.
45
-
46
- ## Alternative: portable `torch.export`
47
-
48
- ```python
49
- import torch
50
- m = torch.export.load("model.pt2").module()
51
- y = m(torch.randn(1, 3, 224, 224))
52
- ```
 
7
  pipeline_tag: image-segmentation
8
  ---
9
 
10
+ # Lung structures Segmentation (DPT)
11
 
12
+ Pathology segmentation for lung structures (blood vessels and airways).
13
+ - Encoder (freezed): H-optimus-0 ViT backbone (pretrained on histopathology data).
14
+ - Decoder (trained): custom DPT head with multi-scale feature fusion.
15
 
16
  ## Usage
17
 
18
+ The model expects a normalized `(B, 3, H, W)` float tensor as `pixel_values`.
19
+ Use ImageNet mean/std — same stats applied at training time (matches the
20
+ H-optimus-0 backbone's expected input distribution).
21
+
22
+ Input image: 224x224 @ 1.5 MPP
23
+
24
  ```python
25
+ import numpy as np
26
  import torch
27
+ from PIL import Image
28
+ from torchvision.transforms import ToTensor, Normalize, Resize, Compose
29
  from transformers import AutoModel
30
 
31
  model = AutoModel.from_pretrained("RendeiroLab/MetPredict-lung-structure-segmentation", trust_remote_code=True).eval()
32
+ device = next(model.parameters()).device
33
+
34
+ transform = Compose([
35
+ ToTensor(),
36
+ Resize((224, 224)),
37
+ Normalize(
38
+ mean=[0.485, 0.456, 0.406],
39
+ std=[0.229, 0.224, 0.225]
40
+ ),
41
+ ])
42
+
43
+ img = Image.open("tile.png").convert("RGB")
44
+ x = transform(img)
45
+ pixel_values = x.unsqueeze(0).to(device)
46
+
47
+ with torch.inference_mode():
48
  out = model(pixel_values)
49
+ logits = out.logits # (1, n_classes, H, W)
50
+ pred = logits.argmax(dim=1) # (1, H, W)
51
  ```
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
config.json CHANGED
@@ -23,16 +23,6 @@
23
  "decoder_readout": "cat",
24
  "dtype": "float32",
25
  "encoder_depth": 4,
26
- "image_mean": [
27
- 0.485,
28
- 0.456,
29
- 0.406
30
- ],
31
- "image_std": [
32
- 0.229,
33
- 0.224,
34
- 0.225
35
- ],
36
  "in_channels": 3,
37
  "model_type": "metpredict_dpt",
38
  "n_classes": 3,
 
23
  "decoder_readout": "cat",
24
  "dtype": "float32",
25
  "encoder_depth": 4,
 
 
 
 
 
 
 
 
 
 
26
  "in_channels": 3,
27
  "model_type": "metpredict_dpt",
28
  "n_classes": 3,
hf_model.py CHANGED
@@ -7,9 +7,8 @@ classes are reconstructable in a clean env.
7
  """
8
  from __future__ import annotations
9
 
10
- from typing import Any, Literal, Optional, Sequence, Union, cast
11
 
12
- import numpy as np
13
  import torch
14
  import torch.nn.functional as F
15
  from transformers import (
@@ -22,11 +21,6 @@ from transformers.modeling_outputs import SemanticSegmenterOutput
22
 
23
  from .dpt import DPT
24
 
25
- # ImageNet stats — matches the `A.Normalize()` default used at training time
26
- # (correct for Virchow2 / H-optimus-0 backbones).
27
- _IMAGENET_MEAN = (0.485, 0.456, 0.406)
28
- _IMAGENET_STD = (0.229, 0.224, 0.225)
29
-
30
 
31
  class DPTConfig(PretrainedConfig):
32
  model_type = "metpredict_dpt"
@@ -42,8 +36,6 @@ class DPTConfig(PretrainedConfig):
42
  decoder_readout: str = "cat",
43
  activation: Optional[str] = None,
44
  in_channels: int = 3,
45
- image_mean: Sequence[float] = _IMAGENET_MEAN,
46
- image_std: Sequence[float] = _IMAGENET_STD,
47
  **kwargs,
48
  ):
49
  super().__init__(**kwargs)
@@ -56,8 +48,6 @@ class DPTConfig(PretrainedConfig):
56
  self.decoder_readout = decoder_readout
57
  self.activation = activation
58
  self.in_channels = in_channels
59
- self.image_mean = list(image_mean)
60
- self.image_std = list(image_std)
61
  # `auto_map` makes the repo loadable as AutoModel without local imports.
62
  self.auto_map = {
63
  "AutoConfig": "hf_model.DPTConfig",
@@ -69,6 +59,7 @@ class DPTForSegmentation(PreTrainedModel):
69
  config_class = DPTConfig
70
  base_model_prefix = "dpt"
71
  main_input_name = "pixel_values"
 
72
 
73
  def __init__(self, config: DPTConfig):
74
  super().__init__(config)
@@ -86,82 +77,8 @@ class DPTForSegmentation(PreTrainedModel):
86
  activation=config.activation,
87
  )
88
  self.dpt = DPT(**dpt_kwargs)
89
- # Normalize stats kept as buffers so they move with `.to(device)` and
90
- # show up in `state_dict` for inspection but are excluded from grads.
91
- self.register_buffer(
92
- "image_mean",
93
- torch.tensor(config.image_mean, dtype=torch.float32).view(1, -1, 1, 1),
94
- persistent=False,
95
- )
96
- self.register_buffer(
97
- "image_std",
98
- torch.tensor(config.image_std, dtype=torch.float32).view(1, -1, 1, 1),
99
- persistent=False,
100
- )
101
  # Skip post_init weight init — DPT.initialize() already ran inside DPT.__init__.
102
 
103
- @torch.no_grad()
104
- def preprocess(
105
- self,
106
- images: Union[
107
- "PIL.Image.Image", # noqa: F821
108
- np.ndarray,
109
- torch.Tensor,
110
- list,
111
- ],
112
- ) -> torch.Tensor:
113
- """Convert raw inputs into a model-ready ``pixel_values`` tensor.
114
-
115
- Accepts a single image or a batch, in any of:
116
- - PIL.Image (RGB)
117
- - numpy.ndarray, shape (H, W, 3) or (B, H, W, 3), dtype uint8 or float
118
- - torch.Tensor, shape (3, H, W) or (B, 3, H, W), dtype uint8 or float
119
-
120
- Returns a float tensor of shape ``(B, 3, H, W)`` normalized with the
121
- ImageNet stats used during training, on the model's current device.
122
-
123
- Notes:
124
- - uint8 inputs are scaled to [0, 1] before normalization.
125
- - No resize is applied: the DPT is fully convolutional but the H and W
126
- must be divisible by the backbone's patch size (typically 14 or 16).
127
- Tile/pad upstream as needed.
128
- """
129
- if isinstance(images, list):
130
- tensors = [self._to_chw_float(x) for x in images]
131
- batch = torch.stack(tensors, dim=0)
132
- else:
133
- t = self._to_chw_float(images)
134
- batch = t if t.ndim == 4 else t.unsqueeze(0)
135
- batch = batch.to(self.image_mean.device, dtype=torch.float32)
136
- return (batch - self.image_mean) / self.image_std
137
-
138
- @staticmethod
139
- def _to_chw_float(x: Any) -> torch.Tensor:
140
- """Convert a single image-like input to a CHW float tensor in [0, 1]."""
141
- # Lazy import: PIL is optional at inference time.
142
- try:
143
- from PIL import Image as _PILImage
144
- except ImportError: # pragma: no cover
145
- _PILImage = None # type: ignore[assignment]
146
- if _PILImage is not None and isinstance(x, _PILImage.Image):
147
- arr = np.asarray(x.convert("RGB")) # HWC uint8
148
- t = torch.from_numpy(arr).permute(2, 0, 1).contiguous()
149
- elif isinstance(x, np.ndarray):
150
- t = torch.from_numpy(x)
151
- if t.ndim == 3 and t.shape[-1] in (1, 3):
152
- t = t.permute(2, 0, 1).contiguous()
153
- elif t.ndim == 4 and t.shape[-1] in (1, 3):
154
- t = t.permute(0, 3, 1, 2).contiguous()
155
- elif isinstance(x, torch.Tensor):
156
- t = x
157
- else:
158
- raise TypeError(f"Unsupported image type: {type(x).__name__}")
159
- if t.dtype == torch.uint8:
160
- t = t.float() / 255.0
161
- else:
162
- t = t.float()
163
- return t
164
-
165
  def forward(
166
  self,
167
  pixel_values: torch.Tensor,
 
7
  """
8
  from __future__ import annotations
9
 
10
+ from typing import Any, Literal, Optional, cast
11
 
 
12
  import torch
13
  import torch.nn.functional as F
14
  from transformers import (
 
21
 
22
  from .dpt import DPT
23
 
 
 
 
 
 
24
 
25
  class DPTConfig(PretrainedConfig):
26
  model_type = "metpredict_dpt"
 
36
  decoder_readout: str = "cat",
37
  activation: Optional[str] = None,
38
  in_channels: int = 3,
 
 
39
  **kwargs,
40
  ):
41
  super().__init__(**kwargs)
 
48
  self.decoder_readout = decoder_readout
49
  self.activation = activation
50
  self.in_channels = in_channels
 
 
51
  # `auto_map` makes the repo loadable as AutoModel without local imports.
52
  self.auto_map = {
53
  "AutoConfig": "hf_model.DPTConfig",
 
59
  config_class = DPTConfig
60
  base_model_prefix = "dpt"
61
  main_input_name = "pixel_values"
62
+ all_tied_weights_keys: dict = {} # To be compatible with transformers 4.x and 5.x
63
 
64
  def __init__(self, config: DPTConfig):
65
  super().__init__(config)
 
77
  activation=config.activation,
78
  )
79
  self.dpt = DPT(**dpt_kwargs)
 
 
 
 
 
 
 
 
 
 
 
 
80
  # Skip post_init weight init — DPT.initialize() already ran inside DPT.__init__.
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  def forward(
83
  self,
84
  pixel_values: torch.Tensor,