| | import sys |
| | import hydra |
| | import torch |
| | import numpy as np |
| | import pandas as pd |
| | import os.path as osp |
| | from tqdm import tqdm |
| | from copy import deepcopy |
| | from itertools import product |
| | import matplotlib.pyplot as plt |
| | import matplotlib.patches as patches |
| | import matplotlib.cm as cm |
| | from torch.nn.functional import one_hot |
| | from torch_geometric.nn.pool.consecutive import consecutive_cluster |
| | from src.utils.hydra import init_config |
| | from src.utils.neighbors import knn_2 |
| | from src.utils.graph import to_trimmed |
| | from src.utils.cpu import available_cpu_count |
| | from src.utils.scatter import scatter_mean_weighted |
| | from src.utils.semantic import _set_attribute_preserving_transforms |
| |
|
| |
|
| | src_folder = osp.dirname(osp.dirname(osp.abspath(__file__))) |
| | sys.path.append(src_folder) |
| | sys.path.append(osp.join(src_folder, "dependencies/grid_graph/python/bin")) |
| | sys.path.append(osp.join(src_folder, "dependencies/parallel_cut_pursuit/python/wrappers")) |
| |
|
| |
|
| | from grid_graph import edge_list_to_forward_star |
| | from cp_d0_dist import cp_d0_dist |
| |
|
| |
|
| | __all__ = [ |
| | 'generate_random_bbox_data', 'generate_random_segment_data', |
| | 'instance_cut_pursuit', 'oracle_superpoint_clustering', 'get_stuff_mask', |
| | 'compute_panoptic_metrics', 'compute_panoptic_metrics_s3dis_6fold', |
| | 'grid_search_panoptic_partition'] |
| |
|
| |
|
| | _MAX_NUM_EDGES = 4294967295 |
| |
|
| |
|
| | def generate_random_bbox_data( |
| | num_img=1, |
| | num_classes=1, |
| | height=128, |
| | width=128, |
| | h_split=1, |
| | w_split=2, |
| | det_gt_ratio=1): |
| | |
| | instance_images = -torch.ones(num_img, height, width, dtype=torch.long) |
| | label_images = -torch.ones(num_img, height, width, dtype=torch.long) |
| |
|
| | h_gt = height // h_split |
| | w_gt = width // w_split |
| |
|
| | gt_boxes = torch.zeros(num_img * h_split * w_split, 4) |
| | gt_labels = torch.randint(0, num_classes, (num_img * h_split * w_split,)) |
| | iterator = product(range(num_img), range(h_split), range(w_split)) |
| | for idx, (i_img, i, j) in enumerate(iterator): |
| | h1 = i * h_gt |
| | h2 = (i + 1) * h_gt |
| | w1 = j * w_gt |
| | w2 = (j + 1) * w_gt |
| | instance_images[i_img, h1:h2, w1:w2] = idx |
| | label_images[i_img, h1:h2, w1:w2] = gt_labels[idx] |
| | gt_boxes[idx] = torch.tensor([h1, w1, h2, w2]) |
| |
|
| | |
| | num_gt = (instance_images.max() + 1).item() |
| | num_det = int(num_gt * det_gt_ratio) |
| |
|
| | i_center_det = torch.randint(0, height, (num_det,)) |
| | j_center_det = torch.randint(0, width, (num_det,)) |
| | h_det = torch.randint(int(h_gt * 0.7), int(h_gt * 1.3), (num_det,)) |
| | w_det = torch.randint(int(w_gt * 0.7), int(w_gt * 1.3), (num_det,)) |
| |
|
| | det_boxes = torch.vstack([ |
| | (i_center_det - h_det / 2).clamp(min=0), |
| | (j_center_det - w_det / 2).clamp(min=0), |
| | (i_center_det + h_det / 2).clamp(max=height), |
| | (j_center_det + w_det / 2).clamp(max=width)]).T.round() |
| | det_img_idx = torch.randint(0, num_img, (num_det,)) |
| | det_labels = torch.randint(0, num_classes, (num_det,)) |
| | det_scores = torch.rand(num_det) |
| |
|
| | |
| | |
| | fig, ax = plt.subplots() |
| | ax.imshow(instance_images.view(-1, width), cmap='jet') |
| | for idx_det in range(num_det): |
| | i = det_boxes[idx_det, 0] + det_img_idx[idx_det] * height |
| | j = det_boxes[idx_det, 1] |
| | h = det_boxes[idx_det, 2] - det_boxes[idx_det, 0] |
| | w = det_boxes[idx_det, 3] - det_boxes[idx_det, 1] |
| | rect = patches.Rectangle( |
| | (j, i), |
| | w, |
| | h, |
| | linewidth=3, |
| | edgecolor=cm.nipy_spectral(idx_det / num_det), |
| | facecolor='none') |
| | ax.add_patch(rect) |
| | plt.show() |
| |
|
| | |
| | |
| | fig, ax = plt.subplots() |
| | ax.imshow(label_images.view(-1, width).float() / num_classes, cmap='jet') |
| | for idx_det in range(num_det): |
| | i = det_boxes[idx_det, 0] + det_img_idx[idx_det] * height |
| | j = det_boxes[idx_det, 1] |
| | h = det_boxes[idx_det, 2] - det_boxes[idx_det, 0] |
| | w = det_boxes[idx_det, 3] - det_boxes[idx_det, 1] |
| | c = cm.nipy_spectral(det_labels[idx_det].float().item() / num_classes) |
| | rect = patches.Rectangle( |
| | (j, i), |
| | w, |
| | h, |
| | linewidth=3, |
| | edgecolor=c, |
| | facecolor='none') |
| | ax.add_patch(rect) |
| | plt.show() |
| |
|
| | |
| | iterator = zip(gt_boxes.view(num_img, -1, 4), gt_labels.view(num_img, -1)) |
| | targets = [ |
| | dict(boxes=boxes, labels=labels) |
| | for boxes, labels in iterator] |
| |
|
| | preds = [ |
| | dict( |
| | boxes=det_boxes[det_img_idx == i_img], |
| | labels=det_labels[det_img_idx == i_img], |
| | scores=det_scores[det_img_idx == i_img]) |
| | for i_img in range(num_img)] |
| |
|
| | |
| | |
| | |
| | |
| | |
| | pred_idx = [] |
| | gt_idx = [] |
| | gt_y = [] |
| | for idx_det in range(num_det): |
| | i_img = det_img_idx[idx_det] |
| | x1, y1, x2, y2 = det_boxes[idx_det].long() |
| | num_points = (x2 - x1) * (y2 - y1) |
| | pred_idx.append(torch.full((num_points,), idx_det)) |
| | gt_idx.append(instance_images[i_img, x1:x2, y1:y2].flatten()) |
| | gt_y.append(label_images[i_img, x1:x2, y1:y2].flatten()) |
| | pred_idx = torch.cat(pred_idx) |
| | gt_idx = torch.cat(gt_idx) |
| | gt_y = torch.cat(gt_y) |
| | count = torch.ones_like(pred_idx) |
| |
|
| | from src.data.instance import InstanceData |
| | instance_data = InstanceData(pred_idx, gt_idx, count, gt_y, dense=True) |
| |
|
| | return targets, preds, gt_idx, gt_y, count, instance_data |
| |
|
| |
|
| | def generate_single_random_segment_image( |
| | num_gt=10, |
| | num_pred=12, |
| | num_classes=3, |
| | height=32, |
| | width=64, |
| | shift=5, |
| | random_pred_label=False, |
| | show=True, |
| | iterations=20): |
| | """Generate an image with random ground truth and predicted instance |
| | and semantic segmentation data. To make the images realisitc, and to |
| | ensure that the instances form a PARTITION of the image, we rely on |
| | voronoi cells. Besides, to encourage a realistic overalp between the |
| | predicted and target instances, the predcition cell centers are |
| | sampled near the target samples. |
| | """ |
| | |
| | |
| | |
| | |
| | x = torch.randint(0, height, (num_gt,)) |
| | y = torch.randint(0, width, (num_gt,)) |
| | gt_xy = torch.vstack((x, y)).T |
| | if num_pred <= num_gt: |
| | idx_ref_gt = torch.from_numpy( |
| | np.random.choice(num_gt, num_pred, replace=False)) |
| | else: |
| | idx_ref_gt = torch.from_numpy( |
| | np.random.choice(num_gt, num_pred % num_gt, replace=False)) |
| | idx_ref_gt = torch.cat(( |
| | torch.arange(num_gt).repeat(num_pred // num_gt), idx_ref_gt)) |
| | xy_shift = torch.randint(0, 2 * shift, (num_pred, 2)) - shift |
| | pred_xy = gt_xy[idx_ref_gt] + xy_shift |
| | clamp_min = torch.tensor([0, 0]) |
| | clamp_max = torch.tensor([height, width]) |
| | pred_xy = pred_xy.clamp(min=clamp_min, max=clamp_max) |
| |
|
| | |
| | |
| | |
| | already_used_xy_ids = [] |
| | for i_pred, xy in enumerate(pred_xy): |
| | xy_id = xy[0] * width + xy[1] |
| | count = 0 |
| |
|
| | while xy_id in already_used_xy_ids and count < iterations: |
| | xy_shift = torch.randint(0, 2 * shift, (2,)) - shift |
| | xy = gt_xy[idx_ref_gt[i_pred]] + xy_shift |
| | xy = xy.clamp(min=clamp_min, max=clamp_max) |
| | xy_id = xy[0] * width + xy[1] |
| | count += 1 |
| |
|
| | if count == iterations: |
| | raise ValueError( |
| | f"Reached max iterations={iterations} while resampling " |
| | "duplicate prediction centers") |
| |
|
| | already_used_xy_ids.append(xy_id) |
| | pred_xy[i_pred] = xy |
| |
|
| | |
| | gt_labels = torch.randint(0, num_classes, (num_gt,)) |
| | if random_pred_label: |
| | pred_labels = torch.randint(0, num_classes, (num_pred,)) |
| | else: |
| | pred_labels = gt_labels[idx_ref_gt] |
| | pred_scores = torch.rand(num_pred) |
| |
|
| | |
| | |
| | |
| | x, y = torch.meshgrid( |
| | torch.arange(height), torch.arange(width), indexing='ij') |
| | x = x.flatten() |
| | y = y.flatten() |
| | z = torch.zeros_like(x) |
| | xyz = torch.vstack((x, y, z)).T |
| |
|
| | |
| | |
| | gt_xyz = torch.cat((gt_xy, torch.zeros_like(gt_xy[:, [0]])), dim=1).float() |
| | gt_nn = knn_2(gt_xyz, xyz.float(), 1, r_max=max(width, height))[0] |
| | gt_seg_image = gt_nn.view(height, width) |
| | gt_label_image = gt_labels[gt_seg_image] |
| |
|
| | |
| | |
| | pred_xyz = torch.cat((pred_xy, torch.zeros_like(pred_xy[:, [0]])), dim=1).float() |
| | pred_nn = knn_2(pred_xyz, xyz.float(), 1, r_max=max(width, height))[0] |
| | pred_seg_image = pred_nn.view(height, width) |
| | pred_label_image = pred_labels[pred_seg_image] |
| |
|
| | |
| | if show: |
| | plt.subplot(2, 2, 1) |
| | plt.title('Ground truth instances') |
| | plt.imshow(gt_seg_image) |
| | plt.subplot(2, 2, 2) |
| | plt.title('Predicted instances') |
| | plt.imshow(pred_seg_image) |
| | plt.subplot(2, 2, 3) |
| | plt.title('Ground truth labels') |
| | plt.imshow(gt_label_image) |
| | plt.subplot(2, 2, 4) |
| | plt.title('Predicted labels') |
| | plt.imshow(pred_label_image) |
| | plt.show() |
| |
|
| | |
| | tm_targets = dict( |
| | masks=torch.stack([gt_seg_image == i_gt for i_gt in range(num_gt)]), |
| | labels=gt_labels) |
| |
|
| | tm_preds = dict( |
| | masks=torch.stack([pred_seg_image == i_pred for i_pred in range(num_pred)]), |
| | labels=pred_labels, |
| | scores=pred_scores) |
| |
|
| | tm_data = (tm_preds, tm_targets) |
| |
|
| | |
| | pred_idx = pred_seg_image.flatten() |
| | gt_idx = gt_seg_image.flatten() |
| | gt_y = gt_label_image.flatten() |
| | count = torch.ones_like(pred_idx) |
| |
|
| | from src.data.instance import InstanceData |
| | instance_data = InstanceData(pred_idx, gt_idx, count, gt_y, dense=True) |
| | spt_data = (pred_scores, pred_labels, instance_data) |
| |
|
| | return tm_data, spt_data |
| |
|
| |
|
| | def generate_random_segment_data( |
| | num_img=2, |
| | num_gt_per_img=10, |
| | num_pred_per_img=14, |
| | num_classes=2, |
| | height=32, |
| | width=64, |
| | shift=5, |
| | random_pred_label=False, |
| | verbose=True): |
| | """Generate multiple images with random ground truth and predicted |
| | instance and semantic segmentation data. To make the images |
| | realistic, and to ensure that the instances form a PARTITION of the |
| | image, we rely on voronoi cells. Besides, to encourage a realistic |
| | overlap between the predicted and target instances, the prediction |
| | cell centers are sampled near the target samples. |
| | """ |
| | tm_data = [] |
| | spt_data = [] |
| |
|
| | for i_img in range(num_img): |
| | if verbose: |
| | print(f"\nImage {i_img + 1}/{num_img}") |
| | tm_data_, spt_data_ = generate_single_random_segment_image( |
| | num_gt=num_gt_per_img, |
| | num_pred=num_pred_per_img, |
| | num_classes=num_classes, |
| | height=height, |
| | width=width, |
| | shift=shift, |
| | random_pred_label=random_pred_label, |
| | show=verbose) |
| | tm_data.append(tm_data_) |
| | spt_data.append(spt_data_) |
| |
|
| | return tm_data, spt_data |
| |
|
| |
|
| | def _instance_cut_pursuit( |
| | node_x, |
| | node_logits, |
| | node_size, |
| | edge_index, |
| | edge_affinity_logits, |
| | loss_type='l2_kl', |
| | regularization=1e-2, |
| | x_weight=1, |
| | p_weight=1, |
| | cutoff=1, |
| | parallel=True, |
| | iterations=10, |
| | trim=False, |
| | discrepancy_epsilon=1e-4, |
| | temperature=1, |
| | dampening=0, |
| | verbose=False): |
| | """Partition an instance graph using cut-pursuit. |
| | |
| | :param node_x: Tensor of shape [num_nodes, num_dim] |
| | Node features |
| | :param node_logits: Tensor of shape [num_nodes, num_classes] |
| | Predicted classification logits for each node |
| | :param node_size: Tensor of shape [num_nodes] |
| | Size of each node |
| | :param edge_index: Tensor of shape [2, num_edges] |
| | Edges of the graph, in torch-geometric's format |
| | :param edge_affinity_logits: Tensor of shape [num_edges] |
| | Predicted affinity logits (ie in R+, before sigmoid) of each |
| | edge |
| | :param loss_type: str |
| | Rules the loss applied on the node features. Accepts one of |
| | 'l2' (L2 loss on node features and probabilities), |
| | 'l2_kl' (L2 loss on node features and Kullback-Leibler |
| | divergence on node probabilities) |
| | :param regularization: float |
| | Regularization parameter for the partition |
| | :param x_weight: float |
| | Weight used to mitigate the impact of the node position in the |
| | partition. The larger, the lesser features importance before |
| | the probabilities |
| | :param p_weight: float |
| | Weight used to mitigate the impact of the node probabilities in |
| | the partition. The larger, the lesser features importance before |
| | the features |
| | :param cutoff: float |
| | Minimum number of points in each cluster |
| | :param parallel: bool |
| | Whether cut-pursuit should run in parallel |
| | :param iterations: int |
| | Maximum number of iterations for each partition |
| | :param trim: bool |
| | Whether the input graph should be trimmed. See `to_trimmed()` |
| | documentation for more details on this operation |
| | :param discrepancy_epsilon: float |
| | Mitigates the maximum discrepancy. More precisely: |
| | `affinity=1 ⇒ discrepancy=1/discrepancy_epsilon` |
| | :param temperature: float |
| | Temperature used in the softmax when converting node logits to |
| | probabilities |
| | :param dampening: float |
| | Dampening applied to the node probabilities to mitigate the |
| | impact of near-zero probabilities in the Kullback-Leibler |
| | divergence |
| | :param verbose: bool |
| | :return: |
| | """ |
| |
|
| | |
| | assert node_x.dim() == 2, \ |
| | "`node_x` must have shape `[num_nodes, num_dim]`" |
| | assert node_logits.dim() == 2, \ |
| | "`node_logits` must have shape `[num_nodes, num_classes]`" |
| | assert node_logits.shape[0] == node_x.shape[0], \ |
| | "`node_logits` and `node_x` must have the same number of points" |
| | assert node_size.dim() == 1, \ |
| | "`node_size` must have shape `[num_nodes]`" |
| | assert node_size.shape[0] == node_x.shape[0], \ |
| | "`node_size` and `node_x` must have the same number of points" |
| | assert edge_index.dim() == 2 and edge_index.shape[0] == 2, \ |
| | "`edge_index` must be of shape `[2, num_edges]`" |
| | edge_affinity_logits = edge_affinity_logits.squeeze() |
| | assert edge_affinity_logits.dim() == 1, \ |
| | "`edge_affinity_logits` must be of shape `[num_edges]`" |
| | assert edge_affinity_logits.shape[0] == edge_index.shape[1], \ |
| | "`edge_affinity_logits` and `edge_index` must have the same number " \ |
| | "of edges" |
| | loss_type = loss_type.lower() |
| | assert loss_type in ['l2', 'l2_kl'], \ |
| | "`loss_type` must be one of ['l2', 'l2_kl']" |
| | assert 0 < discrepancy_epsilon, \ |
| | "`discrepancy_epsilon` must be strictly positive" |
| | assert 0 < temperature, "`temperature` must be strictly positive" |
| | assert 0 <= dampening <= 1, "`dampening` must be in [0, 1]" |
| |
|
| | device = node_x.device |
| | num_nodes = node_x.shape[0] |
| | x_dim = node_x.shape[1] |
| | p_dim = node_logits.shape[1] |
| | dim = x_dim + p_dim |
| | num_edges = edge_affinity_logits.numel() |
| |
|
| | assert num_nodes < np.iinfo(np.uint32).max, \ |
| | "Too many nodes for `uint32` indices" |
| | assert num_edges < np.iinfo(np.uint32).max, \ |
| | "Too many edges for `uint32` indices" |
| |
|
| | |
| | num_threads = available_cpu_count() if parallel else 1 |
| |
|
| | |
| | if num_nodes < 2: |
| | return torch.zeros(num_nodes, dtype=torch.long, device=device) |
| |
|
| | |
| | if trim: |
| | edge_index, edge_affinity_logits = to_trimmed( |
| | edge_index, edge_attr=edge_affinity_logits, reduce='mean') |
| |
|
| | if verbose: |
| | print( |
| | f'Launching instance partition reg={regularization}, ' |
| | f'cutoff={cutoff}') |
| |
|
| | |
| | if num_edges > _MAX_NUM_EDGES and verbose: |
| | print( |
| | f"WARNING: number of edges {num_edges} exceeds the uint32 limit " |
| | f"{_MAX_NUM_EDGES}. Please update the cut-pursuit source code to " |
| | f"accept a larger data type for `index_t`.") |
| |
|
| | |
| | edge_affinity = edge_affinity_logits.sigmoid() |
| | edge_discrepancy = edge_affinity / (1 - edge_affinity + discrepancy_epsilon) |
| |
|
| | |
| | source_csr, target, reindex = edge_list_to_forward_star( |
| | num_nodes, edge_index.T.contiguous().cpu().numpy()) |
| | source_csr = source_csr.astype('uint32') |
| | target = target.astype('uint32') |
| | edge_weights = edge_discrepancy.cpu().numpy()[reindex] * regularization \ |
| | if edge_discrepancy is not None else regularization |
| |
|
| | |
| | node_probas = torch.nn.functional.softmax(node_logits / temperature, dim=1) |
| |
|
| | |
| | |
| | |
| | |
| | num_classes = node_probas.shape[1] |
| | node_probas = (1 - dampening) * node_probas + dampening / num_classes |
| |
|
| | |
| | |
| | |
| | node_x = node_x - node_x.mean(dim=0).view(1, -1) |
| |
|
| | |
| | |
| | x = torch.cat((node_x, node_probas), dim=1) |
| | x = np.asfortranarray(x.cpu().numpy().T) |
| | node_size = node_size.float().cpu().numpy() |
| |
|
| | |
| | |
| | |
| | l2_dim = dim if loss_type == 'l2' else x_dim |
| |
|
| | |
| | coor_weights_dim = dim if loss_type == 'l2' else x_dim + 1 |
| | coor_weights = np.ones(coor_weights_dim, dtype=np.float32) |
| | coor_weights[:x_dim] *= x_weight |
| | coor_weights[x_dim:] *= p_weight |
| |
|
| | |
| | obj_index, x_c, cluster, edges, times = cp_d0_dist( |
| | l2_dim, |
| | x, |
| | source_csr, |
| | target, |
| | edge_weights=edge_weights, |
| | vert_weights=node_size, |
| | coor_weights=coor_weights, |
| | min_comp_weight=cutoff, |
| | cp_dif_tol=1e-2, |
| | K=4, |
| | cp_it_max=iterations, |
| | split_damp_ratio=0.7, |
| | verbose=verbose, |
| | max_num_threads=num_threads, |
| | balance_parallel_split=True, |
| | compute_Time=True, |
| | compute_List=True, |
| | compute_Graph=True) |
| |
|
| | if verbose: |
| | delta_t = (times[1:] - times[:-1]).round(2) |
| | print(f'Instance partition times: {delta_t}') |
| |
|
| | |
| | obj_index = torch.from_numpy(obj_index.astype('int64')).to(device) |
| |
|
| | return obj_index |
| |
|
| |
|
| | def instance_cut_pursuit( |
| | batch, |
| | node_x, |
| | node_logits, |
| | stuff_classes, |
| | node_size, |
| | edge_index, |
| | edge_affinity_logits, |
| | loss_type='l2_kl', |
| | regularization=1e-2, |
| | x_weight=1, |
| | p_weight=1, |
| | cutoff=1, |
| | parallel=True, |
| | iterations=10, |
| | trim=False, |
| | discrepancy_epsilon=1e-4, |
| | temperature=1, |
| | dampening=0, |
| | verbose=False): |
| | """The forward step will compute the partition on the instance |
| | graph, based on the node features, node logits, and edge |
| | affinities. The partition segments will then be further merged |
| | so that there is at most one instance of each stuff class per |
| | batch item (ie per scene). |
| | |
| | :param batch: Tensor of shape [num_nodes] |
| | Batch index of each node |
| | :param node_x: Tensor of shape [num_nodes, num_dim] |
| | Predicted node embeddings |
| | :param node_logits: Tensor of shape [num_nodes, num_classes] |
| | Predicted classification logits for each node |
| | :param stuff_classes: List or Tensor |
| | List of 'stuff' class labels. These are used for merging |
| | stuff segments together to ensure there is at most one |
| | predicted instance of each 'stuff' class per batch item |
| | :param node_size: Tensor of shape [num_nodes] |
| | Size of each node |
| | :param edge_index: Tensor of shape [2, num_edges] |
| | Edges of the graph, in torch-geometric's format |
| | :param edge_affinity_logits: Tensor of shape [num_edges] |
| | Predicted affinity logits (ie in R+, before sigmoid) of each |
| | edge |
| | :param loss_type: str |
| | Rules the loss applied on the node features. Accepts one of |
| | 'l2' (L2 loss on node features and probabilities), |
| | 'l2_kl' (L2 loss on node features and Kullback-Leibler |
| | divergence on node probabilities) |
| | :param regularization: float |
| | Regularization parameter for the partition |
| | :param x_weight: float |
| | Weight used to mitigate the impact of the node position in the |
| | partition. The larger, the lesser features importance before |
| | the probabilities |
| | :param p_weight: float |
| | Weight used to mitigate the impact of the node probabilities in |
| | the partition. The larger, the lesser features importance before |
| | the features |
| | :param cutoff: float |
| | Minimum number of points in each cluster |
| | :param parallel: bool |
| | Whether cut-pursuit should run in parallel |
| | :param iterations: int |
| | Maximum number of iterations for each partition |
| | :param trim: bool |
| | Whether the input graph should be trimmed. See `to_trimmed()` |
| | documentation for more details on this operation |
| | :param discrepancy_epsilon: float |
| | Mitigates the maximum discrepancy. More precisely: |
| | `affinity=1 ⇒ discrepancy=1/discrepancy_epsilon` |
| | :param temperature: float |
| | Temperature used in the softmax when converting node logits to |
| | probabilities |
| | :param dampening: float |
| | Dampening applied to the node probabilities to mitigate the |
| | impact of near-zero probabilities in the Kullback-Leibler |
| | divergence |
| | :param verbose: bool |
| | |
| | :return: obj_index: Tensor of shape [num_nodes] |
| | Indicates which predicted instance each node belongs to |
| | """ |
| |
|
| | |
| | |
| | obj_index = _instance_cut_pursuit( |
| | node_x, |
| | node_logits, |
| | node_size, |
| | edge_index, |
| | edge_affinity_logits, |
| | loss_type=loss_type, |
| | regularization=regularization, |
| | x_weight=x_weight, |
| | p_weight=p_weight, |
| | cutoff=cutoff, |
| | parallel=parallel, |
| | iterations=iterations, |
| | trim=trim, |
| | discrepancy_epsilon=discrepancy_epsilon, |
| | temperature=temperature, |
| | dampening=dampening, |
| | verbose=verbose) |
| |
|
| | |
| | |
| | obj_logits = scatter_mean_weighted(node_logits, obj_index, node_size) |
| | obj_y = obj_logits.argmax(dim=1) |
| |
|
| | |
| | |
| | |
| | obj_is_stuff = get_stuff_mask(obj_y, stuff_classes) |
| |
|
| | |
| | node_obj_y = obj_y[obj_index] |
| | node_is_stuff = obj_is_stuff[obj_index] |
| |
|
| | |
| | |
| | |
| | |
| | |
| | batch = batch if batch is not None else torch.zeros_like(obj_index) |
| | num_batch_items = batch.max() + 1 |
| | final_obj_index = obj_index.clone() |
| | final_obj_index[node_is_stuff] = \ |
| | obj_index.max() + 1 \ |
| | + node_obj_y[node_is_stuff] * num_batch_items \ |
| | + batch[node_is_stuff] |
| | final_obj_index, perm = consecutive_cluster(final_obj_index) |
| |
|
| | return final_obj_index |
| |
|
| |
|
| | def oracle_superpoint_clustering( |
| | nag, |
| | num_classes, |
| | stuff_classes, |
| | mode='pas', |
| | graph_kwargs=None, |
| | partition_kwargs=None): |
| | """Compute an oracle for superpoint clustering for instance and |
| | panoptic segmentation. This is a proxy for the highest achievable |
| | graph clustering performance with the superpoint partition at hand |
| | and the input clustering parameters. |
| | |
| | The output `InstanceData` can then be used to compute final |
| | segmentation metrics using: |
| | - `InstanceData.semantic_segmentation_oracle()` |
| | - `InstanceData.instance_segmentation_oracle()` |
| | - `InstanceData.panoptic_segmentation_oracle()` |
| | |
| | More precisely, for the optimal superpoint clustering: |
| | - build the instance graph on the input `NAG` `level`-partition |
| | - for each edge, the oracle perfectly predicts the affinity |
| | - for each node, the oracle perfectly predicts the offset |
| | - for each node, the oracle predicts the dominant label from its |
| | label histogram (excluding the 'void' label) |
| | - partition the instance graph using the oracle edge affinities, |
| | node offsets and node classes |
| | - merge superpoints if they are assigned to the same object |
| | - merge 'stuff' predictions together, so that there is at most 1 |
| | prediction of each 'stuff' class per batch item |
| | |
| | :param nag: NAG object |
| | :param num_classes: int |
| | Number of classes in the dataset, allows differentiating between |
| | valid and void classes |
| | :param stuff_classes: List[int] |
| | List of labels for 'stuff' classes |
| | :param mode: str |
| | String characterizing whether edge affinities, node semantics, |
| | positions and offsets should be used in the graph clustering. |
| | 'p': use node position. |
| | 'o': use oracle offset. |
| | 'a': use oracle edge affinities. |
| | 's': use oracle node semantics. |
| | In contrast, not setting 'p', nor 'o' is equivalent to setting |
| | all nodes positions and offsets to 0. |
| | Similarly, not setting 'a' will set the same weight to all the |
| | edges. |
| | Finally, not setting 's' will set the same class to all the |
| | nodes. |
| | :param graph_kwargs: dict |
| | Dictionary of kwargs to be passed to the graph constructor |
| | `OnTheFlyInstanceGraph()` |
| | :param partition_kwargs: dict |
| | Dictionary of kwargs to be passed to the partition function |
| | `instance_cut_pursuit()` |
| | :return: |
| | """ |
| |
|
| | |
| |
|
| | |
| | from src.transforms import OnTheFlyInstanceGraph |
| | from src.models.panoptic import PanopticSegmentationOutput |
| | from src.metrics import PanopticQuality3D |
| |
|
| | |
| | graph_kwargs = {} if graph_kwargs is None else graph_kwargs |
| | graph_kwargs = dict(graph_kwargs, **dict(level=1, num_classes=num_classes)) |
| | nag = OnTheFlyInstanceGraph(**graph_kwargs)(nag) |
| |
|
| | |
| | node_y = nag[1].y[:, :num_classes].argmax(dim=1) |
| | node_size = nag.get_sub_size(1) |
| | edge_index = nag[1].obj_edge_index |
| |
|
| | |
| | |
| | |
| | |
| | |
| | node_logits = one_hot(node_y, num_classes=num_classes).float() * 100 |
| |
|
| | |
| | |
| | if 's' not in mode.lower(): |
| | partition_kwargs['p_weight'] = 0 |
| |
|
| | |
| | |
| | edge_affinity_logits = torch.special.logit(nag[1].obj_edge_affinity) \ |
| | if 'a' in mode.lower() \ |
| | else torch.zeros(edge_index.shape[1], device=nag.device) |
| |
|
| | |
| | |
| | |
| | if 'o' in mode.lower(): |
| | node_x = nag[1].obj_pos |
| | is_stuff = get_stuff_mask(node_y, stuff_classes) |
| | node_x[is_stuff] = nag[1].pos[is_stuff] |
| |
|
| | |
| | elif 'p' in mode.lower(): |
| | node_x = nag[1].pos |
| |
|
| | |
| | else: |
| | partition_kwargs['x_weight'] = 0 |
| | node_x = nag[1].pos * 0 |
| |
|
| | |
| | batch = nag[1].batch if nag[1].batch is not None \ |
| | else torch.zeros(nag[1].num_nodes, dtype=torch.long, device=nag.device) |
| |
|
| | |
| | partition_kwargs = {} if partition_kwargs is None else partition_kwargs |
| | obj_index = instance_cut_pursuit( |
| | batch, |
| | node_x, |
| | node_logits, |
| | stuff_classes, |
| | node_size, |
| | edge_index, |
| | edge_affinity_logits, |
| | **partition_kwargs) |
| |
|
| | |
| | output = PanopticSegmentationOutput( |
| | node_logits, |
| | stuff_classes, |
| | edge_affinity_logits, |
| | |
| | node_size) |
| |
|
| | |
| | output.obj_edge_index = getattr(nag[1], 'obj_edge_index', None) |
| | output.obj_edge_affinity = getattr(nag[1], 'obj_edge_affinity', None) |
| | output.pos = nag[1].pos |
| | output.obj_pos = getattr(nag[1], 'obj_pos', None) |
| | output.obj = nag[1].obj |
| | output.y_hist = nag[1].y |
| | output.obj_index_pred = obj_index |
| |
|
| | |
| | panoptic_metrics = PanopticQuality3D( |
| | num_classes, |
| | ignore_unseen_classes=True, |
| | stuff_classes=stuff_classes, |
| | compute_on_cpu=True) |
| |
|
| | |
| | |
| | obj_score, obj_y, instance_data = output.panoptic_pred() |
| |
|
| | |
| | panoptic_metrics.update(obj_y, instance_data.cpu()) |
| | results = panoptic_metrics.compute() |
| |
|
| | return results |
| |
|
| |
|
| | def get_stuff_mask(y, stuff_classes): |
| | """Helper function producing a boolean mask of size `y.shape[0]` |
| | indicating which of the `y` (labels if 1D or logits/probabilities if |
| | 2D) are among the `stuff_classes`. |
| | """ |
| | |
| | labels = y.long() if y.dim() == 1 else y.argmax(dim=1) |
| |
|
| | |
| | stuff_classes = torch.as_tensor( |
| | stuff_classes, dtype=labels.dtype, device=labels.device) |
| | return torch.isin(labels, stuff_classes) |
| |
|
| |
|
| | def compute_panoptic_metrics( |
| | model, |
| | datamodule, |
| | stage='val', |
| | graph_kwargs=None, |
| | partition_kwargs=None, |
| | verbose=True): |
| | """Helper function to compute the semantic, instance, panoptic |
| | segmentation metrics of a model on a given dataset, for given |
| | instance graph and partition parameters. |
| | """ |
| | |
| | from src.data import NAGBatch |
| |
|
| | |
| | |
| | |
| | if stage == 'train': |
| | dataset = datamodule.train_dataset |
| | dataloader = datamodule.train_dataloader() |
| | elif stage == 'val': |
| | dataset = datamodule.val_dataset |
| | dataloader = datamodule.val_dataloader() |
| | elif stage == 'test': |
| | dataset = datamodule.test_dataset |
| | dataloader = datamodule.test_dataloader() |
| | else: |
| | raise ValueError(f"Unknown stage : {stage}") |
| |
|
| | |
| | |
| | dataset = _set_attribute_preserving_transforms(dataset) |
| |
|
| | |
| | dataset = _set_graph_construction_parameters(dataset, graph_kwargs) |
| |
|
| | |
| | model, backup_kwargs = _set_partitioner_parameters(model, partition_kwargs) |
| |
|
| | |
| | |
| | with torch.no_grad(): |
| | enum = tqdm(dataloader) if verbose else dataloader |
| | for nag_list in enum: |
| | nag = NAGBatch.from_nag_list([nag.cuda() for nag in nag_list]) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | nag = dataset.on_device_transform(nag) |
| |
|
| | |
| | |
| | model.validation_step(nag, None) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | panoptic = deepcopy(model.val_panoptic) |
| | instance = deepcopy(model.val_instance) |
| | semantic = deepcopy(model.val_semantic) |
| | model.val_affinity_oa.reset() |
| | model.val_affinity_f1.reset() |
| | model.val_panoptic.reset() |
| | model.val_semantic.reset() |
| | model.val_instance.reset() |
| |
|
| | |
| | model, _ = _set_partitioner_parameters(model, backup_kwargs) |
| |
|
| | if not verbose: |
| | return panoptic, instance, semantic |
| |
|
| | for k, v in panoptic.compute().items(): |
| | print(f"{k:<22}: {v}") |
| |
|
| | if not model.no_instance_metrics: |
| | for k, v in instance.compute().items(): |
| | print(f"{k:<22}: {v}") |
| |
|
| | print(f"mIoU : {semantic.miou().cpu().item()}") |
| |
|
| | return panoptic, instance, semantic |
| |
|
| |
|
| | def compute_panoptic_metrics_s3dis_6fold( |
| | fold_ckpt, |
| | experiment_config, |
| | stage='val', |
| | graph_kwargs=None, |
| | partition_kwargs=None, |
| | verbose=False): |
| | """Helper function to compute the semantic, instance, panoptic |
| | segmentation metrics of a model on a S3DIS 6-fold, for given |
| | instance graph and partition parameters. |
| | |
| | :param fold_ckpt: dict |
| | Dictionary with S3DIS fold numbers as keys and checkpoint paths |
| | as values |
| | :param experiment_config: str |
| | Experiment config to use for inference. For instance for S3DIS |
| | with stuff panoptic segmentation: 'panoptic/s3dis_with_stuff' |
| | :param stage: str |
| | :param graph_kwargs: dict |
| | :param partition_kwargs: dict |
| | :param verbose: bool |
| | :return: |
| | """ |
| | |
| | from src.metrics import PanopticQuality3D, MeanAveragePrecision3D, \ |
| | ConfusionMatrix |
| |
|
| | |
| | |
| | import warnings |
| | warnings.filterwarnings("ignore") |
| |
|
| | panoptic_list = [] |
| | instance_list = [] |
| | semantic_list = [] |
| | no_instance_metrics = None |
| | min_instance_size = None |
| | num_classes = None |
| | stuff_classes = None |
| |
|
| | for fold, ckpt_path in fold_ckpt.items(): |
| |
|
| | if verbose: |
| | print(f"\nFold {fold}") |
| |
|
| | |
| | cfg = init_config(overrides=[ |
| | f"experiment={experiment_config}", |
| | f"datamodule.fold={fold}", |
| | f"ckpt_path={ckpt_path}"]) |
| |
|
| | |
| | datamodule = hydra.utils.instantiate(cfg.datamodule) |
| | datamodule.prepare_data() |
| | datamodule.setup() |
| |
|
| | |
| | model = hydra.utils.instantiate(cfg.model) |
| |
|
| | |
| | model = model._load_from_checkpoint(cfg.ckpt_path) |
| | model = model.eval().cuda() |
| |
|
| | |
| | panoptic, instance, semantic = compute_panoptic_metrics( |
| | model, |
| | datamodule, |
| | stage=stage, |
| | graph_kwargs=graph_kwargs, |
| | partition_kwargs=partition_kwargs, |
| | verbose=verbose) |
| |
|
| | |
| | |
| | no_instance_metrics = model.no_instance_metrics |
| | min_instance_size = model.hparams.min_instance_size |
| | num_classes = datamodule.train_dataset.num_classes |
| | stuff_classes = datamodule.train_dataset.stuff_classes |
| |
|
| | del model, datamodule |
| |
|
| | |
| | panoptic_list.append(panoptic) |
| | instance_list.append(instance) |
| | semantic_list.append(semantic) |
| |
|
| | |
| | panoptic_6fold = PanopticQuality3D( |
| | num_classes, |
| | ignore_unseen_classes=True, |
| | stuff_classes=stuff_classes, |
| | compute_on_cpu=True) |
| |
|
| | instance_6fold = MeanAveragePrecision3D( |
| | num_classes, |
| | stuff_classes=stuff_classes, |
| | min_size=min_instance_size, |
| | compute_on_cpu=True, |
| | remove_void=True) |
| |
|
| | semantic_6fold = ConfusionMatrix(num_classes) |
| |
|
| | |
| | for i in range(len(panoptic_list)): |
| |
|
| | panoptic_6fold.instance_data += panoptic_list[i].instance_data |
| | panoptic_6fold.prediction_semantic += panoptic_list[i].prediction_semantic |
| |
|
| | if not no_instance_metrics: |
| | instance_6fold.prediction_score += instance_list[i].prediction_score |
| | instance_6fold.prediction_semantic += instance_list[i].prediction_semantic |
| | instance_6fold.instance_data += instance_list[i].instance_data |
| |
|
| | semantic_6fold.confmat += semantic_list[i].confmat.cpu() |
| |
|
| | |
| | print(f"\n6-fold") |
| | for k, v in panoptic_6fold.compute().items(): |
| | print(f"{k:<22}: {v}") |
| |
|
| | if not no_instance_metrics: |
| | for k, v in instance_6fold.compute().items(): |
| | print(f"{k:<22}: {v}") |
| |
|
| | print(f"mIoU : {semantic_6fold.miou().cpu().item()}") |
| |
|
| | return (panoptic_6fold, panoptic_list), (instance_6fold, instance_list), (semantic_6fold, semantic_list) |
| |
|
| |
|
| | def _set_graph_construction_parameters(dataset, graph_kwargs): |
| | """Searches for the last occurrence of `OnTheFlyInstanceGraph` among |
| | the `on_device_transform` of the dataset and modifies the graph |
| | construction parameters passed in the `graph_kwargs` dictionary. |
| | """ |
| | if graph_kwargs is None: |
| | return dataset |
| |
|
| | |
| | from src.transforms import OnTheFlyInstanceGraph |
| |
|
| | |
| | |
| | i_transform = None |
| | for i, transform in enumerate(dataset.on_device_transform.transforms): |
| | if isinstance(transform, OnTheFlyInstanceGraph): |
| | i_transform = i |
| |
|
| | |
| | if i_transform is not None and graph_kwargs is not None: |
| | for k, v in graph_kwargs.items(): |
| | setattr(dataset.on_device_transform.transforms[i_transform], k, v) |
| |
|
| | return dataset |
| |
|
| |
|
| | def _set_partitioner_parameters(model, partition_kwargs): |
| | """Modifies the `model.partitioner` parameters with parameters |
| | passed in the `partition_kwargs` dictionary. |
| | """ |
| | backup_kwargs = {} |
| |
|
| | if partition_kwargs is None: |
| | return model, backup_kwargs |
| |
|
| | |
| | if partition_kwargs is not None: |
| | for k, v in partition_kwargs.items(): |
| | backup_kwargs[k] = getattr(model.partitioner, k, None) |
| | setattr(model.partitioner, k, v) |
| |
|
| | return model, backup_kwargs |
| |
|
| |
|
| | def _forward_multi_partition( |
| | model, |
| | nag, |
| | partition_kwargs, |
| | mode='pas'): |
| | """Local helper to compute multiple instance partitions from the |
| | same input data, based on diverse partition parameter settings. |
| | """ |
| | |
| | from src.models.panoptic import PanopticSegmentationOutput |
| |
|
| | |
| | |
| | |
| | partition_kwargs = { |
| | k: v if isinstance(v, list) else [v] |
| | for k, v in partition_kwargs.items()} |
| |
|
| | with torch.no_grad(): |
| |
|
| | |
| | x = model.net(nag) |
| |
|
| | |
| | semantic_pred = [head(x_) for head, x_ in zip(model.head, x)] \ |
| | if model.multi_stage_loss else model.head(x) |
| |
|
| | |
| | x = x[0] if model.multi_stage_loss else x |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | x_edge = x[nag[1].obj_edge_index] |
| | x_edge = torch.cat( |
| | ((x_edge[0] - x_edge[1]).abs(), (x_edge[0] + x_edge[1]) / 2), dim=1) |
| | edge_affinity_logits = model.edge_affinity_head(x_edge).squeeze() |
| |
|
| | |
| | |
| | if 'a' not in mode.lower(): |
| | edge_affinity_logits = edge_affinity_logits * 0 |
| |
|
| | |
| | elif 'A' in mode: |
| | edge_affinity_logits = torch.special.logit(nag[1].obj_edge_affinity) |
| |
|
| | |
| | if 's' not in mode.lower(): |
| | partition_kwargs['p_weight'] = [0] |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | elif 'S' in mode: |
| | node_y = nag[1].y[:, :model.num_classes].argmax(dim=1) |
| | node_logits = one_hot( |
| | node_y, num_classes=model.num_classes).float() * 10 |
| | if model.multi_stage_loss: |
| | semantic_pred[0] = node_logits |
| | else: |
| | semantic_pred = node_logits |
| |
|
| | |
| | if 'p' not in mode.lower() and 'o' not in mode.lower(): |
| | partition_kwargs['x_weight'] = [0] |
| |
|
| | |
| | elif 'o' in mode: |
| | node_offset_pred = model.node_offset_head(x) |
| |
|
| | |
| | node_logits = semantic_pred[0] if model.multi_stage_loss \ |
| | else semantic_pred |
| | is_stuff = get_stuff_mask(node_logits, model.stuff_classes) |
| | node_offset_pred[is_stuff] = 0 |
| |
|
| | |
| | |
| | |
| | elif 'O' in mode: |
| | is_stuff = get_stuff_mask(nag[1].y, model.stuff_classes) |
| | nag[1].pos[~is_stuff] = nag[1].obj_pos[~is_stuff] |
| |
|
| | |
| | partition_keys = list(partition_kwargs.keys()) |
| | enum = [ |
| | {k: v for k, v in zip(partition_keys, values)} |
| | for values in product(*partition_kwargs.values())] |
| | partitions = {} |
| | for kwargs in tqdm(enum): |
| |
|
| | |
| | model, backup_kwargs = _set_partitioner_parameters(model, kwargs) |
| |
|
| | |
| | output = PanopticSegmentationOutput( |
| | semantic_pred, |
| | model.stuff_classes, |
| | edge_affinity_logits, |
| | |
| | nag.get_sub_size(1)) |
| |
|
| | |
| | output = model._forward_partition(nag, output) |
| |
|
| | |
| | |
| | partitions[tuple(kwargs.values())] = output.obj_index_pred |
| |
|
| | |
| | model, _ = _set_partitioner_parameters(model, backup_kwargs) |
| |
|
| | output = model.get_target(nag, output) |
| |
|
| | return output, partitions, partition_keys |
| |
|
| |
|
| | def grid_search_panoptic_partition( |
| | model, |
| | dataset, |
| | i_cloud=0, |
| | graph_kwargs=None, |
| | partition_kwargs=None, |
| | mode='pas', |
| | panoptic=True, |
| | instance=False): |
| | """Runs a grid search on the partition parameters to find the best |
| | setup on a given sample `dataset[i_cloud]`. |
| | |
| | :param model: PanopticSegmentationModule |
| | :param dataset: BaseDataset |
| | :param i_cloud: int |
| | The grid search will be computed on `dataset[i_cloud]` |
| | :param graph_kwargs: dict |
| | Dictionary of parameters to be passed to the instance graph |
| | constructor `OnTheFlyInstanceGraph`. NB: the grid search does |
| | not cover these parameters---only a single value can be passed |
| | for each of these parameters |
| | :param partition_kwargs: dict |
| | Dictionary of parameters to be passed to `model.partitioner`. |
| | Passing a list of values for a given parameter will trigger the |
| | grid search across these values. Beware of the combinatorial |
| | explosion ! |
| | :param mode: str |
| | String characterizing whether edge affinities, node semantics, |
| | positions and offsets should be used in the graph clustering. |
| | 'p': use node position. |
| | 'o': use predicted node offset. |
| | 'O': use oracle offset. |
| | 'a': use predicted edge affinity. |
| | 'A': use oracle edge affinities. |
| | 's': use predicted node semantics. |
| | 'S': use oracle node semantics. |
| | In contrast, not setting 'p', 'o', nor 'O' is equivalent to |
| | setting all node positions and offsets to 0. |
| | Similarly, not setting 'a' nor 'A' will set the same weight to |
| | all the edges. |
| | Finally, not setting 's', nor 'S' will set the same class to all |
| | the nodes. |
| | :param panoptic: bool |
| | Whether panoptic segmentation metrics should be computed |
| | :param instance: bool |
| | Whether instance segmentation metrics should be computed |
| | :return: |
| | """ |
| | |
| |
|
| | |
| | from src.metrics import PanopticQuality3D, MeanAveragePrecision3D |
| |
|
| | assert panoptic or instance, \ |
| | "At least 'panoptic' or 'instance' must be True" |
| |
|
| | |
| | max_len = 6 |
| |
|
| | |
| | |
| | dataset = _set_attribute_preserving_transforms(dataset) |
| |
|
| | |
| | dataset = _set_graph_construction_parameters(dataset, graph_kwargs) |
| |
|
| | |
| | |
| | nag = dataset[i_cloud] |
| |
|
| | |
| | |
| | |
| | |
| | nag = dataset.on_device_transform(nag.cuda()) |
| |
|
| | |
| | output, partitions, partition_keys = _forward_multi_partition( |
| | model, |
| | nag, |
| | partition_kwargs, |
| | mode=mode) |
| |
|
| | |
| | output = model.get_target(nag, output) |
| |
|
| | |
| | instance_metrics = MeanAveragePrecision3D( |
| | model.num_classes, |
| | stuff_classes=model.stuff_classes, |
| | min_size=model.hparams.min_instance_size, |
| | compute_on_cpu=True, |
| | remove_void=True) |
| |
|
| | panoptic_metrics = PanopticQuality3D( |
| | model.num_classes, |
| | ignore_unseen_classes=True, |
| | stuff_classes=model.stuff_classes, |
| | compute_on_cpu=True) |
| |
|
| | |
| | results = {} |
| | results_data = [] |
| | best_pq = -1 |
| | best_map = -1 |
| | best_pq_params = None |
| | best_map_params = None |
| | for (kwargs_values), obj_index_pred in partitions.items(): |
| |
|
| | |
| | kwargs = {k: v for k, v in zip(partition_keys, kwargs_values)} |
| |
|
| | output.obj_index_pred = obj_index_pred |
| |
|
| | obj_score, obj_y, instance_data = output.panoptic_pred() |
| | obj_score = obj_score.detach().cpu() |
| | obj_y = obj_y.detach().cpu() |
| |
|
| | if panoptic: |
| | panoptic_metrics.update(obj_y, instance_data.cpu()) |
| | panoptic_results = panoptic_metrics.compute() |
| | panoptic_metrics.reset() |
| | if panoptic_results.pq > best_pq: |
| | best_pq_params = tuple(kwargs.values()) |
| | best_pq = panoptic_results.pq |
| | else: |
| | panoptic_results = None |
| |
|
| | if instance: |
| | instance_metrics.update(obj_score, obj_y, instance_data.cpu()) |
| | instance_results = instance_metrics.compute() |
| | instance_metrics.reset() |
| | if instance_results.map > best_map: |
| | best_map_params = tuple(kwargs.values()) |
| | best_map = instance_results.map |
| | else: |
| | instance_results = None |
| |
|
| | |
| | |
| | results[tuple(kwargs.values())] = (panoptic_results, instance_results) |
| |
|
| | |
| | current_results = [*kwargs.values()] |
| | if panoptic: |
| | current_results += [ |
| | round(panoptic_results.pq.item() * 100, 2), |
| | round(panoptic_results.sq.item() * 100, 2), |
| | round(panoptic_results.rq.item() * 100, 2)] |
| | if instance: |
| | current_results += [ |
| | round(instance_results.map.item() * 100, 2), |
| | round(instance_results.map_50.item() * 100, 2)] |
| | results_data.append(current_results) |
| |
|
| | |
| | metric_columns = [] |
| | if panoptic: |
| | metric_columns += ['PQ', 'SQ', 'RQ'] |
| | if instance: |
| | metric_columns += ['mAP', 'mAP 50'] |
| | with pd.option_context('display.precision', 2): |
| | print(pd.DataFrame( |
| | data=results_data, |
| | columns=[ |
| | *[ |
| | x[:max_len - 1] + '.' if len(x) > max_len else x |
| | for x in partition_keys |
| | ], |
| | *metric_columns])) |
| | print() |
| |
|
| | |
| | if panoptic and best_pq_params is not None: |
| |
|
| | |
| | print(f"\nBest panoptic setup: PQ={100 * best_pq:0.2f}") |
| | with pd.option_context('display.precision', 2): |
| | print(pd.DataFrame( |
| | data=[best_pq_params], |
| | columns=[ |
| | x[:max_len - 1] + '.' if len(x) > max_len else x |
| | for x in partition_keys])) |
| |
|
| | print() |
| |
|
| | |
| | res = results[best_pq_params][0] |
| | with pd.option_context('display.precision', 2): |
| | print(pd.DataFrame( |
| | data=torch.column_stack([ |
| | res.pq_per_class.mul(100), |
| | res.sq_per_class.mul(100), |
| | res.rq_per_class.mul(100), |
| | res.precision_per_class.mul(100), |
| | res.recall_per_class.mul(100), |
| | res.tp_per_class, |
| | res.fp_per_class, |
| | res.fn_per_class]), |
| | index=dataset.class_names[:-1], |
| | columns=['PQ', 'SQ', 'RQ', 'PREC.', 'REC.', 'TP', 'FP', 'FN'])) |
| | print() |
| |
|
| | |
| | output.obj_index_pred = partitions[best_pq_params] |
| |
|
| | |
| | if instance and best_map_params is not None: |
| |
|
| | |
| | print(f"\nBest instance setup: mAP={100 * best_map:0.2f}") |
| | with pd.option_context('display.precision', 2): |
| | print(pd.DataFrame( |
| | data=[best_map_params], |
| | columns=[ |
| | x[:max_len - 1] + '.' if len(x) > max_len else x |
| | for x in partition_keys])) |
| | print() |
| |
|
| | |
| | res = results[best_map_params][1] |
| | thing_class_names = [ |
| | c for i, c in enumerate(dataset.class_names) if i in dataset.thing_classes] |
| | with pd.option_context('display.precision', 2): |
| | print(pd.DataFrame( |
| | data=torch.column_stack([res.map_per_class.mul(100)]), |
| | index=thing_class_names, |
| | columns=['mAP'])) |
| | print() |
| |
|
| | return output, partitions, results |
| |
|