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
Download ctvit.py from Oncosoft-Research/RepConTUR: direct link, hf CLI and curl.
- Browser
- Download file 16.8 kB
-
https://huggingface.co/Oncosoft-Research/RepConTUR/resolve/main/ctvit.py
- Command line
-
hf download hf://Oncosoft-Research/RepConTUR/ctvit.py
-
curl -L -o ctvit.py https://huggingface.co/Oncosoft-Research/RepConTUR/resolve/main/ctvit.py
16.8 kB
| from pathlib import Path | |
| import copy | |
| import math | |
| from functools import wraps | |
| import torch | |
| import torch.nn.functional as F | |
| from torch import nn, einsum | |
| from torch.autograd import grad as torch_grad | |
| from torchvision import transforms as T, utils | |
| import torchvision | |
| from einops import rearrange, repeat, pack, unpack | |
| from einops.layers.torch import Rearrange | |
| from vector_quantize_pytorch import VectorQuantize | |
| from .attention import Attention, Transformer, ContinuousPositionBias | |
| # helpers | |
| def exists(val): | |
| return val is not None | |
| def default(val, d): | |
| return val if exists(val) else d | |
| def divisible_by(numer, denom): | |
| return (numer % denom) == 0 | |
| def leaky_relu(p = 0.1): | |
| return nn.LeakyReLU(p) | |
| def remove_vgg(fn): | |
| def inner(self, *args, **kwargs): | |
| has_vgg = hasattr(self, 'vgg') | |
| if has_vgg: | |
| vgg = self.vgg | |
| delattr(self, 'vgg') | |
| out = fn(self, *args, **kwargs) | |
| if has_vgg: | |
| self.vgg = vgg | |
| return out | |
| return inner | |
| def pair(val): | |
| ret = (val, val) if not isinstance(val, tuple) else val | |
| assert len(ret) == 2 | |
| return ret | |
| def cast_tuple(val, l = 1): | |
| return val if isinstance(val, tuple) else (val,) * l | |
| def gradient_penalty(images, output, weight = 10): | |
| batch_size = images.shape[0] | |
| device=torch.device('cuda') | |
| gradients = torch_grad( | |
| outputs = output, | |
| inputs = images, | |
| grad_outputs = torch.ones(output.size(), device = device), | |
| create_graph = True, | |
| retain_graph = True, | |
| only_inputs = True | |
| )[0] | |
| gradients = rearrange(gradients, 'b ... -> b (...)') | |
| return weight * ((gradients.norm(2, dim = 1) - 1) ** 2).mean() | |
| def l2norm(t): | |
| return F.normalize(t, dim = -1) | |
| def leaky_relu(p = 0.1): | |
| return nn.LeakyReLU(p) | |
| def safe_div(numer, denom, eps = 1e-8): | |
| return numer / (denom + eps) | |
| # gan losses | |
| def hinge_discr_loss(fake, real): | |
| return (F.relu(1 + fake) + F.relu(1 - real)).mean() | |
| def hinge_gen_loss(fake): | |
| return -fake.mean() | |
| def bce_discr_loss(fake, real): | |
| return (-log(1 - torch.sigmoid(fake)) - log(torch.sigmoid(real))).mean() | |
| def bce_gen_loss(fake): | |
| return -log(torch.sigmoid(fake)).mean() | |
| def grad_layer_wrt_loss(loss, layer): | |
| return torch_grad( | |
| outputs = loss, | |
| inputs = layer, | |
| grad_outputs = torch.ones_like(loss), | |
| retain_graph = True | |
| )[0].detach() | |
| # ctvit - 3d ViT with factorized spatial and temporal attention made into an vqgan-vae autoencoder | |
| def pick_video_frame(video, frame_indices): | |
| batch, device = video.shape[0], video.device | |
| video = rearrange(video, 'b c f ... -> b f c ...') | |
| device=torch.device('cuda') | |
| batch_indices = torch.arange(batch, device = device) | |
| batch_indices = rearrange(batch_indices, 'b -> b 1') | |
| images = video[batch_indices, frame_indices] | |
| images = rearrange(images, 'b 1 c ... -> b c ...') | |
| return images | |
| class CTViT(nn.Module): | |
| def __init__( | |
| self, | |
| *, | |
| dim, | |
| codebook_size, | |
| image_size, | |
| patch_size, | |
| temporal_patch_size, | |
| spatial_depth, | |
| temporal_depth, | |
| discr_base_dim = 16, | |
| dim_head = 64, | |
| heads = 8, | |
| channels = 1, | |
| use_vgg_and_gan = True, | |
| vgg = None, | |
| discr_attn_res_layers = (16,), | |
| use_hinge_loss = True, | |
| attn_dropout = 0., | |
| ff_dropout = 0. | |
| ): | |
| """ | |
| einstein notations: | |
| b - batch | |
| c - channels | |
| t - time | |
| d - feature dimension | |
| p1, p2, pt - image patch sizes and then temporal patch size | |
| """ | |
| super().__init__() | |
| self.image_size = pair(image_size) | |
| self.patch_size = pair(patch_size) | |
| patch_height, patch_width = self.patch_size | |
| self.temporal_patch_size = temporal_patch_size | |
| self.spatial_rel_pos_bias = ContinuousPositionBias(dim = dim, heads = heads) | |
| image_height, image_width = self.image_size | |
| assert (image_height % patch_height) == 0 and (image_width % patch_width) == 0 | |
| self.to_patch_emb_first_frame = nn.Sequential( | |
| Rearrange('b c 1 (h p1) (w p2) -> b 1 h w (c p1 p2)', p1 = patch_height, p2 = patch_width), | |
| nn.LayerNorm(channels * patch_width * patch_height), | |
| nn.Linear(channels * patch_width * patch_height, dim), | |
| nn.LayerNorm(dim) | |
| ) | |
| self.to_patch_emb = nn.Sequential( | |
| Rearrange('b c (t pt) (h p1) (w p2) -> b t h w (c pt p1 p2)', p1 = patch_height, p2 = patch_width, pt = temporal_patch_size), | |
| nn.LayerNorm(channels * patch_width * patch_height * temporal_patch_size), | |
| nn.Linear(channels * patch_width * patch_height * temporal_patch_size, dim), | |
| nn.LayerNorm(dim) | |
| ) | |
| transformer_kwargs = dict( | |
| dim = dim, | |
| dim_head = dim_head, | |
| heads = heads, | |
| attn_dropout = attn_dropout, | |
| ff_dropout = ff_dropout, | |
| peg = True, | |
| peg_causal = True, | |
| ) | |
| self.enc_spatial_transformer = Transformer(depth = spatial_depth, **transformer_kwargs) | |
| self.enc_temporal_transformer = Transformer(depth = temporal_depth, **transformer_kwargs) | |
| self.vq = VectorQuantize(dim = dim, codebook_size = codebook_size, use_cosine_sim = True) | |
| self.vq.float() # VQ must stay in float32 for compatibility | |
| self.to_pixels_first_frame = nn.Sequential( | |
| nn.Linear(dim, channels * patch_width * patch_height), | |
| Rearrange('b 1 h w (c p1 p2) -> b c 1 (h p1) (w p2)', p1 = patch_height, p2 = patch_width) | |
| ) | |
| self.to_pixels = nn.Sequential( | |
| nn.Linear(dim, channels * patch_width * patch_height * temporal_patch_size), | |
| Rearrange('b t h w (c pt p1 p2) -> b c (t pt) (h p1) (w p2)', p1 = patch_height, p2 = patch_width, pt = temporal_patch_size), | |
| ) | |
| self.gen_loss = hinge_gen_loss if use_hinge_loss else bce_gen_loss | |
| def _apply(self, fn): | |
| # Override _apply to keep VQ module in float32 | |
| # This catches to(), half(), bfloat16(), cuda(), etc. | |
| super()._apply(fn) | |
| self.vq.float() | |
| return self | |
| def calculate_video_token_mask(self, videos, video_frame_mask): | |
| *_, h, w = videos.shape | |
| ph, pw = self.patch_size | |
| first_frame_mask, rest_frame_mask = video_frame_mask[:, :1], video_frame_mask[:, 1:] | |
| rest_vq_mask = rearrange(rest_frame_mask, 'b (f p) -> b f p', p = self.temporal_patch_size) | |
| video_mask = torch.cat((first_frame_mask, rest_vq_mask.any(dim = -1)), dim = -1) | |
| return repeat(video_mask, 'b f -> b (f hw)', hw = (h // ph) * (w // pw)) | |
| def get_video_patch_shape(self, num_frames, include_first_frame = True): | |
| patch_frames = 0 | |
| if include_first_frame: | |
| num_frames -= 1 | |
| patch_frames += 1 | |
| patch_frames += (num_frames // self.temporal_patch_size) | |
| return (patch_frames, *self.patch_height_width) | |
| def image_num_tokens(self): | |
| return int(self.image_size[0] / self.patch_size[0]) * int(self.image_size[1] / self.patch_size[1]) | |
| def frames_per_num_tokens(self, num_tokens): | |
| tokens_per_frame = self.image_num_tokens | |
| assert (num_tokens % tokens_per_frame) == 0, f'number of tokens must be divisible by number of tokens per frame {tokens_per_frame}' | |
| assert (num_tokens > 0) | |
| pseudo_frames = num_tokens // tokens_per_frames | |
| return (pseudo_frames - 1) * self.temporal_patch_size + 1 | |
| def num_tokens_per_frames(self, num_frames, include_first_frame = True): | |
| image_num_tokens = self.image_num_tokens | |
| total_tokens = 0 | |
| if include_first_frame: | |
| num_frames -= 1 | |
| total_tokens += image_num_tokens | |
| assert (num_frames % self.temporal_patch_size) == 0 | |
| return total_tokens + int(num_frames / self.temporal_patch_size) * image_num_tokens | |
| def copy_for_eval(self): | |
| device = next(self.parameters()).device | |
| device=torch.device('cuda') | |
| vae_copy = copy.deepcopy(self.cpu()) | |
| if vae_copy.use_vgg_and_gan: | |
| del vae_copy.discr | |
| del vae_copy.vgg | |
| vae_copy.eval() | |
| return vae_copy.to(device) | |
| #@remove_vgg | |
| def state_dict(self, *args, **kwargs): | |
| return super().state_dict(*args, **kwargs) | |
| #@remove_vgg | |
| def load_state_dict(self, *args, **kwargs): | |
| return super().load_state_dict(*args, **kwargs) | |
| def load(self, path): | |
| path = Path(path) | |
| assert path.exists() | |
| pt = torch.load(str(path)) | |
| self.load_state_dict(pt) | |
| def decode_from_codebook_indices(self, indices): | |
| codes = self.vq.codebook[indices] | |
| return self.decode(codes) | |
| def patch_height_width(self): | |
| return self.image_size[0] // self.patch_size[0], self.image_size[1] // self.patch_size[1] | |
| def encode( | |
| self, | |
| tokens | |
| ): | |
| b = tokens.shape[0] | |
| h, w = self.patch_height_width | |
| video_shape = tuple(tokens.shape[:-1]) | |
| tokens = rearrange(tokens, 'b t h w d -> (b t) (h w) d') | |
| device=torch.device('cuda') | |
| attn_bias = self.spatial_rel_pos_bias(h, w, device = device) | |
| tokens = self.enc_spatial_transformer(tokens, attn_bias = attn_bias, video_shape = video_shape) | |
| tokens = rearrange(tokens, '(b t) (h w) d -> b t h w d', b = b, h = h , w = w) | |
| # encode - temporal | |
| tokens = rearrange(tokens, 'b t h w d -> (b h w) t d') | |
| tokens = self.enc_temporal_transformer(tokens, video_shape = video_shape) | |
| tokens = rearrange(tokens, '(b h w) t d -> b t h w d', b = b, h = h, w = w) | |
| return tokens | |
| def decode( | |
| self, | |
| tokens | |
| ): | |
| b = tokens.shape[0] | |
| h, w = self.patch_height_width | |
| if tokens.ndim == 3: | |
| tokens = rearrange(tokens, 'b (t h w) d -> b t h w d', h = h, w = w) | |
| video_shape = tuple(tokens.shape[:-1]) | |
| # decode - temporal | |
| tokens = rearrange(tokens, 'b t h w d -> (b h w) t d') | |
| tokens = self.dec_temporal_transformer(tokens, video_shape = video_shape) | |
| tokens = rearrange(tokens, '(b h w) t d -> b t h w d', b = b, h = h, w = w) | |
| # decode - spatial | |
| tokens = rearrange(tokens, 'b t h w d -> (b t) (h w) d') | |
| device=torch.device('cuda') | |
| attn_bias = self.spatial_rel_pos_bias(h, w, device = device) | |
| tokens = self.dec_spatial_transformer(tokens, attn_bias = attn_bias, video_shape = video_shape) | |
| tokens = rearrange(tokens, '(b t) (h w) d -> b t h w d', b = b, h = h , w = w) | |
| # to pixels | |
| #first_frame_token, rest_frames_tokens = tokens[:, :1], tokens[:, 1:] | |
| #first_frame = self.to_pixels_first_frame(first_frame_token) | |
| #rest_frames = self.to_pixels(rest_frames_tokens) | |
| recon_video = self.to_pixels(tokens) | |
| #recon_video = torch.cat((first_frame, rest_frames), dim = 2) | |
| return recon_video | |
| def forward( | |
| self, | |
| video, | |
| mask = None, | |
| return_recons = False, | |
| return_recons_only = False, | |
| return_discr_loss = False, | |
| apply_grad_penalty = True, | |
| return_only_codebook_ids = False, | |
| return_encoded_tokens=False | |
| ): | |
| assert video.ndim in {4, 5} | |
| is_image = video.ndim == 4 | |
| #print(video.shape) | |
| if is_image: | |
| video = rearrange(video, 'b c h w -> b c 1 h w') | |
| assert not exists(mask) | |
| b, c, f, *image_dims, device = *video.shape, video.device | |
| device=torch.device('cuda') | |
| assert tuple(image_dims) == self.image_size | |
| assert not exists(mask) or mask.shape[-1] == f | |
| first_frame, rest_frames = video[:, :, :1], video[:, :, 1:] | |
| # derive patches | |
| #first_frame_tokens = self.to_patch_emb_first_frame(first_frame) | |
| #rest_frames_tokens = self.to_patch_emb(rest_frames) | |
| #tokens = torch.cat((first_frame_tokens, rest_frames_tokens), dim = 1) | |
| tokens = self.to_patch_emb(video) | |
| # save height and width in | |
| shape = tokens.shape | |
| *_, h, w, _ = shape | |
| # encode - spatial | |
| tokens = self.encode(tokens) | |
| # quantize | |
| tokens, packed_fhw_shape = pack([tokens], 'b * d') | |
| vq_mask = None | |
| if exists(mask): | |
| vq_mask = self.calculate_video_token_mask(video, mask) | |
| # VQ stays in float32 - convert input/output only | |
| tokens_dtype = tokens.dtype | |
| tokens = tokens.float() | |
| tokens, indices, commit_loss = self.vq(tokens, mask = vq_mask) | |
| tokens = tokens.to(tokens_dtype) | |
| if return_only_codebook_ids: | |
| indices, = unpack(indices, packed_fhw_shape, 'b *') | |
| return indices | |
| tokens = rearrange(tokens, 'b (t h w) d -> b t h w d', h = h, w = w) | |
| if return_encoded_tokens: | |
| return tokens | |
| recon_video = self.decode(tokens) | |
| returned_recon = rearrange(recon_video, 'b c 1 h w -> b c h w') if is_image else recon_video.clone() | |
| if return_recons_only: | |
| return returned_recon | |
| if exists(mask): | |
| # variable lengthed video / images training | |
| recon_loss = F.mse_loss(video, recon_video, reduction = 'none') | |
| recon_loss = recon_loss[repeat(mask, 'b t -> b c t', c = c)] | |
| recon_loss = recon_loss.mean() | |
| else: | |
| recon_loss = F.mse_loss(video, recon_video) | |
| # prepare a random frame index to be chosen for discriminator and perceptual loss | |
| pick_frame_logits = torch.randn(b, f) | |
| if exists(mask): | |
| mask_value = -torch.finfo(pick_frame_logits.dtype).max | |
| pick_frame_logits = pick_frame_logits.masked_fill(~mask, mask_value) | |
| frame_indices = pick_frame_logits.topk(1, dim = -1).indices | |
| # whether to return discriminator loss | |
| if return_discr_loss: | |
| assert exists(self.discr), 'discriminator must exist to train it' | |
| video = pick_video_frame(video, frame_indices) | |
| recon_video = pick_video_frame(recon_video, frame_indices) | |
| recon_video = recon_video.detach() | |
| video.requires_grad_() | |
| transform = T.Compose([T.Resize(256)]) | |
| recon_video = transform(recon_video) | |
| video = transform(video) | |
| #print("TEST") | |
| #print(recon_video.shape) | |
| recon_video_discr_logits, video_discr_logits = map(self.discr, (recon_video, video)) | |
| discr_loss = self.discr_loss(recon_video_discr_logits, video_discr_logits) | |
| if apply_grad_penalty: | |
| gp = gradient_penalty(video, video_discr_logits) | |
| loss = discr_loss + gp | |
| if return_recons: | |
| return loss, returned_recon | |
| return loss | |
| # early return if training on grayscale | |
| if not self.use_vgg_and_gan: | |
| if return_recons: | |
| return recon_loss, returned_recon | |
| return recon_loss | |
| # perceptual loss | |
| input_vgg_input = pick_video_frame(video, frame_indices) | |
| recon_vgg_input = pick_video_frame(recon_video, frame_indices) | |
| transform = T.Compose([T.Resize(256)]) | |
| input_vgg_input = transform(input_vgg_input) | |
| recon_vgg_input=transform(recon_vgg_input) | |
| # handle grayscale for vgg | |
| if video.shape[1] == 1: | |
| input_vgg_input2, recon_vgg_input2 = map(lambda t: repeat(t, 'b 1 ... -> b c ...', c = 3), (input_vgg_input, recon_vgg_input)) | |
| transform = T.Compose([T.Resize(256)]) | |
| input_vgg_input2 = transform(input_vgg_input2) | |
| recon_vgg_input2 = transform(recon_vgg_input2) | |
| input_vgg_feats = self.vgg(input_vgg_input2) | |
| recon_vgg_feats = self.vgg(recon_vgg_input2) | |
| perceptual_loss = F.mse_loss(input_vgg_feats, recon_vgg_feats) | |
| # generator loss | |
| gen_loss = self.gen_loss(self.discr(recon_vgg_input)) | |
| # calculate adaptive weight | |
| last_dec_layer = self.to_pixels[0].weight | |
| norm_grad_wrt_gen_loss = grad_layer_wrt_loss(gen_loss, last_dec_layer).norm(p = 2) | |
| norm_grad_wrt_perceptual_loss = grad_layer_wrt_loss(perceptual_loss, last_dec_layer).norm(p = 2) | |
| adaptive_weight = safe_div(norm_grad_wrt_perceptual_loss, norm_grad_wrt_gen_loss) | |
| adaptive_weight.clamp_(max = 1e4) | |
| # combine losses | |
| loss = recon_loss + perceptual_loss + commit_loss + adaptive_weight * gen_loss | |
| if return_recons: | |
| return loss, returned_recon | |
| return loss | |