"""Panda detector v3m2 with shared queries for multiple label sets."""
from __future__ import annotations
from copy import deepcopy
from typing import Dict, Literal, Optional, Tuple
import torch
import torch.nn.functional as F
from torch import nn
from timm.layers import trunc_normal_
from pimm.models.builder import MODELS, build_model
from pimm.models.losses import build_criteria
from pimm.models.modules import PointModel
from pimm.models.utils.misc import offset2bincount
from pimm.models.utils.structure import Point
from pimm.utils.comm import reduce_scalar_outputs_for_logging
from .layers import Block, MLP
from .postprocess import postprocess_batch
class MaskQueryDecoder(nn.Module):
"""Masked-cross-attention query decoder (loosely Mask2Former / OneFormer3D)."""
__max_seqlen = 0
def __init__(
self,
full_in_channels,
hidden_channels,
num_heads,
num_classes,
num_queries=32,
depth=3,
mlp_ratio=4.0,
qkv_bias=False,
qk_scale=None,
attn_drop=0.0,
proj_drop=0.0,
drop_path=0.0,
layer_scale=None,
norm_layer=nn.LayerNorm,
act_layer=nn.GELU,
pre_norm=True,
enable_flash=True,
upcast_attention=True,
upcast_softmax=True,
pos_emb=True,
enc_mode=True,
query_type: Literal["learned", "superpoint"] = "superpoint",
mlp_point_proj=False,
use_stuff_head=False,
stuff_classes=None,
supervise_attn_mask=True,
train_filter_use_gt: bool = False,
):
super().__init__()
self.full_in_channels = full_in_channels
self.mask_channels = hidden_channels
self.num_classes = num_classes
self.enc_mode = enc_mode
self.num_queries = num_queries
self.use_stuff_head = use_stuff_head
self.stuff_classes = set(stuff_classes) if stuff_classes is not None else set()
self.train_filter_use_gt = bool(train_filter_use_gt)
self.query_type = query_type
if self.query_type == "learned":
self.query_feat = nn.Embedding(self.num_queries, hidden_channels)
self.query_embed = nn.Embedding(self.num_queries, hidden_channels)
self.pos_emb = (
nn.Sequential(
nn.Linear(3, hidden_channels),
nn.GELU(),
nn.Linear(hidden_channels, hidden_channels),
)
if pos_emb
else None
)
# annealing parameters (can be set via set_attn_mask_anneal)
self.attn_mask_anneal = False
self.attn_mask_anneal_steps = 10000
self.attn_mask_warmup_steps = 0
self.attn_mask_progressive = False
self.attn_mask_progressive_delay = 0
self.supervise_attn_mask = supervise_attn_mask
self.blocks = nn.ModuleList(
[
Block(
channels=hidden_channels,
num_heads=num_heads,
mlp_ratio=mlp_ratio,
qkv_bias=qkv_bias,
qk_scale=qk_scale,
attn_drop=attn_drop,
proj_drop=proj_drop,
drop_path=drop_path,
layer_scale=layer_scale,
norm_layer=norm_layer,
act_layer=act_layer,
pre_norm=pre_norm,
enable_flash=enable_flash,
upcast_attention=upcast_attention,
upcast_softmax=upcast_softmax,
use_attn_mask=True, # always True for instance segmentation
attn_mask_eps=1e-6,
attn_mask_anneal=self.attn_mask_anneal,
attn_mask_anneal_steps=self.attn_mask_anneal_steps,
attn_mask_warmup_steps=self.attn_mask_warmup_steps,
supervise_attn_mask=self.supervise_attn_mask,
is_last_block=(i == depth - 1),
)
for i in range(depth)
]
)
self.final_norm = norm_layer(hidden_channels)
# output FFN
self.cls_pred = (
MLP(hidden_channels, hidden_channels, num_classes + 1)
if mlp_point_proj
else nn.Linear(hidden_channels, num_classes + 1)
)
self.full_point_proj = (
MLP(full_in_channels, hidden_channels, hidden_channels)
if mlp_point_proj
else nn.Linear(full_in_channels, hidden_channels)
)
# stuff head: point-wise binary classifier (stuff vs thing)
if self.use_stuff_head:
self.stuff_head = nn.Sequential(
nn.Linear(full_in_channels, hidden_channels),
nn.ReLU(),
nn.Linear(hidden_channels, 1),
)
self.apply(self._init_weights)
@staticmethod
def _init_weights(module):
if isinstance(module, nn.Linear):
trunc_normal_(module.weight, std=0.02)
if module.bias is not None:
nn.init.zeros_(module.bias)
def set_attn_mask_anneal(
self,
enable: bool,
anneal_steps: int = 10000,
warmup_steps: int = 0,
progressive: bool = False,
progressive_delay: int = 0,
):
"""Configure attention-mask annealing for all blocks."""
self.attn_mask_anneal = enable
self.attn_mask_anneal_steps = anneal_steps
self.attn_mask_warmup_steps = warmup_steps
self.attn_mask_progressive = progressive
self.attn_mask_progressive_delay = progressive_delay
for i, block in enumerate(self.blocks):
block.attn_mask_anneal = enable
block.attn_mask_anneal_steps = anneal_steps
if progressive and progressive_delay > 0:
block.attn_mask_warmup_steps = warmup_steps + (i * progressive_delay)
else:
block.attn_mask_warmup_steps = warmup_steps
def update_anneal_step(self, step: int):
"""Update annealing progress for all blocks."""
for block in self.blocks:
block.set_anneal_step(step)
def _max_seqlen(self, seq_len: int) -> int:
if seq_len > self.__max_seqlen:
self.__max_seqlen = seq_len
return self.__max_seqlen
def _get_queries(
self, point: Point
) -> Tuple[torch.Tensor, Optional[torch.Tensor], torch.Tensor, torch.Tensor]:
batch_size = point.offset.shape[0]
device = point.feat.device
max_queries = self.num_queries
if self.query_type == "learned":
base_q = self.query_feat.weight # [Q, C]
base_pos = self.query_embed.weight if hasattr(self, "query_embed") else None
q = base_q.unsqueeze(0).repeat(batch_size, 1, 1) # [B, Q, C]
pos_q = None
if base_pos is not None:
pos_q = base_pos.unsqueeze(0).repeat(batch_size, 1, 1)
counts = torch.full(
(batch_size,), max_queries, dtype=torch.int32, device=device
)
valid_mask = torch.ones(
batch_size, max_queries, dtype=torch.bool, device=device
)
return q, pos_q, counts, valid_mask
else:
raise NotImplementedError(f"Invalid query type: {self.query_type}")
def _expand_masks(
self, mask_logits: torch.Tensor, thing_mask: torch.Tensor
) -> torch.Tensor:
"""Expand (Q, N_things) mask logits to (Q, N) over the full cloud; dropped
points get a large-negative sentinel."""
Q = mask_logits.shape[0]
N = thing_mask.shape[0]
mask_logits_full = mask_logits.new_full((Q, N), -1e4)
mask_logits_full[:, thing_mask] = mask_logits
return mask_logits_full
def _forward_decoder(self, point: Point, return_aux: bool = False):
"""Run the transformer decoder blocks. Returns out_q, final_mask_logits,
query_counts, query_valid (+ aux lists if return_aux)."""
point_proj = point.feat # projection done in caller
pos_k = self.pos_emb(point.coord) if self.pos_emb else None
cu_seqlens_kv = torch.cat(
[point.offset.new_zeros(1), point.offset]
).int() # [B + 1]
max_seqlen_kv = cu_seqlens_kv.diff().max()
q, pos_q, query_counts, query_valid = self._get_queries(point)
cu_seqlens_q = torch.cat(
[query_counts.new_zeros(1), query_counts.cumsum(dim=0)]
).int()
max_seqlen_q = int(query_counts.max().item()) if query_counts.numel() > 0 else 0
q = q.reshape(-1, self.mask_channels) # [B * Q, C]
pos_q = pos_q.reshape(-1, self.mask_channels) if pos_q is not None else None
query_valid = query_valid.reshape(-1, 1)
query_valid_f = query_valid.to(q.dtype)
aux_outputs = []
aux_p_hat_list = []
final_mask_logits = None
final_mask_embed = None
final_mask_point_proj = None
for blk in self.blocks:
q, mask_logits, mask_embed, mask_point_proj = blk(
q,
point_proj,
cu_seqlens_q,
cu_seqlens_kv,
max_seqlen_q,
max_seqlen_kv,
pos_q,
pos_k,
)
if mask_logits is not None:
mask_logits = mask_logits * query_valid_f
final_mask_logits = mask_logits
final_mask_embed = (
mask_embed * query_valid_f if mask_embed is not None else None
)
final_mask_point_proj = mask_point_proj
q = q * query_valid_f
if return_aux:
aux_outputs.append(self.final_norm(q))
if self.supervise_attn_mask:
aux_p_hat_list.append(mask_logits)
else:
aux_p_hat_list.append(None)
q_norm = self.final_norm(q)
query_counts_long = query_counts.to(torch.long)
query_valid_flat = query_valid.squeeze(-1).bool()
outputs = {
"out_q": q_norm,
"point_proj": point_proj,
"final_mask_logits": final_mask_logits,
"final_mask_embed": final_mask_embed,
"final_mask_point_proj": final_mask_point_proj,
"query_counts": query_counts_long,
"query_valid": query_valid_flat,
}
if return_aux:
outputs["aux_q_list"] = aux_outputs[:-1]
outputs["aux_mask_logits_list"] = aux_p_hat_list[:-1]
return outputs
def up_cast(self, point):
"""Upcast features to point-level resolution. enc_mode=False: no-op.
enc_mode=True: walk the pooling hierarchy, concatenating multi-scale features.
"""
if not self.enc_mode:
return point
while "pooling_parent" in point.keys():
assert "pooling_inverse" in point.keys()
parent = point.pop("pooling_parent")
inverse = point.pop("pooling_inverse")
parent.feat = torch.cat([parent.feat, point.feat[inverse]], dim=-1)
point = parent
return point
_DEFAULT_LABEL_SPECS = {
"particle": dict(
instance_key="instance_particle",
segment_key="segment_particle",
segment_fallback_key="segment_pid",
num_classes=6,
stuff_classes=[5],
),
"interaction": dict(
instance_key="instance_interaction",
segment_key="segment_interaction",
num_classes=2,
stuff_classes=[0],
),
}
def _replace_instance_loss(cfg):
"""Select the optimized loss used by detector-v3m2."""
cfg = deepcopy(cfg)
if isinstance(cfg, dict):
if cfg.get("type") == "InstanceSegmentationLoss":
cfg["type"] = "FastInstanceSegmentationLoss"
return cfg
if isinstance(cfg, list):
return [_replace_instance_loss(item) for item in cfg]
if isinstance(cfg, tuple):
return tuple(_replace_instance_loss(item) for item in cfg)
return cfg
class MultiLabelMaskQueryDecoder(MaskQueryDecoder):
"""Mask-query decoder that batches queries for multiple instance targets."""
def __init__(
self,
label_specs,
full_in_channels,
hidden_channels,
num_heads,
depth=3,
mlp_ratio=4.0,
qkv_bias=False,
qk_scale=None,
attn_drop=0.0,
proj_drop=0.0,
drop_path=0.0,
layer_scale=None,
norm_layer=nn.LayerNorm,
act_layer=nn.GELU,
pre_norm=True,
enable_flash=True,
upcast_attention=True,
upcast_softmax=True,
pos_emb=True,
enc_mode=False,
query_type: Literal["learned"] = "learned",
mlp_point_proj=False,
supervise_attn_mask=True,
):
self.labels = tuple(label_specs.keys())
self.label_specs = label_specs
query_slices = {}
query_start = 0
max_classes = 1
for label, spec in label_specs.items():
query_end = query_start + int(spec["num_queries"])
query_slices[label] = (query_start, query_end)
query_start = query_end
max_classes = max(max_classes, int(spec["num_classes"]))
self.query_slices = query_slices
self.total_queries = query_start
super().__init__(
full_in_channels=full_in_channels,
hidden_channels=hidden_channels,
num_heads=num_heads,
num_classes=max_classes,
num_queries=self.total_queries,
depth=depth,
mlp_ratio=mlp_ratio,
qkv_bias=qkv_bias,
qk_scale=qk_scale,
attn_drop=attn_drop,
proj_drop=proj_drop,
drop_path=drop_path,
layer_scale=layer_scale,
norm_layer=norm_layer,
act_layer=act_layer,
pre_norm=pre_norm,
enable_flash=enable_flash,
upcast_attention=upcast_attention,
upcast_softmax=upcast_softmax,
pos_emb=pos_emb,
enc_mode=enc_mode,
query_type=query_type,
mlp_point_proj=mlp_point_proj,
use_stuff_head=False,
stuff_classes=None,
supervise_attn_mask=supervise_attn_mask,
train_filter_use_gt=False,
)
# Label-specific heads below replace the superclass's single class head.
self.cls_pred = nn.Identity()
self.cls_pred_by_label = nn.ModuleDict()
self.stuff_head_by_label = nn.ModuleDict()
for label, spec in label_specs.items():
out_classes = int(spec["num_classes"]) + 1
self.cls_pred_by_label[label] = (
MLP(hidden_channels, hidden_channels, out_classes)
if mlp_point_proj
else nn.Linear(hidden_channels, out_classes)
)
if spec["use_stuff_head"]:
self.stuff_head_by_label[label] = nn.Sequential(
nn.Linear(full_in_channels, hidden_channels),
nn.ReLU(),
nn.Linear(hidden_channels, 1),
)
self.apply(self._init_weights)
def _compute_label_predictions(
self,
label,
q_features,
mask_logits,
point,
query_valid,
):
class_embed = self.cls_pred_by_label[label](q_features)
pred_masks = []
pred_cls = []
pred_logits = []
C = int(self.label_specs[label]["num_classes"])
label_q_start, label_q_end = self.query_slices[label]
total_q = self.total_queries
B = point.offset.shape[0]
counts = offset2bincount(point.offset).to(torch.long)
point_offsets = torch.cat([counts.new_zeros(1), point.offset])
for b in range(B):
P_b = counts[b].item()
q_start = b * total_q + label_q_start
q_end = b * total_q + label_q_end
p_start, p_end = point_offsets[b], point_offsets[b + 1]
mask_logits_b = mask_logits[q_start:q_end, p_start:p_end]
cls_b = class_embed[q_start:q_end]
valid_b = query_valid[q_start:q_end]
mask_logits_b = mask_logits_b[valid_b]
cls_b = cls_b[valid_b]
pred_masks.append(mask_logits_b)
pred_cls.append(cls_b)
if mask_logits_b.shape[0] > 0:
s = mask_logits_b.transpose(0, 1).unsqueeze(-1)
c = cls_b[:, :C].unsqueeze(0)
logits_b = torch.logsumexp(s + c, dim=1)
else:
logits_b = mask_logits.new_zeros((P_b, C))
pred_logits.append(logits_b)
pred_logits = (
torch.cat(pred_logits, dim=0)
if pred_logits
else mask_logits.new_zeros((0, C))
)
return {
"pred_masks": pred_masks,
"pred_logits": pred_cls,
"seg_logits": pred_logits,
}
def forward(self, point: Point, return_decoder: bool = False):
point_full = self.up_cast(point)
decoder_point = point_full.copy()
decoder_point.feat = self.full_point_proj(point_full.feat)
return_aux = self.training
decoder_outputs = self._forward_decoder(decoder_point, return_aux=return_aux)
final_mask_logits = decoder_outputs["final_mask_logits"]
query_valid = decoder_outputs["query_valid"]
out_q = decoder_outputs["out_q"]
outputs_by_label = {}
for label in self.labels:
predictions = self._compute_label_predictions(
label, out_q, final_mask_logits, point_full, query_valid
)
if return_aux:
aux_outputs = []
for aux_q, aux_mask_logits in zip(
decoder_outputs["aux_q_list"],
decoder_outputs["aux_mask_logits_list"],
):
if aux_mask_logits is None:
continue
aux_outputs.append(
self._compute_label_predictions(
label, aux_q, aux_mask_logits, point_full, query_valid
)
)
if aux_outputs:
predictions["aux_outputs"] = aux_outputs
if label in self.stuff_head_by_label:
stuff_logits = self.stuff_head_by_label[label](point_full.feat).squeeze(
-1
)
predictions["stuff_logits"] = stuff_logits
predictions["stuff_probs"] = stuff_logits.sigmoid()
outputs_by_label[label] = predictions
if return_decoder:
return outputs_by_label, decoder_outputs
return outputs_by_label
[docs]
@MODELS.register_module("detector-v3m2")
class MultiLabelDetectorV3M2(PointModel):
"""Shared-backbone Panda detector with one batched query decoder for all labels."""
def __init__(
self,
full_in_channels,
hidden_channels,
num_heads,
labels=("particle",),
num_queries=32,
num_classes=None,
label_configs=None,
loss_weights=None,
eval_label=None,
backbone=None,
criteria=None,
criteria_by_label=None,
depth=3,
mlp_ratio=4.0,
qkv_bias=False,
qk_scale=None,
attn_drop=0.0,
proj_drop=0.0,
drop_path=0.0,
layer_scale=None,
norm_layer=nn.LayerNorm,
act_layer=nn.GELU,
pre_norm=True,
enable_flash=True,
upcast_attention=False,
upcast_softmax=False,
pos_emb=True,
attn_mask_anneal=False,
attn_mask_anneal_steps=10000,
attn_mask_warmup_steps=0,
attn_mask_progressive=False,
attn_mask_progressive_delay=0,
query_type: Literal["learned"] = "learned",
use_stuff_head=True,
stuff_classes=None,
supervise_attn_mask=True,
train_filter_use_gt: bool = False,
mlp_point_proj=False,
# postprocessing parameters for the primary/eval label
stuff_threshold=0.5,
mask_threshold=0.5,
conf_threshold=0.5,
nms_kernel="gaussian",
nms_sigma=2.0,
nms_pre=-1,
nms_max=-1,
min_points=2,
fill_uncovered=False,
):
super().__init__()
if isinstance(labels, str):
labels = (labels,)
self.labels = tuple(labels)
if len(self.labels) == 0:
raise ValueError("labels must contain at least one label name")
self.label_specs = self._build_label_specs(
self.labels,
label_configs=label_configs,
num_queries=num_queries,
num_classes=num_classes,
stuff_classes=stuff_classes,
use_stuff_head=use_stuff_head,
loss_weights=loss_weights,
)
self.eval_label = eval_label or self.labels[-1]
if self.eval_label not in self.label_specs:
raise ValueError(
f"eval_label={self.eval_label!r} must be one of {self.labels}"
)
self.backbone = build_model(backbone)
self.criteria_by_label = {
label: build_criteria(
self._criteria_cfg(label, criteria, criteria_by_label)
)
for label in self.labels
}
if train_filter_use_gt:
raise ValueError(
"detector-v3m2 batches queries across labels and does not support "
"per-label GT filtering inside cross-attention. Use stuff losses "
"and postprocess thresholds instead."
)
self.decoder = MultiLabelMaskQueryDecoder(
label_specs=self.label_specs,
full_in_channels=full_in_channels,
hidden_channels=hidden_channels,
num_heads=num_heads,
depth=depth,
mlp_ratio=mlp_ratio,
qkv_bias=qkv_bias,
qk_scale=qk_scale,
attn_drop=attn_drop,
proj_drop=proj_drop,
drop_path=drop_path,
layer_scale=layer_scale,
norm_layer=norm_layer,
act_layer=act_layer,
pre_norm=pre_norm,
enable_flash=enable_flash,
upcast_attention=upcast_attention,
upcast_softmax=upcast_softmax,
pos_emb=pos_emb,
enc_mode=False,
query_type=query_type,
mlp_point_proj=mlp_point_proj,
supervise_attn_mask=supervise_attn_mask,
)
if attn_mask_anneal:
self.decoder.set_attn_mask_anneal(
enable=True,
anneal_steps=attn_mask_anneal_steps,
warmup_steps=attn_mask_warmup_steps,
progressive=attn_mask_progressive,
progressive_delay=attn_mask_progressive_delay,
)
self.postprocess_cfg = dict(
stuff_threshold=stuff_threshold,
mask_threshold=mask_threshold,
conf_threshold=conf_threshold,
nms_kernel=nms_kernel,
nms_sigma=nms_sigma,
nms_pre=nms_pre,
nms_max=nms_max,
min_points=min_points,
fill_uncovered=fill_uncovered,
)
@staticmethod
def _select_value(value, labels, label, idx, default=None):
if value is None:
return default
if isinstance(value, dict):
return value.get(label, default)
if isinstance(value, (list, tuple)) and len(value) == len(labels):
return value[idx]
return value
@classmethod
def _select_stuff_classes(cls, value, labels, label, idx, default=None):
if value is None:
return default
if isinstance(value, dict):
return value.get(label, default)
if isinstance(value, (list, tuple)) and len(value) == len(labels):
if all(isinstance(v, (list, tuple, set)) for v in value):
return value[idx]
return value
@classmethod
def _build_label_specs(
cls,
labels,
label_configs=None,
num_queries=32,
num_classes=None,
stuff_classes=None,
use_stuff_head=True,
loss_weights=None,
):
label_configs = label_configs or {}
specs = {}
for idx, label in enumerate(labels):
spec = deepcopy(_DEFAULT_LABEL_SPECS.get(label, {}))
spec.update(deepcopy(label_configs.get(label, {})))
queries = cls._select_value(num_queries, labels, label, idx, None)
classes = cls._select_value(
num_classes, labels, label, idx, spec.get("num_classes")
)
stuff = cls._select_stuff_classes(
stuff_classes, labels, label, idx, spec.get("stuff_classes", [])
)
task_use_stuff = cls._select_value(use_stuff_head, labels, label, idx, True)
loss_weight = cls._select_value(loss_weights, labels, label, idx, 1.0)
if queries is None:
raise ValueError(f"num_queries is required for label {label!r}")
if classes is None:
raise ValueError(
f"num_classes is required for label {label!r}; provide "
"num_classes or label_configs"
)
if "instance_key" not in spec:
spec["instance_key"] = f"instance_{label}"
if "segment_key" not in spec:
spec["segment_key"] = f"segment_{label}"
spec["num_queries"] = int(queries)
spec["num_classes"] = int(classes)
spec["stuff_classes"] = list(stuff or [])
spec["use_stuff_head"] = bool(task_use_stuff)
spec["loss_weight"] = float(loss_weight)
specs[label] = spec
return specs
@staticmethod
def _criteria_cfg(label, criteria, criteria_by_label):
if criteria_by_label is not None and label in criteria_by_label:
return _replace_instance_loss(criteria_by_label[label])
return _replace_instance_loss(criteria)
[docs]
def update_anneal_step(self, step: int):
self.decoder.update_anneal_step(step)
[docs]
def up_cast(self, point: Point) -> Point:
enc_mode = getattr(self.backbone, "enc_mode", True)
if not enc_mode:
return point
while "pooling_parent" in point.keys():
assert "pooling_inverse" in point.keys()
parent = point.pop("pooling_parent")
inverse = point.pop("pooling_inverse")
parent.feat = torch.cat([parent.feat, point.feat[inverse]], dim=-1)
point = parent
return point
def _segment_key(self, input_dict: Dict, spec: Dict) -> str:
segment_key = spec["segment_key"]
if segment_key in input_dict:
return segment_key
fallback = spec.get("segment_fallback_key")
if fallback is not None and fallback in input_dict:
return fallback
return segment_key
def _task_input(self, input_dict: Dict, label: str) -> Dict:
spec = self.label_specs[label]
instance_key = spec["instance_key"]
segment_key = self._segment_key(input_dict, spec)
if instance_key not in input_dict:
raise KeyError(f"Missing instance labels for {label!r}: {instance_key}")
if segment_key not in input_dict:
raise KeyError(f"Missing semantic labels for {label!r}: {segment_key}")
task_input = dict(input_dict)
task_input["instance"] = input_dict[instance_key]
task_input["segment"] = input_dict[segment_key]
return task_input
@staticmethod
def _compute_stuff_loss(
decoder: MultiLabelMaskQueryDecoder,
label: str,
stuff_logits: torch.Tensor,
task_input: Dict,
) -> torch.Tensor:
segment = task_input["segment"]
if (
isinstance(segment, torch.Tensor)
and segment.dim() == 2
and segment.shape[1] == 1
):
segment = segment.squeeze(1)
stuff_target = torch.zeros_like(stuff_logits)
for stuff_class in decoder.label_specs[label]["stuff_classes"]:
stuff_target[segment == int(stuff_class)] = 1.0
return F.binary_cross_entropy_with_logits(
stuff_logits, stuff_target, reduction="mean"
)
def _sync_scalar_outputs(self, return_dict: Dict) -> Dict:
return reduce_scalar_outputs_for_logging(return_dict)
[docs]
def forward(self, input_dict, return_point=False, return_decoder=False):
point = Point(input_dict)
point = self.backbone(point)
point_full = self.up_cast(point)
decoder_result = self.decoder(
Point(point_full.copy()), return_decoder=return_decoder
)
if return_decoder:
outputs_by_label, decoder_outputs = decoder_result
else:
outputs_by_label = decoder_result
decoder_outputs = None
loss = None
return_dict = {}
emit_predictions = not self.training
for label in self.labels:
task_input = None
spec = self.label_specs[label]
segment_key = self._segment_key(input_dict, spec)
has_targets = (
spec["instance_key"] in input_dict and segment_key in input_dict
)
if has_targets or self.training:
task_input = self._task_input(input_dict, label)
outputs = outputs_by_label[label]
outputs_by_label[label] = outputs
if emit_predictions:
return_dict[f"{label}_seg_logits"] = outputs["seg_logits"]
return_dict[f"{label}_pred_logits"] = outputs.get("pred_logits")
return_dict[f"{label}_pred_masks"] = outputs.get("pred_masks")
if "stuff_probs" in outputs:
return_dict[f"{label}_stuff_probs"] = outputs["stuff_probs"]
if task_input is not None:
loss_result = self.criteria_by_label[label](outputs, task_input)
if isinstance(loss_result, tuple):
task_loss, components = loss_result
else:
task_loss, components = loss_result, {}
task_loss = task_loss * self.label_specs[label]["loss_weight"]
loss = task_loss if loss is None else loss + task_loss
return_dict[f"{label}_loss"] = task_loss
for key, value in components.items():
return_dict[f"{label}_{key}"] = value
if (
self.label_specs[label]["use_stuff_head"]
and "stuff_logits" in outputs
):
stuff_loss = self._compute_stuff_loss(
self.decoder, label, outputs["stuff_logits"], task_input
)
loss = loss + stuff_loss
return_dict[f"{label}_stuff_loss"] = stuff_loss
if emit_predictions or return_point:
primary_outputs = outputs_by_label[self.eval_label]
primary_spec = self.label_specs[self.eval_label]
point_full.outputs = primary_outputs
point_full.outputs_by_label = outputs_by_label
point_full.pred_cls = primary_outputs["pred_logits"]
point_full.pred_masks = primary_outputs["pred_masks"]
point_full.pred_logits = primary_outputs["seg_logits"]
if primary_spec["instance_key"] in input_dict:
point_full.instance = input_dict[primary_spec["instance_key"]]
primary_segment_key = self._segment_key(input_dict, primary_spec)
if primary_segment_key in input_dict:
point_full.segment = input_dict[primary_segment_key]
if emit_predictions:
return_dict["outputs_by_label"] = outputs_by_label
return_dict["seg_logits_by_label"] = {
label: outputs["seg_logits"]
for label, outputs in outputs_by_label.items()
}
return_dict["pred_logits_by_label"] = {
label: outputs["pred_logits"]
for label, outputs in outputs_by_label.items()
}
return_dict["pred_masks_by_label"] = {
label: outputs["pred_masks"]
for label, outputs in outputs_by_label.items()
}
return_dict["seg_logits"] = primary_outputs["seg_logits"]
return_dict["pred_logits"] = primary_outputs.get("pred_logits")
return_dict["pred_masks"] = primary_outputs.get("pred_masks")
return_dict["stuff_probs"] = primary_outputs.get("stuff_probs")
return_dict["point_counts"] = offset2bincount(point_full.offset)
if return_decoder and decoder_outputs is not None:
return_dict["decoder"] = decoder_outputs
if (
return_decoder
and decoder_outputs is not None
and "decoder" not in return_dict
):
return_dict["decoder"] = decoder_outputs
if loss is not None:
return_dict["loss"] = loss
if return_point:
return_dict["point"] = point_full
return self._sync_scalar_outputs(return_dict)
[docs]
def postprocess(
self,
forward_output: dict,
label: str = None,
stuff_threshold: float = None,
mask_threshold: float = None,
conf_threshold: float = None,
nms_kernel: str = None,
nms_sigma: float = None,
nms_pre: int = None,
nms_max: int = None,
min_points: int = None,
background_class_label: int = None,
fill_uncovered: bool = None,
):
label = label or self.eval_label
cfg = self.postprocess_cfg.copy()
overrides = {
"stuff_threshold": stuff_threshold,
"mask_threshold": mask_threshold,
"conf_threshold": conf_threshold,
"nms_kernel": nms_kernel,
"nms_sigma": nms_sigma,
"nms_pre": nms_pre,
"nms_max": nms_max,
"min_points": min_points,
"background_class_label": background_class_label,
"fill_uncovered": fill_uncovered,
}
for key, value in overrides.items():
if value is not None:
cfg[key] = value
if "outputs_by_label" in forward_output:
task_output = forward_output["outputs_by_label"][label]
pred_masks = task_output["pred_masks"]
pred_logits = task_output["pred_logits"]
stuff_probs = task_output.get("stuff_probs")
point_counts = forward_output.get("point_counts")
elif (
"pred_masks_by_label" in forward_output
and label in forward_output["pred_masks_by_label"]
):
pred_masks = forward_output["pred_masks_by_label"][label]
pred_logits = forward_output["pred_logits_by_label"][label]
stuff_probs = None
if "stuff_probs_by_label" in forward_output:
stuff_probs = forward_output["stuff_probs_by_label"].get(label)
point_counts = forward_output.get("point_counts")
else:
pred_masks = forward_output["pred_masks"]
pred_logits = forward_output["pred_logits"]
stuff_probs = forward_output.get("stuff_probs")
point_counts = forward_output.get("point_counts")
if point_counts is None:
point_counts = forward_output.get(f"{label}_point_counts")
return postprocess_batch(
pred_masks=pred_masks,
pred_logits=pred_logits,
stuff_probs=stuff_probs,
point_counts=point_counts,
stuff_classes=self.label_specs[label]["stuff_classes"],
**cfg,
)