Image Segmentation
Transformers
PyTorch
repcontur
feature-extraction
medical
radiology
ct
3d
tumor-segmentation
referring-segmentation
report-generation
visual-question-answering
custom_code
Instructions to use Oncosoft-Research/RepConTUR with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Oncosoft-Research/RepConTUR with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-segmentation", model="Oncosoft-Research/RepConTUR", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("Oncosoft-Research/RepConTUR", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 6,495 Bytes
c6be546 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | """
CT Volume Encoder Wrapper for RepConTUR.
Wraps CTViT or NNUNet3DEncoder to encode full 3D CT volumes into
a flattened token sequence of shape (B, N, dim).
The encoder is loaded from a CT-CLIP checkpoint and fully frozen.
"""
from pathlib import Path
from typing import Optional, List
import torch
import torch.nn as nn
from ._compat import print_log
class CTVolumeEncoder(nn.Module):
"""
Wraps CTViT or NNUNet3DEncoder for 3D CT volume encoding.
Input: (B, 1, D, H, W) — full 3D CT volume
Output: (B, T*H'*W', dim) — flattened spatial/temporal tokens
The encoder is always frozen. Only downstream modules (attentional
pooler, MLP projection) are trainable.
"""
def __init__(
self,
encoder_type: str = 'ctvit',
checkpoint_path: Optional[str] = None,
ct_clip_source_path: Optional[str] = None,
# CTViT params
dim: int = 512,
codebook_size: int = 8192,
image_size: int = 360,
patch_size: int = 20,
temporal_patch_size: int = 10,
spatial_depth: int = 4,
temporal_depth: int = 4,
dim_head: int = 32,
heads: int = 8,
# NNUNet params
nnunet_depth: int = 5,
nnunet_channels: Optional[List[int]] = None,
nnunet_stride_list: Optional[List[List[int]]] = None,
):
super().__init__()
self.encoder_type = encoder_type
self.dim = dim
if nnunet_channels is None:
nnunet_channels = [32, 64, 128, 256, 320, 512]
if nnunet_stride_list is None:
nnunet_stride_list = [[1, 2, 2], [2, 2, 2], [2, 2, 2], [2, 2, 2], [2, 2, 2]]
# transformer_maskgit is vendored alongside this module (ct_clip_source_path ignored).
if encoder_type == 'ctvit':
from .ctvit import CTViT
self.encoder = CTViT(
dim=dim,
codebook_size=codebook_size,
image_size=image_size,
patch_size=patch_size,
temporal_patch_size=temporal_patch_size,
spatial_depth=spatial_depth,
temporal_depth=temporal_depth,
dim_head=dim_head,
heads=heads,
)
elif encoder_type == 'nnunet':
from .nnunet import NNUNet3DEncoder
self.encoder = NNUNet3DEncoder(
depth=nnunet_depth,
channels=nnunet_channels,
stride_list=nnunet_stride_list,
)
self.dim = nnunet_channels[nnunet_depth] # output channel dim
else:
raise ValueError(f"Unknown encoder_type: {encoder_type}. Use 'ctvit' or 'nnunet'.")
# Load checkpoint
if checkpoint_path is not None:
self._load_checkpoint(checkpoint_path)
# Freeze the entire encoder
self.encoder.requires_grad_(False)
self.encoder.eval()
total_params = sum(p.numel() for p in self.encoder.parameters())
print_log(
f"CTVolumeEncoder ({encoder_type}): loaded with {total_params:,} params (all frozen)",
logger='current'
)
def _load_checkpoint(self, checkpoint_path: str):
"""Load encoder weights from a CT-CLIP checkpoint."""
path = Path(checkpoint_path)
if not path.exists():
raise FileNotFoundError(f"Checkpoint not found: {checkpoint_path}")
if path.suffix == '.safetensors':
from safetensors import safe_open
f = safe_open(str(path), framework='pt', device='cpu')
all_keys = list(f.keys())
encoder_state = {}
for prefix in ['ctclip.visual_transformer.', 'visual_transformer.']:
for k in all_keys:
if k.startswith(prefix):
encoder_state[k[len(prefix):]] = f.get_tensor(k)
if encoder_state:
break
if not encoder_state:
for k in all_keys:
encoder_state[k] = f.get_tensor(k)
else:
# Standard PyTorch checkpoint
ckpt = torch.load(str(path), map_location='cpu')
if isinstance(ckpt, dict) and 'state_dict' in ckpt:
ckpt = ckpt['state_dict']
encoder_state = {}
for prefix in ['ctclip.visual_transformer.', 'visual_transformer.']:
for k, v in ckpt.items():
if k.startswith(prefix):
encoder_state[k[len(prefix):]] = v
if encoder_state:
break
if not encoder_state:
encoder_state = ckpt
# Load with strict=False to skip decoder keys (to_pixels*, dec_*)
missing, unexpected = self.encoder.load_state_dict(encoder_state, strict=False)
print_log(
f"CTVolumeEncoder: loaded checkpoint from {checkpoint_path}\n"
f" Matched keys: {len(encoder_state) - len(unexpected)}\n"
f" Missing keys (expected for encoder-only): {len(missing)}\n"
f" Unexpected keys (decoder, skipped): {len(unexpected)}",
logger='current'
)
def train(self, mode=True):
"""Override train to keep encoder always in eval mode."""
super().train(mode)
self.encoder.eval()
return self
def forward(self, volume: torch.Tensor) -> torch.Tensor:
"""
Args:
volume: (B, 1, D, H, W) — preprocessed 3D CT volume
Returns:
tokens: (B, N, dim) — flattened tokens where N = T*H'*W'
For CTViT with (180, 360, 360): N = 18*18*18 = 5832, dim = 512
For NNUNet with (180, 360, 360): N varies, dim = channels[-1]
"""
# Query the encoder's actual parameter dtype at runtime.
# .to(bf16) in __init__ may not persist (DeepSpeed can reset
# frozen-parameter dtypes), so we always match dynamically.
encoder_dtype = next(self.encoder.parameters()).dtype
volume = volume.to(dtype=encoder_dtype)
with torch.no_grad(), torch.cuda.amp.autocast(enabled=False):
tokens = self.encoder(volume, return_encoded_tokens=True)
# CTViT: (B, T, H', W', dim) e.g. (B, 18, 18, 18, 512)
# NNUNet: (B, D', H', W', dim) e.g. (B, 11, 11, 11, 512)
B = tokens.shape[0]
tokens = tokens.reshape(B, -1, self.dim) # (B, N, dim)
return tokens
|