Download mmdetection/projects/XDecoder/xdecoder/unified_head.py from maverickrzw/ct_detection: direct link, hf CLI and curl.
- Browser
- Download file 16.2 kB
-
https://huggingface.co/maverickrzw/ct_detection/resolve/main/mmdetection/projects/XDecoder/xdecoder/unified_head.py
- Command line
-
hf download hf://maverickrzw/ct_detection/mmdetection/projects/XDecoder/xdecoder/unified_head.py
-
curl -L -o unified_head.py https://huggingface.co/maverickrzw/ct_detection/resolve/main/mmdetection/projects/XDecoder/xdecoder/unified_head.py
16.2 kB
| import copy | |
| from typing import Sequence | |
| import torch | |
| from mmengine.structures import InstanceData, PixelData | |
| from torch import nn | |
| from torch.nn import functional as F | |
| from mmdet.evaluation.functional import INSTANCE_OFFSET | |
| from mmdet.registry import MODELS | |
| from .utils import (is_lower_torch_version, retry_if_cuda_oom, | |
| sem_seg_postprocess) | |
| class XDecoderUnifiedhead(nn.Module): | |
| def __init__(self, | |
| in_channels: int, | |
| pixel_decoder: nn.Module, | |
| transformer_decoder: nn.Module, | |
| task: str = 'semseg', | |
| test_cfg=None): | |
| super().__init__() | |
| self.task = task | |
| self.test_cfg = test_cfg | |
| pixel_decoder_ = copy.deepcopy(pixel_decoder) | |
| pixel_decoder_.update(in_channels=in_channels) | |
| self.pixel_decoder = MODELS.build(pixel_decoder_) | |
| transformer_decoder_ = copy.deepcopy(transformer_decoder) | |
| transformer_decoder_.update(task=task) | |
| self.predictor = MODELS.build(transformer_decoder_) | |
| self.return_inter_mask = False | |
| if self.task == 'ref-caption': | |
| # ref-caption = ref-seg + caption, | |
| # so we need to return the intermediate mask | |
| self.return_inter_mask = True | |
| self._all_text_prompts = None | |
| self._extra = None | |
| # TODO: Very trick, for retrieval task | |
| self._force_not_use_cache = False | |
| def pre_process(self, batch_data_samples, device): | |
| extra = {} | |
| if self.task != 'caption': | |
| # have text | |
| all_text_prompts = [] | |
| num_thing_class = 0 | |
| for data_samples in batch_data_samples: | |
| if isinstance(data_samples.text, str): | |
| text = data_samples.text.split('.') | |
| elif isinstance(data_samples.text, Sequence): | |
| text = data_samples.text | |
| else: | |
| raise TypeError( | |
| 'Type pf data_sample.text must be sequence or str') | |
| text = list(filter(lambda x: len(x) > 0, text)) | |
| all_text_prompts.append(text) | |
| num_thing_class = len(text) | |
| # for panoptic | |
| if 'stuff_text' in data_samples: | |
| if isinstance(data_samples.stuff_text, str): | |
| text = data_samples.stuff_text.split('.') | |
| elif isinstance(data_samples.stuff_text, Sequence): | |
| text = data_samples.stuff_text | |
| else: | |
| raise TypeError('Type pf data_sample.stuff_text ' | |
| 'must be sequence or str') | |
| text = list(filter(lambda x: len(x) > 0, text)) | |
| all_text_prompts[-1].extend(text) | |
| # TODO: support batch | |
| all_text_prompts = all_text_prompts[0] | |
| if all_text_prompts != self._all_text_prompts \ | |
| or self._force_not_use_cache: | |
| # avoid redundant computation | |
| self._all_text_prompts = all_text_prompts | |
| if self.task in ['semseg', 'instance', 'panoptic']: | |
| self.predictor.lang_encoder.get_mean_embeds( | |
| all_text_prompts + ['background']) | |
| elif self.task == 'ref-seg': | |
| token_info = self.predictor.lang_encoder.get_text_embeds( | |
| all_text_prompts, norm=False) | |
| token_emb = token_info['token_emb'] | |
| tokens = token_info['tokens'] | |
| query_emb = token_emb[tokens['attention_mask'].bool()] | |
| extra['grounding_tokens'] = query_emb[:, None] | |
| extra['class_emb'] = token_info['class_emb'] | |
| elif self.task == 'retrieval': | |
| token_info = self.predictor.lang_encoder.get_text_embeds( | |
| all_text_prompts, norm=True) | |
| extra['class_emb'] = token_info['class_emb'] | |
| self._extra = extra | |
| return extra, all_text_prompts, num_thing_class | |
| else: | |
| return self._extra, all_text_prompts, num_thing_class | |
| else: | |
| if not hasattr(self, 'start_token'): | |
| self.start_token = self.predictor.lang_encoder. \ | |
| get_sot_token(device=device) | |
| extra['start_token'] = self.start_token | |
| return extra, None, None | |
| def predict(self, features, batch_data_samples): | |
| # multi scale feature | |
| mask_features, multi_scale_features = self.pixel_decoder(features) | |
| # pre process | |
| extra, all_text_prompts, num_thing_class = self.pre_process( | |
| batch_data_samples, mask_features.device) | |
| # transformer decoder forward | |
| predictions = self.predictor( | |
| multi_scale_features, mask_features, extra=extra) | |
| # post process | |
| return self.post_process(predictions, batch_data_samples, | |
| all_text_prompts, num_thing_class) | |
| def post_process(self, predictions, batch_data_samples, all_text_prompts, | |
| num_thing_class): | |
| batch_img_metas = [ | |
| data_samples.metainfo for data_samples in batch_data_samples | |
| ] | |
| batch_input_shape = batch_data_samples[0].metainfo['batch_input_shape'] | |
| if self.task == 'caption': | |
| for text, data_samples in zip(predictions['pred_caption'], | |
| batch_data_samples): | |
| data_samples.pred_caption = text | |
| if 'pred_instances' in batch_data_samples[0]: | |
| for img_metas, data_samples in zip(batch_img_metas, | |
| batch_data_samples): | |
| original_caption = data_samples.text.split('.') | |
| text_prompts = list( | |
| filter(lambda x: len(x) > 0, original_caption)) | |
| height = img_metas['ori_shape'][0] | |
| width = img_metas['ori_shape'][1] | |
| image_size = img_metas['grounding_img_shape'][:2] | |
| mask_pred_result = data_samples.pred_instances.masks.float( | |
| ) | |
| mask_cls_result = data_samples.pred_instances.scores.float( | |
| ) | |
| mask_pred_result = retry_if_cuda_oom(sem_seg_postprocess)( | |
| mask_pred_result, image_size, height, width) | |
| pred_instances = retry_if_cuda_oom( | |
| self._instance_inference)(mask_cls_result, | |
| mask_pred_result, | |
| text_prompts) | |
| data_samples.pred_instances = pred_instances | |
| elif self.task in ['semseg', 'instance', 'panoptic']: | |
| mask_pred_results = predictions['pred_masks'] | |
| mask_cls_results = predictions['pred_logits'] | |
| if is_lower_torch_version(): | |
| mask_pred_results = F.interpolate( | |
| mask_pred_results, | |
| size=(batch_input_shape[-2], batch_input_shape[-1]), | |
| mode='bicubic', | |
| align_corners=False) | |
| else: | |
| mask_pred_results = F.interpolate( | |
| mask_pred_results, | |
| size=(batch_input_shape[-2], batch_input_shape[-1]), | |
| mode='bicubic', | |
| align_corners=False, | |
| antialias=True) | |
| # for batch | |
| for mask_cls_result, \ | |
| mask_pred_result, \ | |
| img_metas, \ | |
| data_samples in zip( | |
| mask_cls_results, | |
| mask_pred_results, | |
| batch_img_metas, | |
| batch_data_samples): | |
| height = img_metas['ori_shape'][0] | |
| width = img_metas['ori_shape'][1] | |
| image_size = img_metas['img_shape'][:2] | |
| mask_pred_result = retry_if_cuda_oom(sem_seg_postprocess)( | |
| mask_pred_result, image_size, height, width) | |
| mask_cls_result = mask_cls_result.to(mask_pred_result) | |
| if self.task == 'semseg': | |
| pred_sem_seg = retry_if_cuda_oom(self._semantic_inference)( | |
| mask_cls_result, mask_pred_result, all_text_prompts) | |
| data_samples.pred_sem_seg = pred_sem_seg | |
| elif self.task == 'instance': | |
| pred_instances = retry_if_cuda_oom( | |
| self._instance_inference)(mask_cls_result, | |
| mask_pred_result, | |
| all_text_prompts) | |
| data_samples.pred_instances = pred_instances | |
| elif self.task == 'panoptic': | |
| pred_panoptic_seg = retry_if_cuda_oom( | |
| self._panoptic_inference)(mask_cls_result, | |
| mask_pred_result, | |
| all_text_prompts, | |
| num_thing_class) | |
| data_samples.pred_panoptic_seg = pred_panoptic_seg | |
| elif self.task == 'ref-seg': | |
| mask_pred_results = predictions['pred_masks'] | |
| mask_cls_results = predictions['pred_logits'] | |
| results_ = zip(mask_pred_results, mask_cls_results, | |
| batch_img_metas, batch_data_samples) | |
| for mask_pred_result, mask_cls_result, \ | |
| img_metas, data_samples in results_: | |
| if is_lower_torch_version(): | |
| mask_pred_result = F.interpolate( | |
| mask_pred_result[None], | |
| size=(batch_input_shape[-2], batch_input_shape[-1]), | |
| mode='bicubic', | |
| align_corners=False)[0] | |
| else: | |
| mask_pred_result = F.interpolate( | |
| mask_pred_result[None], | |
| size=(batch_input_shape[-2], batch_input_shape[-1]), | |
| mode='bicubic', | |
| align_corners=False, | |
| antialias=True)[0] | |
| if self.return_inter_mask: | |
| mask = mask_pred_result > 0 | |
| pred_instances = InstanceData() | |
| pred_instances.masks = mask | |
| pred_instances.scores = mask_cls_result | |
| data_samples.pred_instances = pred_instances | |
| continue | |
| height = img_metas['ori_shape'][0] | |
| width = img_metas['ori_shape'][1] | |
| image_size = img_metas['img_shape'][:2] | |
| mask_pred_result = retry_if_cuda_oom(sem_seg_postprocess)( | |
| mask_pred_result, image_size, height, width) | |
| pred_instances = retry_if_cuda_oom(self._instance_inference)( | |
| mask_cls_result, mask_pred_result, all_text_prompts) | |
| data_samples.pred_instances = pred_instances | |
| elif self.task == 'retrieval': | |
| batch_data_samples[0].pred_score = predictions['pred_logits'] | |
| return batch_data_samples | |
| def _instance_inference(self, mask_cls, mask_pred, text_prompts): | |
| num_class = len(text_prompts) | |
| if self.task in ['ref-seg', 'caption']: | |
| scores = F.softmax(mask_cls, dim=-1) | |
| scores_per_image = scores.max(dim=-1)[0] | |
| labels_per_image = torch.arange(num_class) | |
| else: | |
| scores = F.softmax(mask_cls, dim=-1)[:, :-1] | |
| labels = torch.arange( | |
| num_class, | |
| device=scores.device).unsqueeze(0).repeat(scores.shape[0], | |
| 1).flatten(0, 1) | |
| scores_per_image, topk_indices = scores.flatten(0, 1).topk( | |
| self.test_cfg.get('max_per_img', 100), sorted=False) | |
| labels_per_image = labels[topk_indices] | |
| topk_indices = (topk_indices // num_class) | |
| mask_pred = mask_pred[topk_indices] | |
| result = InstanceData() | |
| mask_pred = mask_pred.sigmoid() | |
| result.masks = (mask_pred > self.test_cfg.mask_thr).float() | |
| # calculate average mask prob | |
| mask_scores_per_image = (mask_pred.flatten(1) * | |
| result.masks.flatten(1)).sum(1) / ( | |
| result.masks.flatten(1).sum(1) + 1e-6) | |
| result.scores = scores_per_image * mask_scores_per_image | |
| result.labels = labels_per_image | |
| result.label_names = [ | |
| text_prompts[label] for label in labels_per_image | |
| ] | |
| result.bboxes = result.scores.new_zeros(len(result.scores), 4) | |
| return result | |
| def _semantic_inference(self, mask_cls, mask_pred, text_prompts): | |
| mask_cls = F.softmax(mask_cls, dim=-1)[..., :-1] | |
| mask_pred = mask_pred.sigmoid() | |
| sem_seg = torch.einsum('qc,qhw->chw', mask_cls, mask_pred) | |
| if sem_seg.shape[0] == 1: | |
| # 0 is foreground, ignore_index is background | |
| sem_seg = (sem_seg.squeeze(0) <= self.test_cfg.mask_thr).int() | |
| sem_seg[sem_seg == 1] = self.test_cfg.get('ignore_index', 255) | |
| else: | |
| # 0 is foreground, ignore_index is background | |
| if self.test_cfg.use_thr_for_mc: | |
| foreground_flag = sem_seg > self.test_cfg.mask_thr | |
| sem_seg = sem_seg.max(0)[1] | |
| sem_seg[foreground_flag.sum(0) == 0] = self.test_cfg.get( | |
| 'ignore_index', 255) | |
| else: | |
| sem_seg = sem_seg.max(0)[1] | |
| pred_sem_seg = PixelData( | |
| sem_seg=sem_seg[None], | |
| metainfo={ | |
| 'label_names': text_prompts, | |
| 'ignore_index': self.test_cfg.get('ignore_index', 255) | |
| }) | |
| return pred_sem_seg | |
| def _panoptic_inference(self, mask_cls, mask_pred, all_text_prompts, | |
| num_thing_class): | |
| scores, labels = F.softmax(mask_cls, dim=-1).max(-1) | |
| mask_pred = mask_pred.sigmoid() | |
| keep = labels.ne(len(all_text_prompts)) & ( | |
| scores > self.test_cfg.mask_thr) | |
| cur_scores = scores[keep] | |
| cur_classes = labels[keep] | |
| cur_masks = mask_pred[keep] | |
| cur_prob_masks = cur_scores.view(-1, 1, 1) * cur_masks | |
| h, w = cur_masks.shape[-2:] | |
| panoptic_seg = torch.full((h, w), | |
| self.test_cfg.get('ignore_index', 255), | |
| dtype=torch.int32, | |
| device=cur_masks.device) | |
| instance_id = 1 | |
| if cur_masks.shape[0] > 0: | |
| cur_mask_ids = cur_prob_masks.argmax(0) | |
| for k in range(cur_classes.shape[0]): | |
| pred_class = cur_classes[k].item() | |
| isthing = int(pred_class) < num_thing_class | |
| mask_area = (cur_mask_ids == k).sum().item() | |
| original_area = (cur_masks[k] >= 0.5).sum().item() | |
| mask = (cur_mask_ids == k) & (cur_masks[k] >= 0.5) | |
| if mask_area > 0 and original_area > 0 and mask.sum().item( | |
| ) > 0: | |
| if mask_area / original_area < self.test_cfg.overlap_thr: | |
| continue | |
| # merge stuff regions | |
| if not isthing: | |
| panoptic_seg[mask] = int(pred_class) | |
| else: | |
| panoptic_seg[mask] = int( | |
| pred_class) + instance_id * INSTANCE_OFFSET | |
| instance_id += 1 | |
| panoptic_seg = PixelData( | |
| sem_seg=panoptic_seg[None], | |
| metainfo={ | |
| 'label_names': all_text_prompts, | |
| 'ignore_index': self.test_cfg.get('ignore_index', 255) | |
| }) | |
| return panoptic_seg | |