| | import torch |
| | from src.data import NAG, InstanceData |
| | from src.transforms import Transform |
| | from src.utils import cluster_radius_nn_graph, knn_1_graph, to_trimmed |
| | from torch_geometric.nn.pool.consecutive import consecutive_cluster |
| |
|
| |
|
| | __all__ = ['NAGPropagatePointInstances', 'OnTheFlyInstanceGraph'] |
| |
|
| |
|
| | class NAGPropagatePointInstances(Transform): |
| | """Compute the instances contained in each superpoint of each level, |
| | provided that the first level has an 'obj' attribute holding an |
| | `InstanceData`. |
| | |
| | :param strict: bool |
| | If True, will raise an exception if the level Data does not have |
| | instance |
| | """ |
| |
|
| | _IN_TYPE = NAG |
| | _OUT_TYPE = NAG |
| | _NO_REPR = ['strict'] |
| |
|
| | def __init__(self, strict=False): |
| | self.strict = strict |
| |
|
| | def _process(self, nag): |
| | |
| | obj_0 = nag[0].obj |
| | if obj_0 is None or not isinstance(obj_0, InstanceData): |
| | if not self.strict: |
| | return nag |
| | raise ValueError(f"Could not find any InstanceData in `nag[0].obj`") |
| |
|
| | for i_level in range(1, nag.num_levels): |
| | super_index = nag.get_super_index(i_level) |
| | nag[i_level].obj = obj_0.merge(super_index) |
| |
|
| | return nag |
| |
|
| |
|
| | class OnTheFlyInstanceGraph(Transform): |
| | """Compute the non-oriented graph used for instance and panoptic |
| | segmentation. |
| | |
| | We choose the following assignment rule: |
| | - each superpoint is assigned to the instance it overlaps the most |
| | |
| | Importantly, one could think of other assignment rules, such as the |
| | maximum IoU, for instance. But the latter would not work for |
| | over-segmented scenes with small superpoints and very large 'stuff' |
| | instances. We favor the overlap-size rule due to this scenario and |
| | for its simplicity. |
| | |
| | It is recommended to call this transform only AFTER all geometric |
| | transformations and sampling have been applied to the batch. This |
| | is typically important when using samplings of subgraphs, where the |
| | position of the entire clusters are maintained, even after being |
| | cropped. Such behavior may damage the instance centroid estimation |
| | step. |
| | |
| | :param level: int |
| | Partition level at which to compute the instance graph. Setting |
| | `level=-1` or `level=None` will skip the present Transform (can |
| | be useful for integrating this Transform in a pipeline and |
| | optionally skip it) |
| | :param num_classes: int |
| | Number of classes in the dataset. Specifying `num_classes` |
| | allows identifying 'void' labels. By convention, we assume |
| | `y ∈ [0, self.num_classes-1]` ARE ALL VALID LABELS (i.e. not |
| | 'ignored', 'void', 'unknown', etc), while `y < 0` AND |
| | `y >= self.num_classes` ARE VOID LABELS. Void data is dealt |
| | with following https://arxiv.org/abs/1801.00868 and |
| | https://arxiv.org/abs/1905.01220 |
| | :param adjacency_mode: str |
| | Method used to compute search for adjacent nodes. If 'available' |
| | the already-existing graph in the input's 'edge_index' will be |
| | used. If 'radius', the `radius` parameter will be used to search |
| | for all neighboring clusters with points within `radius` of each |
| | other. If 'radius-centroid', the `radius` parameter will be used |
| | to search for all neighboring clusters solely based on their |
| | centroid position. This is likely faster but less accurate than |
| | 'radius' |
| | :param k_max: int |
| | Maximum number of neighbors per cluster if `adjacency_mode` |
| | calls for it |
| | :param radius: float |
| | Radius used for neighbor search if `adjacency_mode` calls for it |
| | :param use_batch: bool |
| | If True, the 'NAG[level].batch' attribute will be used to |
| | guide neighbor search if `adjacency_mode` calls for it. More |
| | specifically, if the input NAG is a NAGBatch made up of multiple |
| | NAGs, the neighbor search will ensure that clusters from |
| | different batch items cannot be neighbors. It is recommended to |
| | keep the default `use_batch=True` |
| | :param centroid_mode: str |
| | Method used to estimate the centroids. 'iou' will weigh down |
| | the centroids of the clusters overlapping each instance by |
| | their IoU. 'ratio-product' will use the product of the size |
| | ratios of the overlap wrt the cluster and wrt the instance |
| | :param centroid_level: int |
| | Partition level to use to estimate the centroids. The purer the |
| | partition, the better the estimation. But the larger the |
| | partition, the slower the computation |
| | :param smooth_affinity: bool |
| | If True, the affinity score computed for each edge will |
| | follow the 'smooth' formulation: |
| | `(overlap_i_obj_j / size_i + overlap_j_obj_i / size_j) / 2` |
| | for the edge `(i, j)`, where `obj_i` designates the target |
| | instance of `i`. If False, the affinity will be computed |
| | with the simpler formulation: `obj_i == obj_j` |
| | """ |
| |
|
| | _IN_TYPE = NAG |
| | _OUT_TYPE = NAG |
| | _ADJACENCY_MODES = ['available', 'radius', 'radius-centroid'] |
| | _CENTROID_MODES = ['iou', 'ratio-product'] |
| |
|
| | def __init__( |
| | self, |
| | level=1, |
| | num_classes=None, |
| | adjacency_mode='radius', |
| | k_max=30, |
| | radius=1, |
| | use_batch=True, |
| | centroid_mode='iou', |
| | centroid_level=1, |
| | smooth_affinity=True): |
| | assert adjacency_mode.lower() in self._ADJACENCY_MODES, \ |
| | f"Expected 'mode' to be one of {self._ADJACENCY_MODES}" |
| | assert centroid_mode.lower() in self._CENTROID_MODES, \ |
| | f"Expected 'mode' to be one of {self._CENTROID_MODES}" |
| | self.level = level |
| | self.num_classes = num_classes |
| | self.adjacency_mode = adjacency_mode.lower() |
| | self.k_max = k_max |
| | self.radius = radius |
| | self.use_batch = use_batch |
| | self.centroid_mode = centroid_mode.lower() |
| | self.centroid_level = centroid_level |
| | self.smooth_affinity = smooth_affinity |
| |
|
| | def _process(self, nag): |
| | |
| | |
| | if self.level is None or self.level < 0: |
| | return nag |
| |
|
| | data = nag[self.level] |
| |
|
| | |
| | |
| | |
| | if self.adjacency_mode == 'available': |
| | obj_edge_index = data.edge_index |
| |
|
| | |
| | |
| | elif self.adjacency_mode == 'radius': |
| | |
| | super_index = nag.get_super_index(self.level, low=0) |
| | obj_edge_index, _ = cluster_radius_nn_graph( |
| | nag[0].pos, |
| | super_index, |
| | k_max=self.k_max, |
| | gap=self.radius, |
| | batch=nag[self.level].batch if self.use_batch else None) |
| |
|
| | |
| | elif self.adjacency_mode == 'radius-centroid': |
| | obj_edge_index, _ = knn_1_graph( |
| | nag[self.level].pos, |
| | self.k_max, |
| | r_max=self.radius, |
| | batch=nag[self.level].batch if self.use_batch else None) |
| |
|
| | else: |
| | raise NotImplementedError |
| |
|
| | |
| | |
| | if obj_edge_index is None: |
| | obj_edge_index = torch.empty( |
| | 2, 0, dtype=torch.long, device=data.device) |
| |
|
| | |
| | |
| | |
| | if data.obj is None: |
| | data.obj_edge_index = to_trimmed(obj_edge_index) |
| | data.obj_edge_affinity = None |
| | data.obj_pos = None |
| | nag._list[self.level] = data |
| | return nag |
| |
|
| | |
| | data.obj_edge_index, data.obj_edge_affinity = data.obj.instance_graph( |
| | obj_edge_index, |
| | num_classes=self.num_classes, |
| | smooth_affinity=self.smooth_affinity) |
| |
|
| | |
| | |
| | i_level = min(self.centroid_level, nag.num_levels - 1) |
| | obj_pos, obj_idx = nag[i_level].estimate_instance_centroid( |
| | mode=self.centroid_mode) |
| |
|
| | |
| | |
| | sp_obj_idx = data.obj.major(num_classes=self.num_classes)[0] |
| |
|
| | |
| | |
| | |
| | |
| | |
| | joint_obj_idx = torch.cat((sp_obj_idx, obj_idx)) |
| | joint_obj_idx_consec = consecutive_cluster(joint_obj_idx)[0] |
| | sp_obj_idx_consec = joint_obj_idx_consec[:sp_obj_idx.numel()] |
| | data.obj_pos = obj_pos[sp_obj_idx_consec] |
| |
|
| | |
| | nag._list[self.level] = data |
| |
|
| | return nag |
| |
|