Example #1
0
def convert():
    basepath = 'pascal_panoptic_parts/releases/20201704/pascal_panoptic_parts_v1'

    filepaths = glob.glob(op.join(basepath, 'training/*.tif')) + glob.glob(
        op.join(basepath, 'validation/*.tif'))

    for fp in tqdm(filepaths):
        uids = np.asarray(Image.open(fp), dtype=np.int32)
        # transformation 1 (tvmonitor-unlabeled becomes tvmonitor-frame): {20_XXX, 20_XXX_00} -> 20_XXX_02
        sids, iids, pids, sids_iids, sids_pids = decode_uids(
            uids, return_sids_iids=True, return_sids_pids=True)
        pids = np.where(
            np.logical_and(
                iids >= 0,
                np.logical_or(np.equal(sids_pids, 20),
                              np.equal(sids_pids, 20_00))), 2, pids)
        uids = encode_ids(sids, iids, pids)
        # transformation 1 (remove 00): XX_XXX_00 -> XX_XXX
        _, _, pids, sids_iids = decode_uids(uids, return_sids_iids=True)
        uids = np.where(np.logical_and(uids >= 1_000_00, np.equal(pids, 0)),
                        sids_iids, uids)

        path_new = fp.replace('20201704/pascal_panoptic_parts_v1',
                              '20210503/pascal_panoptic_parts_v2')
        assert not op.exists(path_new), f'path {path_new} exists.'
        os.makedirs(op.dirname(path_new), exist_ok=True)
        Image.fromarray(uids, mode='I').save(path_new,
                                             format='TIFF',
                                             compression='tiff_lzw')
Example #2
0
def read_and_decode_files():
    cpp_paths = glob.glob(
        '/home/panos/git/p.meletis/panoptic_parts_datasets/tests/tests_files/gtFinePanopticParts/*/*/*.tif'
    )
    cpp_spec = '/home/panos/git/github/pmeletis/metrics_design/panoptic_parts/panoptic_parts/specs/dataset_specs/cpp_datasetspec.yaml'
    ppp_paths = glob.glob(
        '/home/panos/git/p.meletis/panoptic_parts_datasets/tests/tests_files/pascal_panoptic_parts/labels_v2/*/*.tif'
    )
    ppp_spec = '/home/panos/git/github/pmeletis/metrics_design/panoptic_parts/panoptic_parts/specs/dataset_specs/ppp_datasetspec.yaml'

    cpp_spec = DatasetSpec(cpp_spec)
    ppp_spec = DatasetSpec(ppp_spec)

    for fp in cpp_paths:
        uids = np.asanyarray(Image.open(fp), dtype=np.int32)
        decode_uids(uids,
                    return_sids_iids=True,
                    return_sids_pids=True,
                    experimental_dataset_spec=cpp_spec,
                    experimental_correct_range=True)

    for fp in ppp_paths:
        uids = np.asanyarray(Image.open(fp), dtype=np.int32)
        decode_uids(uids,
                    return_sids_iids=True,
                    return_sids_pids=True,
                    experimental_dataset_spec=ppp_spec,
                    experimental_correct_range=True)
def visualize_from_paths(datasetspec_path, label_path):
    """
  Visualizes in a pyplot window a label from the provided path.

  For visualization pixels are colored on:
    - semantic-level: according to colors defined in dataspec.sid2scene_color
    - semantic-instance-level: with random shades of colors defined in dataspec.sid2scene_color
    - semantic-instance-parts-level: with a mixture of parula colormap and the shades above
  See panoptic_parts.utils.visualization.uid2color for more information on color generation.

  Args:
    datasetspec_path: a YAML file path, including keys:
      `sid2scene_color`, `scene_class_part_class_from_sid_pid`
    label_path: a label path, will be passed to Pillow.Image.open
  """
    spec = DatasetSpec(datasetspec_path)
    uids = np.array(Image.open(label_path), dtype=np.int32)
    # for PPP, we need to fold groupable parts (see dataset ppp_datasetspec.yaml for more details)
    uids = encode_ids(*decode_uids(
        uids, experimental_dataset_spec=spec, experimental_correct_range=True))

    uids_sem_inst_parts_colored, uid2color_dct = experimental_colorize_label(
        uids,
        sid2color=spec.sid2scene_color,
        emphasize_instance_boundaries=True,
        return_uid2color=True,
        experimental_deltas=(60, 60, 60),
        experimental_alpha=0.5)

    # plot
    _, ax1 = plt.subplots()

    # generate legend, h is a hidden rectangle just to create a legend entry
    handles = []
    handles_text = []
    uids_unique = np.unique(uids)
    for uid in uids_unique:
        h = plt.Rectangle((0, 0),
                          1,
                          1,
                          fc=list(map(lambda x: x / 255, uid2color_dct[uid])))
        handles.append(h)
        _, _, _, sid_pid = decode_uids(uid, return_sids_pids=True)
        scene_class_part_class = spec.scene_class_part_class_from_sid_pid(
            sid_pid)
        handles_text.append(f'{uid}: {scene_class_part_class}')

    ax1.imshow(uids_sem_inst_parts_colored)
    ax1.set_title('labels colored on semantic, instance, and part levels',
                  fontsize='small')
    ax1.legend(handles,
               handles_text,
               ncol=3,
               fontsize='small',
               handlelength=1.0,
               loc='center left',
               bbox_to_anchor=(1.01, 0.5))
    plt.tight_layout()
    plt.show()
Example #4
0
def _num_instances_per_sid(uids):
    # Note: instances in Cityscapes are not always labeled with continuous iids,
    # e.g. one image can have instances with iids: 000, 001, 003, 007
    # TODO(panos): move this functionality to utils.format
    # np.array is needed since uids are Python ints
    # and np.unique implicitly converts them to np.int64
    # TODO(panos): remove this need when np.int64 is supported in decode_uids
    uids_unique = np.unique(np.array(uids, dtype=np.int32))
    _, _, _, sids_iids = decode_uids(uids_unique, return_sids_iids=True)
    sids_iids_unique = np.unique(sids_iids)
    sid2Ninstances = collections.defaultdict(lambda: 0)
    for sid_iid in sids_iids_unique:
        sid, iid, _ = decode_uids(sid_iid)
        if iid >= 0:
            sid2Ninstances[sid] += 1
    return sid2Ninstances
 def _read_gt_py(fp_gt):
     # eager function, required since .tif images can only be opened by Pillow for now
     fp_gt = fp_gt.numpy().decode('utf-8')
     label_gt = np.asarray(Image.open(fp_gt), dtype=np.int32)
     _, _, _, sids_pids_gt = decode_uids(label_gt,
                                         return_sids_pids=True)
     eids_gt = self.spec.sp2e_np[sids_pids_gt]
     return eids_gt
def pred_reader_fn(fp_pred, sid_pid2eval_id):
    # function provided to ConfusionMatrixEvaluator,
    # see class documentation for more information
    label_pred = np.asarray(Image.open(fp_pred), dtype=np.int32)
    # here we assume that predictions have the same format as ground truth
    _, _, _, sids_pids_pred = decode_uids(label_pred, return_sids_pids=True)
    eids_pred = sid_pid2eval_id[sids_pids_pred]
    return eids_pred
def _sids_iids_are_maintained(inpts):
  lp_orig, lp_ours = inpts
  labels_orig = np.asarray(Image.open(lp_orig), dtype=np.int32)
  labels_ours = np.asarray(Image.open(lp_ours), dtype=np.int32)
  _, _, _, sids_iids = decode_uids(labels_ours, return_sids_iids=True)
  returns = np.all(np.equal(labels_orig, sids_iids))
  # if not returns:
  #   print(lp_orig, lp_ours, sep='\n')
  #   print(np.unique(labels_orig), print(np.unique(sids_iids)), np.unique(labels_ours), sep='\n')
  return returns
Example #8
0
def _sid2pids(uids):
    # a dict mapping a sid to a set of all its pids
    # uids: a list of Python int uids
    # TODO(panos): move this functionality to utils.format
    assert isinstance(uids, list)
    sid2pids = collections.defaultdict(set)
    for uid in set(uids):
        sid, _, pid = decode_uids(uid)
        # decode_uids returns pid = -1 for pixels that don't have part-level labels
        if pid >= 0:
            sid2pids[sid].add(pid)
    return sid2pids
Example #9
0
def _sid2iids(uids):
    # a dict mapping a sid to a set of all its iids
    # or in other words a mapping from a semantic class to all object ids it has
    # uids: a list of Python int uids
    # iids do not need to be consecutive numbers
    # TODO(panos): move this functionality to utils.format
    sid2iids = collections.defaultdict(set)
    for uid in set(uids):
        sid, iid, _ = decode_uids(uid)
        # decode_uids returns iid = -1 for pixels that don't have instance-level labels
        if iid >= 0:
            sid2iids[sid].add(iid)
    return sid2iids
Example #10
0
def uids_lids2uids_cids(uids_with_lids, lids2cids):
  """
  Convert uids with semantic classes encoded as lids to uids with cids.
  This function is useful in the Cityscapes context, or other datasets
  where the lids and cids separation is used.
  """
  uids = uids_with_lids
  sids, _, _ = decode_uids(uids)
  uids_with_cids = np.where(
      uids <= 99,
      lids2cids[sids],
      np.where(uids <= 99_999,
               lids2cids[sids] * 10**3 + uids % 10**3,
               lids2cids[sids] * 10**5 + uids % 10**5))

  return uids_with_cids
def generate_ignore_info_tiff(part_panoptic_gt, eval_spec):
  ignore_img = np.zeros_like(part_panoptic_gt).astype(np.uint8)

  # TODO(daan): currently, this is applied to the original part_panoptic tifs, and not to the format on which we wish to evaluate.
  # TODO(daan): this is not an issue now, but can be when using different eval_sids wrt the dataset_sids, it will be problematic

  # get sid iid pid
  sid, _, _, sid_iid = decode_uids(part_panoptic_gt, return_sids_iids=True, experimental_dataset_spec=eval_spec._dspec)

  # if sid not in l_total: set to 255 (void)
  sid_void = np.logical_not(np.isin(sid, eval_spec.eval_sid_total))
  ignore_img[sid_void] = 255

  # if sid_iid < 1000 and sid in l_things, set to crowd and store sid
  no_iid = sid_iid < 1000
  things = np.isin(sid, eval_spec.eval_sid_things)
  crowd = np.logical_and(no_iid, things)

  ignore_img[crowd] = sid_iid[crowd]

  return ignore_img
Example #12
0
def _transform_uids(uids, max_sid, sid2pids_groups):
  sid_pid_old2sid_pid_new = dict()
  for sid, sid_group in sid2pids_groups.items():
    sid_pid_old2sid_pid_new.update(_parse_sid_group(sid, sid_group))
  sid_pid_old2sid_pid_new[0] = 0
  for sid in range(1, max_sid + 1):
    if sid not in sid_pid_old2sid_pid_new.keys():
      sid_pid_old2sid_pid_new[sid] = sid
    for pid in range(100):
      sid_pid = sid * 100 + pid
      if pid == 0:
        sid_pid_old2sid_pid_new[sid_pid] = sid
        continue
      if sid_pid not in sid_pid_old2sid_pid_new.keys():
        sid_pid_old2sid_pid_new[sid_pid] = sid
  sid_pid_old2sid_pid_new = dict(sorted(sid_pid_old2sid_pid_new.items()))
  palette = np.asarray(list(sid_pid_old2sid_pid_new.values()), dtype=np.int32)

  _, iids, _, sids_pids = decode_uids(uids, return_sids_pids=True)
  sids_pids = palette[sids_pids]
  sids = np.where(sids_pids <= 99, sids_pids, sids_pids // 100)
  pids = np.where(sids_pids <= 99, -1, sids_pids % 100)
  return encode_ids(sids, iids, pids)
def annotation_parsing(spec, sample):
  '''
  parse the numpy encoding defined by dataset definition.

  Args:   
          spec.cat_definition: e.g.
                          {
                              "num_cats": 2,
                              "cat_def":  [{
                                              "sem_cls": [24, 25],
                                              "parts_cls": [1, 2, 3, 4]
                                          },
                                          {
                                              "sem_cls": [26, 27, 28],
                                              "parts_cls": [1, 2, 3, 4, 5]
                                          }]
                          }
          sample: a numpy array with ground truth annotation

  Returns: a dict:
          {
              cat #0: {   "num_instances": int,
                          "binary_masks": numpy array with size num_instances*h*w
                          "parts_annotation": numpy array with size num*instances*h*w
                          (for each instance, one layer for mask (binary), one layer for parts segmentation with
                          annotation from 1 to 4 or 5)
                      }
              ,
              cat #1: {
                          ...
                      }
              ,
              ...
          }
  '''

  h, w = sample.shape

  noinfo_id = 0
  sem_map, inst_map, part_map, sids_pids = decode_uids(sample,
                                                       return_sids_pids=True,
                                                       experimental_noinfo_id=noinfo_id,
                                                       experimental_dataset_spec=spec._dspec)
  sem_map = sem_map.astype(np.int32)
  inst_map = inst_map.astype(np.int32)
  part_map = part_map.astype(np.int32)

  # transform the pids to the pids of the eval_spec, according to dataset_sid_pid2eval_sid_pid,
  # this happends only if dataset_sid_pid2eval_sid_pid is not the identity mapping (k!=v), which
  # this applies only to PPP eval_spec as parts are grouped, while CPP does not group parts
  if any(k != v if v != 'IGNORED' else False for k, v in spec.dataset_sid_pid2eval_sid_pid.items()):
    dsp2esp = parse_dataset_sid_pid2eval_sid_pid(spec.dataset_sid_pid2eval_sid_pid)
    dsp2esp = ndarray_from_dict(dsp2esp, -10**6, length=10000) # -10**6: a random big number
    sids_pids = dsp2esp[sids_pids]
    assert not np.any(np.equal(sids_pids, -10**6)), 'sanity check'
    pids = np.where(sids_pids >= 1_00, sids_pids % 100, noinfo_id)
    # TODO(panos): for now only the pids are mapped, the sids are assumed to be the identical between
    #   the dataset (sem_map) and the eval_spec, so assign only new pids to part_map
    part_map = pids

  meta_dict = {}

  ignore_map = np.zeros((h, w), dtype=np.int32)

  # cat_id is 0, 1, 2, ...,
  cat_definition = spec.cat_definition
  for cat_id in range(cat_definition['num_cats']):
    sem_cls = cat_definition['cat_def'][cat_id]['sem_cls']
    parts_cls = cat_definition['cat_def'][cat_id]['parts_cls']

    # empty list for multiple semantic classes for a single category
    binary_masks_list = []
    parts_annotations_list = []

    for sem_idx in sem_cls:
      selected = sem_map == sem_idx
      selected_ins = inst_map.copy()
      selected_ins[np.invert(selected)] = -1
      if -1 in selected_ins:
        idxs = np.unique(selected_ins)
        # get rid of -1 label stat
        idxs = idxs[1:]
      else:
        # only used if all the pixels belong to the same semantic classes, then there will be no -1 label
        idxs = np.unique(selected_ins)

      binary_masks = np.zeros((idxs.shape[0], h, w)).astype(np.int32)
      parts_annotations = np.zeros((idxs.shape[0], h, w)).astype(np.int32)

      # write the masks and part-level annotations
      for i in range(len(idxs)):
        binary_masks[i, :, :] = selected_ins == idxs[i]
        if len(parts_cls) > 1:
          temp_parts = np.zeros((h, w)).astype(np.int32)
          temp_parts[selected_ins == idxs[i]] = part_map[selected_ins == idxs[i]]
          parts_annotations[i, :, :] = temp_parts

      # Some segments for scene-classes l_parts do not have part annotations (only the background label 0)
      # We cannot apply part-level evaluation to these segments, so we delete them and denote them as crowd
      if len(parts_cls) > 1:
        delete_idx = []
        for i in range(idxs.shape[0]):
          temp_binary_msk = binary_masks[i, :, :]
          temp_parts_anno = parts_annotations[i, :, :]
          part_elements = np.unique(temp_parts_anno[temp_binary_msk > 0.5])
          if part_elements.size == 1 and 0 in part_elements:
            delete_idx.append(i)
            ignore_map[temp_binary_msk > 0.5] = sem_idx
        binary_masks = np.delete(binary_masks, delete_idx, 0)
        parts_annotations = np.delete(parts_annotations, delete_idx, 0)

      binary_masks_list.append(binary_masks)
      parts_annotations_list.append(parts_annotations)

    binary_masks_per_cat = np.concatenate(binary_masks_list)
    parts_annotations_per_cat = np.concatenate(parts_annotations_list)
    num_instances_per_cat = binary_masks_per_cat.shape[0]

    meta_dict[cat_id] = {'num_instances': num_instances_per_cat,
                         'binary_masks': binary_masks_per_cat,
                         'parts_annotation': parts_annotations_per_cat
                         }

  return meta_dict, ignore_map
BASEPATH_LABELS_ORIGINAL = 'tests/tests_files/cityscapes/gtFine'
labels_paths_original = glob.glob(
    op.join(BASEPATH_LABELS_ORIGINAL, 'train', '*', '*_instanceIds.png'))
labels_paths_original.extend(
    glob.glob(
        op.join(BASEPATH_LABELS_ORIGINAL, 'val', '*', '*_instanceIds.png')))
print(len(labels_paths_original))
labels_paths_ours = [
    lp.replace('cityscapes',
               'cityscapes_panoptic_parts').replace('_instanceIds.png',
                                                    '_panopticIds.tif')
    for lp in labels_paths_original
]
print(len(labels_paths_ours))

# validate labels
for i, (lp_orig,
        lp_ours) in enumerate(zip(labels_paths_original, labels_paths_ours)):
    print(f"{i+1}/{len(labels_paths_original)}")
    labels_orig = np.array(Image.open(lp_orig), dtype=np.int32)
    labels_ours = np.array(Image.open(lp_ours), dtype=np.int32)

    _, _, _, sids_iids = decode_uids(labels_ours, return_sids_iids=True)
    if not np.all(np.equal(labels_orig, sids_iids)):
        print(lp_orig, lp_ours, sep='\n')
        print(np.unique(labels_orig),
              print(np.unique(sids_iids)),
              np.unique(labels_ours),
              sep='\n')
        breakpoint()
Example #15
0
def uid2color(uids,
              sid2color=None,
              experimental_deltas=(60, 60, 60),
              experimental_alpha=0.5):
    """
  Generate an RGB palette for all unique uids in `uids`. The palette is a dictionary mapping
  each uid from `uids` to an RGB color tuple, with values in range [0, 255].
  A uid is an up to 7-digit integer that is interpreted according to our panoptic parts format
  (see README), i.e., decode_uids(uid) = (sid, iid, pid).

  The colors are generated in the following way:
    - if uid represents a semantic-level label, i.e. uid = (sid, N/A, N/A),
      then `sid2color`[sid] is used.
    - if uid represents a semantic-instance-level label, i.e. uid = (sid, iid, N/A),
      then a random shade of `sid2color`[sid] is generated, controlled by `experimental_deltas`.
      The shades are generated so they are as diverse as possible and the variability depends
      on the number of iids per sid. The more the instances per sid in the `uids`, the less
      discriminable the shades are.
    - if uid represents a semantic-instance-parts-level label, i.e. uid = (sid, iid, pid),
      then a random shade is generated as in the semantic-instance-level case above and then
      it is mixed with a single color from the parula colormap, controlled by `experimental_alpha`.
      A different parula colormap is generated for each sid to achieve best discriminability
      of parts colors per sid.

  If `sid2color` is not provided (is None) then random colors are used. If `sid2color`
  is provided but does not contain all the sids of `uids` an error is raised.

  Example usage in {cityscapes, pascal}_panoptic_parts/visualize_from_paths.py.

  Args:
    uids: a list of Python int, or a np.int32 np.ndarray, with elements following the panoptic
      parts format (see README)
    sid2color: a dict mapping each sid of uids to an RGB color tuple of Python ints
      with values in range [0, 255], sids that are not present in uids will be ignored
    experimental_deltas: the range per color (Red, Green, Blue) in which to create shades, a small
      range provides shades that are close to the sid color but makes instance colors to have less
      contrast, a higher range provides better contrast but may create similar colors between
      different sid instances
    experimental_alpha: the mixing coeffient of the shade and the parula color, a higher value
      will make the semantic-instance-level shade more dominant over the parula color

  Returns:
    uid2color: a dict mapping each uid to a color tuple of Python int in range [0, 255]
  """

    if VALIDATE_ARGS:
        _validate_uid2color_args(uids, sid2color, experimental_deltas,
                                 experimental_alpha)

    if isinstance(uids, np.ndarray):
        uids = list(map(int, np.unique(uids)))

    ## generate semantic-level colors
    if sid2color is None:
        # TODO(panos): add the list decoding functionality in decode_uids
        sids_unique = set(map(operator.itemgetter(0), map(decode_uids, uids)))
        random_sids_palette = random_colors(len(sids_unique))
        sid2color = {
            sid: tuple(map(int, color))
            for sid, color in zip(sids_unique, random_sids_palette)
        }

    ## generate instance shades
    sid2num_instances = _num_instances_per_sid(uids)
    # TODO(panos): experimental_deltas must be large for sids with many iids and small for
    #   sids with few iids, maybe automate this?
    sid2shades = {
        sid: _generate_shades(sid2color[sid], experimental_deltas, Ninstances)
        for sid, Ninstances in sid2num_instances.items()
    }

    ## generate discriminable per-sid parula colormap for parts
    # For best part-level color discriminability we generate a colormap per-sid,
    # this creates a discrininable colormap per-sid irrespectible of the number of parts.
    sid2num_parts = _num_parts_per_sid(uids)
    is_maybe_cpp = (USE_LEGACY_CPP_PARTS_COLORMAP
                    and all(map(lambda n: n <= 5, sid2num_parts.values())))
    sid2parulaX = {
        sid: LEGACY_PARULA6 if is_maybe_cpp else
        (PARULA99_CM(np.linspace(0, 1, num=Nparts)) *
         255)[:, :3].astype(np.int32)
        for sid, Nparts in sid2num_parts.items()
    }

    ## generate the uid to colors mappings
    # convert sets to lists so they are indexable, the .index() is needed since iids and
    # pids do not need be to be continuous (otherwise sid2shades[sid][iid] is enough)
    # TODO(panos): sid_2_* have overlapping functionality, consider merging them
    sid_2_iids = {
        sid: list(iids)
        for sid, iids in _sid2iids(set(uids)).items()
    }

    def _remove_all_no_error(lst, el):
        if el in lst:
            lst.remove(el)
        assert el not in lst
        return lst

    sid_2_non_zero_pids = {
        sid: _remove_all_no_error(list(pids), 0)
        for sid, pids in _sid2pids(uids).items()
    }

    uid_2_color = dict()
    for uid in set(uids):
        sid, iid, pid = decode_uids(uid)
        if uid <= 99:
            uid_2_color[uid] = sid2color[sid]
            continue
        index_iid = sid_2_iids[sid].index(iid)
        sem_inst_level_color = sid2shades[sid][index_iid]
        if uid <= 99_999 or pid == 0:
            uid_2_color[uid] = sem_inst_level_color
            continue
        if pid >= 1:
            index_pid = sid_2_non_zero_pids[sid].index(pid)
            uid_2_color[uid] = tuple(
                map(
                    int,
                    experimental_alpha * np.array(sem_inst_level_color) +
                    (1 - experimental_alpha) *
                    np.array(sid2parulaX[sid][index_pid])))
        # catch any possible errors
        assert uid in uid_2_color.keys()
def experimental_visualize(image_path, label_path):
    """
    Visualizes in a pyplot window an image and a label pair from
    provided paths. For reading Pillow is used so all paths and formats
    must be Pillow-compatible.

    Args:
        image_path: an image path provided to Pillow.Image.open
        label_path: a label path provided to Pillow.Image.open
    """
    assert op.exists(image_path)
    assert op.exists(label_path)

    # Prepare canvases and decode the labels.
    image = np.array(Image.open(image_path), dtype=np.uint8)
    label = np.array(Image.open(label_path), dtype=np.int32)
    uids_unique_org = np.unique(label)
    semantic_segmentation = np.zeros((image.shape[0], image.shape[1], 3),
                                     dtype=np.uint8)
    instance_segmentation = np.zeros((image.shape[0], image.shape[1], 3),
                                     dtype=np.uint8)
    parts_segmentation = np.zeros((image.shape[0], image.shape[1], 3),
                                  dtype=np.uint8)
    sids, iids, _ = decode_uids(label)

    # Color at the semantic level.
    color_generator = IdGenerator(CATEGORIES)
    for sid in np.unique(sids):
        mask = np.equal(sids, sid)
        color = CATEGORIES[sid]['color']
        semantic_segmentation[mask] = color

    # Color at the semantic and instance level and find the instance-level boundaries.
    sids_only = np.where(iids < 0, sids, np.zeros_like(iids))
    for sid in np.unique(sids_only):
        mask = np.equal(sids_only, sid)
        color = color_generator.get_color(sid)
        instance_segmentation[mask] = color

    sid_iids = np.where(iids >= 0, sids * 10**3 + iids, np.zeros_like(iids))
    boundaries = np.full(sid_iids.shape, False)
    for sid_iid in np.unique(sid_iids):
        if sid_iid != 0:
            mask = np.equal(sid_iids, sid_iid)
            color = color_generator.get_color(sid_iid // 1000)
            instance_segmentation[mask] = color
            boundary_horizon = ndimage.sobel(mask, 0)
            boundary_vertical = ndimage.sobel(mask, 1)
            boundaries = np.logical_or(
                np.hypot(boundary_horizon, boundary_vertical), boundaries)

    # Color at the part level.
    # Conver the original labels into the form for visualization with IdGenerator.
    for uid in uids_unique_org:
        # If uid is sid or sid_iid, encode them as they are.
        if uid <= 99_999:
            sid_iid = uid
        # If uid is sid_iid_pid, map sid_pid to its corresponding sid and create new label as sid_iid.
        else:
            sid, iid, pid = decode_uids(uid)
            sid_pid = sid * 10**2 + pid
            if sid_pid in SID_PID2PARTS_CID:
                sid_iid = SID_PID2PARTS_CID[sid_pid] * 10**3 + iid
            else:
                sid_iid = sid * 10**3 + iid

        label[label == uid] = sid_iid

    color_generator = IdGenerator(CATEGORIES_PARTS)

    for sid_iid in np.unique(label):
        # If sid_iid is in the format of sid , use sid for color generation (things and stuff classes differentiated by IdGenerator inherently).
        if sid_iid <= 99:
            id_ = sid_iid
        # If sid_iid is in the format of sid_iid, get sid.
        else:
            id_ = sid_iid // 1000
        mask = label == sid_iid
        color = color_generator.get_color(id_)
        parts_segmentation[mask] = color

    # Depict boundaries.
    instance_segmentation[boundaries] = [255, 255, 255]
    parts_segmentation[boundaries] = [255, 255, 255]

    # plot
    # initialize figure for plotting
    _, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2)
    # for ax in axes:
    #   ax.set_axis_off()
    ax1.imshow(image)
    ax1.set_title('image')
    ax2.imshow(semantic_segmentation)
    ax2.set_title('labels colored on semantic level')
    ax3.imshow(instance_segmentation)
    ax3.set_title('labels colored on semantic and instance levels')
    ax4.imshow(parts_segmentation)
    ax4.set_title('labels colored on semantic, instance, and parts levels')
    plt.show()
Example #17
0
def experimental_colorize_label(label,
                                sid2color=None,
                                return_sem=False,
                                return_sem_inst=False,
                                emphasize_instance_boundaries=True):
    """
  Colorizes a `label` with semantic-instance-parts-level colors based on sid2color.
  Optionally, semantic-level and semantic-instance-level colorings can be returned.
  The option emphasize_instance_boundaries will draw a 4-pixel white line around instance
  boundaries for the semantic-instance-level and semantic-instance-parts-level outputs.
  If a sid2color dict is provided colors from that will be used otherwise random colors
  will be generated.
  See panoptic_parts.utils.visualization.uid2color for how colors are generated.

  Args:
    label: 2-D, np.int32, np.ndarray with up to 7-digit uids, according to format in README
    sid2color: a dictionary mapping sids to RGB color tuples in [0, 255], all sids in `labels`
      must be in `sid2color`, otherwise provide None to use random colors
    return_sem: if True returns `sem_colored`
    return_sem_inst: if True returns `sem_inst_colored`

  Returns:
    sem_inst_parts_colored: 3-D, np.ndarray with RGB colors in [0, 255],
      colorized `label` with colors that distinguish scene-level semantics, part-level semantics,
      and instance-level ids
    sem_colored: 3-D, np.ndarray with RGB colors in [0, 255], returned if return_sem=True,
      colorized `label` with colors that distinguish scene-level semantics
    sem_inst_colored: 3-D, np.ndarray with RGB colors in [0, 255], returned if return_sem_inst=True,
      colorized `label` with colors that distinguish scene-level semantics and part-level semantics
  """
    if not isinstance(label, np.ndarray):
        raise ValueError(
            f"label is type: {type(label)}, only np.ndarray is supported.")
    if not all([label.ndim == 2, label.dtype == np.int32]):
        raise ValueError(
            f"label has: {label.ndim} dims and {label.dtype} dtype, only 2 dims"
            " and np.int32 are supported.")

    # We visualize labels on three levels: semantic, semantic-instance, semantic-instance-parts.
    # We want to colorize same instances with the same shades across levels for easier comparison
    # so we create ids_all_levels_unique and call uid2color() once to achieve that.
    # sids, iids, sids_iids shapes: (height, width)
    sids, iids, _, sids_iids = decode_uids(label, return_sids_iids=True)
    ids_all_levels_unique = np.unique(np.stack([sids, sids_iids, label]))
    uid2color_dict = uid2color(ids_all_levels_unique, sid2color=sid2color)

    # We colorize ids using numpy advanced indexing (gathering). This needs an array palette, thus we
    # convert the dictionary uid2color_dict to an array palette with shape (Ncolors, 3) and
    # values in range [0, 255] (RGB).
    # uids_*_colored shapes: (height, width, 3)
    palette = _sparse_ids_mapping_to_dense_ids_mapping(uid2color_dict,
                                                       (0, 0, 0),
                                                       dtype=np.uint8)
    uids_sem_colored = palette[sids]
    uids_sem_inst_colored = palette[sids_iids]
    uids_sem_inst_parts_colored = palette[label]

    # optionally add boundaries to the colorized labels uids_*_colored
    # TODO(panos): instance boundaries are found by the iids, if two iids are the same
    #   then an instance boundary is not drawn between different semantic-level classes
    # TODO(panos): same iids islands, that are occluded, must not have closed boundaries
    #   investigate if a solution to that is easy
    edge_option = 'sobel'  # or 'erosion'
    if emphasize_instance_boundaries:
        # TODO(panos): simplify this algorithm
        # create per-instance binary masks
        iids_unique = np.unique(iids)
        boundaries = np.full(iids.shape, False)
        edges = np.full(iids.shape, False)
        for iid in iids_unique:
            if 0 <= iid <= 999:
                iid_mask = np.equal(iids, iid)
                if edge_option == 'sobel':
                    edge_horizont = ndimage.sobel(iid_mask, 0)
                    edge_vertical = ndimage.sobel(iid_mask, 1)
                    edges = np.logical_or(
                        np.hypot(edge_horizont, edge_vertical), edges)
                elif edge_option == 'erosion':
                    boundary = np.logical_xor(
                        iid_mask,
                        ndimage.binary_erosion(iid_mask,
                                               structure=np.ones((4, 4))))
                    boundaries = np.logical_or(boundaries, boundary)

        if edge_option == 'sobel':
            boundaries_image = np.uint8(edges)[..., np.newaxis] * np.uint8(
                [[[255, 255, 255]]])
        elif edge_option == 'erosion':
            boundaries_image = np.uint8(boundaries)[...,
                                                    np.newaxis] * np.uint8(
                                                        [[[255, 255, 255]]])

        uids_sem_inst_colored = np.where(boundaries_image, boundaries_image,
                                         uids_sem_inst_colored)
        uids_sem_inst_parts_colored = np.where(boundaries_image,
                                               boundaries_image,
                                               uids_sem_inst_parts_colored)

    returns = (uids_sem_inst_parts_colored, )
    if return_sem:
        returns += (uids_sem_colored, )
    if return_sem_inst:
        returns += (uids_sem_inst_colored, )
    if len(returns) == 1:
        return returns[0]
    return returns