def get_segmentation(self,gt_segmentation,box,max_ind,imsize): segmentation=[]; gts=gt_segmentation[max_ind] if type(gts) == list: assert (type(gts[0]) != dict) prle= mask.frPyObjects(gts,imsize[0],imsize[1]) elif type(gts) == dict and type(gts['counts']) == list: prle= mask.frPyObjects([gts],imsize[0],imsize[1]) elif type(gts) == dict and \ type(gts['counts'] == unicode or type(gts['counts']) == str): prle = [gts] else: return segmentation if len(prle)==1: prle=prle[0] else: prle= mask.merge(prle) grle=mask.frPyObjects([[box[0],box[1],box[2],box[1],box[2],box[3],box[0],box[3],box[0],box[1]]],imsize[0],imsize[1]) #print grle,'----' pmask=mask.merge([prle,grle[0]],intersect=True) segmentation=pmask # for sm in gts: # poly=Polygon(zip(sm(::2),sm(1::2))) # bpoly=Polygon([(box[0],box[1]),(box[0],box[3]),(box[2],box[3]),(box[2],boxes[1]),(box[0],box[1])]) # bpoly=bpoly.intersection(poly) # coords=array(bpoly.exterior.coords) # coords=coords-[box[0],box[1]] # segmentation.append(coords.ravel().tolist()) return segmentation
def crop_mask(boxes,segmentations,flipped, imsize): assert (boxes.shape[0]==len(segmentations)) psegmentations=[] for i in xrange(len(segmentations)): gts=segmentations[i] box=boxes[i,:] if type(gts) == list and gts: assert (type(gts[0]) != dict) prle= mask.frPyObjects(gts,imsize[1],imsize[0]) elif type(gts) == dict and type(gts['counts']) == list: prle= mask.frPyObjects([gts],imsize[1],imsize[0]) elif type(gts) == dict and \ type(gts['counts'] == unicode or type(gts['counts']) == str): prle = [gts] else: print '{} box has no segmentation'.format(i) psegmentations.append([]) continue if len(prle)==1: prle=prle[0] else: prle= mask.merge(prle) pmask=mask.decode([prle]) if flipped: pmask=pmask[:,::-1,:] pmask=np.copy(pmask[box[1]:box[3],box[0]:box[2],:],order='F') psegmentations.append(mask.encode(pmask)) return psegmentations
def load_dataset(self): dataset = self.cfg.dataset dataset_phase = self.cfg.dataset_phase dataset_ann = self.cfg.dataset_ann # initialize COCO api annFile = '%s/annotations/%s_%s.json'%(dataset,dataset_ann,dataset_phase) self.coco = COCO(annFile) imgIds = self.coco.getImgIds() data = [] # loop through each image for imgId in imgIds: item = DataItem() img = self.coco.loadImgs(imgId)[0] item.im_path = "%s/images/%s/%s"%(dataset, dataset_phase, img["file_name"]) item.im_size = [3, img["height"], img["width"]] item.coco_id = imgId annIds = self.coco.getAnnIds(imgIds=img['id'], iscrowd=False) anns = self.coco.loadAnns(annIds) all_person_keypoints = [] masked_persons_RLE = [] visible_persons_RLE = [] all_visibilities = [] # Consider only images with people has_people = len(anns) > 0 if not has_people and self.cfg.coco_only_images_with_people: continue for ann in anns: # loop through each person person_keypoints = [] visibilities = [] if ann["num_keypoints"] != 0: for i in range(self.cfg.num_joints): x_coord = ann["keypoints"][3 * i] y_coord = ann["keypoints"][3 * i + 1] visibility = ann["keypoints"][3 * i + 2] visibilities.append(visibility) if visibility != 0: # i.e. if labeled person_keypoints.append([i, x_coord, y_coord]) all_person_keypoints.append(np.array(person_keypoints)) visible_persons_RLE.append(maskUtils.decode(self.coco.annToRLE(ann))) all_visibilities.append(visibilities) if ann["num_keypoints"] == 0: masked_persons_RLE.append(self.coco.annToRLE(ann)) item.joints = all_person_keypoints item.im_neg_mask = maskUtils.merge(masked_persons_RLE) if self.cfg.use_gt_segm: item.gt_segm = np.moveaxis(np.array(visible_persons_RLE), 0, -1) item.visibilities = all_visibilities data.append(item) self.has_gt = self.cfg.dataset is not "image_info" return data
def convert(self, mode): width, height = self.size if mode == "mask": rles = mask_utils.frPyObjects( [p.numpy() for p in self.polygons], height, width ) rle = mask_utils.merge(rles) mask = mask_utils.decode(rle) mask = torch.from_numpy(mask) # TODO add squeeze? return mask
def segmentation_to_mask(polys, height, width): """ Convert polygons to binary masks. Args: polys: a list of nx2 float array Returns: a binary matrix of (height, width) """ polys = [p.flatten().tolist() for p in polys] rles = cocomask.frPyObjects(polys, height, width) rle = cocomask.merge(rles) return cocomask.decode(rle)
def _getIgnoreRegion(iid, coco): img = coco.imgs[iid] if not 'ignore_regions_x' in img.keys(): return None if len(img['ignore_regions_x']) == 0: return None rgns_merged = [] for region_x, region_y in zip(img['ignore_regions_x'], img['ignore_regions_y']): rgns = [iter(region_x), iter(region_y)] rgns_merged.append(list(it.next() for it in itertools.cycle(rgns))) rles = maskUtils.frPyObjects(rgns_merged, img['height'], img['width']) rle = maskUtils.merge(rles) return maskUtils.decode(rle)
def annToRLE(self, ann, height, width): """ Convert annotation which can be polygons, uncompressed RLE to RLE. :return: binary mask (numpy 2D array) """ segm = ann['segmentation'] if isinstance(segm, list): # polygon -- a single object might consist of multiple parts # we merge all parts into one mask rle code rles = maskUtils.frPyObjects(segm, height, width) rle = maskUtils.merge(rles) elif isinstance(segm['counts'], list): # uncompressed RLE rle = maskUtils.frPyObjects(segm, height, width) else: # rle rle = ann['segmentation'] return rle
def annToRLE(self, ann): """ Convert annotation which can be polygons, uncompressed RLE to RLE. :return: binary mask (numpy 2D array) """ t = self.imgs[ann['image_id']] h, w = t['height'], t['width'] segm = ann['segmentation'] if type(segm) == list: # polygon -- a single object might consist of multiple parts # we merge all parts into one mask rle code rles = maskUtils.frPyObjects(segm, h, w) rle = maskUtils.merge(rles) elif type(segm['counts']) == list: # uncompressed RLE rle = maskUtils.frPyObjects(segm, h, w) else: # rle rle = ann['segmentation'] return rle
def to_mask(polys, size): """Convert list of polygons to full size binary mask Parameters ---------- polys : list of numpy.ndarray Numpy.ndarray with shape (N, 2) where N is the number of bounding boxes. The second axis represents points of the polygons. Specifically, these are :math:`(x, y)`. size : tuple Tuple of length 2: (width, height). Returns ------- numpy.ndarray Full size binary mask of shape (height, width) """ try_import_pycocotools() import pycocotools.mask as cocomask width, height = size polys = [p.flatten().tolist() for p in polys] rles = cocomask.frPyObjects(polys, height, width) rle = cocomask.merge(rles) return cocomask.decode(rle)
def polys_to_mask(polygons, height, width): rles = cocomask.frPyObjects(polygons, height, width) rle = cocomask.merge(rles) mask = cocomask.decode(rle) return mask
def polygons_to_mask(self, polygons): rle = mask_util.frPyObjects(polygons, self.height, self.width) rle = mask_util.merge(rle) return mask_util.decode(rle)[:, :]
os.mkdir(os.path.join("data/gt", vid)) #Mask ground truth directory by names for frame_id in range(len(frames_list)): im_gt = cv2.imread(os.path.join(train_dir, frames_list[frame_id])) ht, wt = im_gt.shape[:2] #height , width segm = pancreas[i] if segm: rles = maskUtils.frPyObjects(segm, ht, wt) """ What we have is list of polygons: [ polygon ] [[x1,...xn] , [y1,...yn]] rles: list of rle """ rle = maskUtils.merge( rles) # combined rle format for the image mask = maskUtils.decode(rle) # decode the rle padded_mask = np.zeros((mask.shape[0] + 2, mask.shape[1] + 2), dtype=np.uint8) padded_mask[1:-1, 1:-1] = mask im_gt = apply_mask(im_gt, mask, (0.0, 1, 0.0)).astype( np.uint8) # Test Padded Mask segm = cancer[i] if segm: rles = maskUtils.frPyObjects(segm, ht, wt) rle = maskUtils.merge(rles) mask = maskUtils.decode(rle) padded_mask = np.zeros((mask.shape[0] + 2, mask.shape[1] + 2), dtype=np.uint8) padded_mask[1:-1, 1:-1] = mask
def combine_to_panoptic(proc_id, img_ids, img_id2img, inst_by_image, sem_by_image, segmentations_folder, overlap_thr, stuff_area_limit, categories): panoptic_json = [] id_generator = IdGenerator(categories) for idx, img_id in enumerate(img_ids): img = img_id2img[img_id] if idx % 100 == 0: print('Core: {}, {} from {} images processed.'.format( proc_id, idx, len(img_ids))) pan_segm_id = np.zeros((img['height'], img['width']), dtype=np.uint32) used = None annotation = {} annotation['image_id'] = img_id annotation['file_name'] = img['file_name'].replace('.jpg', '.png') segments_info = [] for ann in inst_by_image[img_id]: area = COCOmask.area(ann['segmentation']) if area == 0: continue if used is None: intersect = 0 used = copy.deepcopy(ann['segmentation']) else: intersect = COCOmask.area( COCOmask.merge([used, ann['segmentation']], intersect=True)) if intersect / area > overlap_thr: continue used = COCOmask.merge([used, ann['segmentation']], intersect=False) mask = COCOmask.decode(ann['segmentation']) == 1 if intersect != 0: mask = np.logical_and(pan_segm_id == 0, mask) segment_id = id_generator.get_id(ann['category_id']) panoptic_ann = {} panoptic_ann['id'] = segment_id panoptic_ann['category_id'] = ann['category_id'] pan_segm_id[mask] = segment_id segments_info.append(panoptic_ann) for ann in sem_by_image[img_id]: mask = COCOmask.decode(ann['segmentation']) == 1 mask_left = np.logical_and(pan_segm_id == 0, mask) if mask_left.sum() < stuff_area_limit: continue segment_id = id_generator.get_id(ann['category_id']) panoptic_ann = {} panoptic_ann['id'] = segment_id panoptic_ann['category_id'] = ann['category_id'] pan_segm_id[mask_left] = segment_id segments_info.append(panoptic_ann) annotation['segments_info'] = segments_info panoptic_json.append(annotation) Image.fromarray(id2rgb(pan_segm_id)).save( os.path.join(segmentations_folder, annotation['file_name'])) return panoptic_json
def overlapping_percentage(mask1, mask2): areas = min(mask.area([mask1, mask2])) if areas == 0: return 0 percentage = mask.area(mask.merge([mask1, mask2], intersect=True)) / areas return percentage
def get_segmentation_area_and_bbox(segmentation, image_height, image_width): # Convert into rle rles = mask.frPyObjects(segmentation, image_height, image_width) rle = mask.merge(rles) return mask.area(rle), mask.toBbox(rle)
def polys2mask(polygons, width, height): rles = mask_util.frPyObjects(polygons, height, width) rle = mask_util.merge(rles) mask = np.array(mask_util.decode(rle), dtype=np.float32) return mask
def get_preprocessed_seq_data(self, raw_data, cls): """ Preprocess data for a single sequence for a single class ready for evaluation. Inputs: - raw_data is a dict containing the data for the sequence already read in by get_raw_seq_data(). - cls is the class to be evaluated. Outputs: - data is a dict containing all of the information that metrics need to perform evaluation. It contains the following fields: [num_timesteps, num_gt_ids, num_tracker_ids, num_gt_dets, num_tracker_dets] : integers. [gt_ids, tracker_ids, tracker_confidences]: list (for each timestep) of 1D NDArrays (for each det). [gt_dets, tracker_dets]: list (for each timestep) of lists of detections. [similarity_scores]: list (for each timestep) of 2D NDArrays. Notes: Preprocessing (preproc) occurs in 3 steps. 1) Extract only detections relevant for the class to be evaluated. 2) Match gt dets and tracker dets. Tracker dets that are to a gt det (TPs) are marked as not to be removed. 3) Remove unmatched tracker dets if they fall within an ignore region or are too small, or if that class is marked as an ignore class for that sequence. After the above preprocessing steps, this function also calculates the number of gt and tracker detections and unique track ids. It also relabels gt and tracker ids to be contiguous and checks that ids are unique within each timestep. Note that there is a special 'all' class, which evaluates all of the COCO classes together in a 'class agnostic' fashion. """ # import to reduce minimum requirements from pycocotools import mask as mask_utils # Check that input data has unique ids self._check_unique_ids(raw_data) cls_id = self.class_name_to_class_id[cls] ignore_class_id = cls_id + 100 seq = raw_data['seq'] data_keys = [ 'gt_ids', 'tracker_ids', 'gt_dets', 'tracker_dets', 'tracker_confidences', 'similarity_scores' ] data = {key: [None] * raw_data['num_timesteps'] for key in data_keys} unique_gt_ids = [] unique_tracker_ids = [] num_gt_dets = 0 num_tracker_dets = 0 for t in range(raw_data['num_timesteps']): # Only extract relevant dets for this class if cls == 'all': gt_class_mask = raw_data['gt_classes'][t] < 100 # For waymo, combine predictions for [car, truck, bus, motorcycle] into car, because they are all annotated # together as one 'vehicle' class. elif self.sub_benchmark == 'waymo' and cls == 'car': waymo_vehicle_classes = np.array([3, 4, 6, 8]) gt_class_mask = np.isin(raw_data['gt_classes'][t], waymo_vehicle_classes) else: gt_class_mask = raw_data['gt_classes'][t] == cls_id gt_class_mask = gt_class_mask.astype(np.bool) gt_ids = raw_data['gt_ids'][t][gt_class_mask] if cls == 'all': ignore_regions_mask = raw_data['gt_classes'][t] >= 100 else: ignore_regions_mask = raw_data['gt_classes'][ t] == ignore_class_id ignore_regions_mask = np.logical_or( ignore_regions_mask, raw_data['gt_classes'][t] == 100) if self.sub_benchmark in self.box_gt_benchmarks: gt_dets = raw_data['gt_dets'][t][gt_class_mask] ignore_regions_box = raw_data['gt_dets'][t][ ignore_regions_mask] if len(ignore_regions_box) > 0: ignore_regions_box[:, 2] = ignore_regions_box[:, 2] - ignore_regions_box[:, 0] ignore_regions_box[:, 3] = ignore_regions_box[:, 3] - ignore_regions_box[:, 1] ignore_regions = mask_utils.frPyObjects( ignore_regions_box, self.seq_sizes[seq][0], self.seq_sizes[seq][1]) else: ignore_regions = [] else: gt_dets = [ raw_data['gt_dets'][t][ind] for ind in range(len(gt_class_mask)) if gt_class_mask[ind] ] ignore_regions = [ raw_data['gt_dets'][t][ind] for ind in range(len(ignore_regions_mask)) if ignore_regions_mask[ind] ] if cls == 'all': tracker_class_mask = np.ones_like( raw_data['tracker_classes'][t]) else: tracker_class_mask = np.atleast_1d( raw_data['tracker_classes'][t] == cls_id) tracker_class_mask = tracker_class_mask.astype(np.bool) tracker_ids = raw_data['tracker_ids'][t][tracker_class_mask] tracker_dets = [ raw_data['tracker_dets'][t][ind] for ind in range(len(tracker_class_mask)) if tracker_class_mask[ind] ] tracker_confidences = raw_data['tracker_confidences'][t][ tracker_class_mask] similarity_scores = raw_data['similarity_scores'][t][ gt_class_mask, :][:, tracker_class_mask] tracker_classes = raw_data['tracker_classes'][t][ tracker_class_mask] # Only do preproc if there are ignore regions defined to remove if tracker_ids.shape[0] > 0: # Match tracker and gt dets (with hungarian algorithm) unmatched_indices = np.arange(tracker_ids.shape[0]) if gt_ids.shape[0] > 0 and tracker_ids.shape[0] > 0: matching_scores = similarity_scores.copy() matching_scores[matching_scores < 0.5 - np.finfo('float').eps] = 0 match_rows, match_cols = linear_sum_assignment( -matching_scores) actually_matched_mask = matching_scores[ match_rows, match_cols] > 0 + np.finfo('float').eps # match_rows = match_rows[actually_matched_mask] match_cols = match_cols[actually_matched_mask] unmatched_indices = np.delete(unmatched_indices, match_cols, axis=0) # For unmatched tracker dets remove those that are greater than 50% within an ignore region. # unmatched_tracker_dets = tracker_dets[unmatched_indices, :] # crowd_ignore_regions = raw_data['gt_ignore_regions'][t] # intersection_with_ignore_region = self. \ # _calculate_box_ious(unmatched_tracker_dets, crowd_ignore_regions, box_format='x0y0x1y1', # do_ioa=True) if cls_id in self.seq_ignore_class_ids[seq]: # Remove unmatched detections for classes that are marked as 'ignore' for the whole sequence. to_remove_tracker = unmatched_indices else: unmatched_tracker_dets = [ tracker_dets[i] for i in range(len(tracker_dets)) if i in unmatched_indices ] # For unmatched tracker dets remove those that are too small. tracker_boxes_t = mask_utils.toBbox(unmatched_tracker_dets) unmatched_widths = tracker_boxes_t[:, 2] unmatched_heights = tracker_boxes_t[:, 3] unmatched_size = np.maximum(unmatched_heights, unmatched_widths) min_size = np.min(self.seq_sizes[seq]) / 8 is_too_small = unmatched_size <= min_size + np.finfo( 'float').eps # For unmatched tracker dets remove those that are greater than 50% within an ignore region. if ignore_regions: ignore_region_merged = ignore_regions[0] for mask in ignore_regions[1:]: ignore_region_merged = mask_utils.merge( [ignore_region_merged, mask], intersect=False) intersection_with_ignore_region = self. \ _calculate_mask_ious(unmatched_tracker_dets, [ignore_region_merged], is_encoded=True, do_ioa=True) is_within_ignore_region = np.any( intersection_with_ignore_region > 0.5 + np.finfo('float').eps, axis=1) to_remove_tracker = unmatched_indices[np.logical_or( is_too_small, is_within_ignore_region)] else: to_remove_tracker = unmatched_indices[is_too_small] # For the special 'all' class, you need to remove unmatched detections from all ignore classes and # non-evaluated classes. if cls == 'all': unmatched_tracker_classes = [ tracker_classes[i] for i in range(len(tracker_classes)) if i in unmatched_indices ] is_ignore_class = np.isin(unmatched_tracker_classes, self.seq_ignore_class_ids[seq]) is_not_evaled_class = np.logical_not( np.isin(unmatched_tracker_classes, self.valid_class_ids)) to_remove_all = unmatched_indices[np.logical_or( is_ignore_class, is_not_evaled_class)] to_remove_tracker = np.concatenate( [to_remove_tracker, to_remove_all], axis=0) else: to_remove_tracker = np.array([], dtype=np.int) # remove all unwanted tracker detections data['tracker_ids'][t] = np.delete(tracker_ids, to_remove_tracker, axis=0) data['tracker_dets'][t] = np.delete(tracker_dets, to_remove_tracker, axis=0) data['tracker_confidences'][t] = np.delete(tracker_confidences, to_remove_tracker, axis=0) similarity_scores = np.delete(similarity_scores, to_remove_tracker, axis=1) # keep all ground truth detections data['gt_ids'][t] = gt_ids data['gt_dets'][t] = gt_dets data['similarity_scores'][t] = similarity_scores unique_gt_ids += list(np.unique(data['gt_ids'][t])) unique_tracker_ids += list(np.unique(data['tracker_ids'][t])) num_tracker_dets += len(data['tracker_ids'][t]) num_gt_dets += len(data['gt_ids'][t]) # Re-label IDs such that there are no empty IDs if len(unique_gt_ids) > 0: unique_gt_ids = np.unique(unique_gt_ids) gt_id_map = np.nan * np.ones((np.max(unique_gt_ids) + 1)) gt_id_map[unique_gt_ids] = np.arange(len(unique_gt_ids)) for t in range(raw_data['num_timesteps']): if len(data['gt_ids'][t]) > 0: data['gt_ids'][t] = gt_id_map[data['gt_ids'][t]].astype( np.int) if len(unique_tracker_ids) > 0: unique_tracker_ids = np.unique(unique_tracker_ids) tracker_id_map = np.nan * np.ones((np.max(unique_tracker_ids) + 1)) tracker_id_map[unique_tracker_ids] = np.arange( len(unique_tracker_ids)) for t in range(raw_data['num_timesteps']): if len(data['tracker_ids'][t]) > 0: data['tracker_ids'][t] = tracker_id_map[data['tracker_ids'] [t]].astype(np.int) # Record overview statistics. data['num_tracker_dets'] = num_tracker_dets data['num_gt_dets'] = num_gt_dets data['num_tracker_ids'] = len(unique_tracker_ids) data['num_gt_ids'] = len(unique_gt_ids) data['num_timesteps'] = raw_data['num_timesteps'] data['seq'] = raw_data['seq'] data['frame_size'] = raw_data['frame_size'] # Ensure that ids are unique per timestep. self._check_unique_ids(data, after_preproc=True) return data
def _load_raw_file(self, tracker, seq, is_gt): """Load a file (gt or tracker) in the unified RobMOTS format. If is_gt, this returns a dict which contains the fields: [gt_ids, gt_classes] : list (for each timestep) of 1D NDArrays (for each det). [gt_dets, gt_crowd_ignore_regions]: list (for each timestep) of lists of detections. if not is_gt, this returns a dict which contains the fields: [tracker_ids, tracker_classes, tracker_confidences] : list (for each timestep) of 1D NDArrays (for each det). [tracker_dets]: list (for each timestep) of lists of detections. """ # import to reduce minimum requirements from pycocotools import mask as mask_utils # File location if self.data_is_zipped: if is_gt: zip_file = os.path.join(self.gt_fol, self.split, self.sub_benchmark, 'data.zip') else: zip_file = os.path.join(self.tracker_fol, tracker, 'data.zip') file = seq + '.txt' else: zip_file = None if is_gt: file = os.path.join(self.gt_fol, self.split, self.sub_benchmark, 'data', seq + '.txt') else: file = os.path.join(self.tracker_fol, tracker, self.tracker_sub_fol, self.sub_benchmark, seq + '.txt') # Load raw data from text file read_data, ignore_data = self._load_simple_text_file( file, is_zipped=self.data_is_zipped, zip_file=zip_file, force_delimiters=' ') # Convert data to required format num_timesteps = self.seq_lengths[seq] data_keys = ['ids', 'classes', 'dets'] if not is_gt: data_keys += ['tracker_confidences'] raw_data = {key: [None] * num_timesteps for key in data_keys} for t in range(num_timesteps): time_key = str(t) # list to collect all masks of a timestep to check for overlapping areas (for segmentation datasets) all_valid_masks = [] if time_key in read_data.keys(): try: raw_data['ids'][t] = np.atleast_1d( [det[1] for det in read_data[time_key]]).astype(int) raw_data['classes'][t] = np.atleast_1d( [det[2] for det in read_data[time_key]]).astype(int) if (not is_gt) or (self.sub_benchmark not in self.box_gt_benchmarks): raw_data['dets'][t] = [{ 'size': [int(region[4]), int(region[5])], 'counts': region[6].encode(encoding='UTF-8') } for region in read_data[time_key]] all_valid_masks += [ mask for mask, cls in zip(raw_data['dets'][t], raw_data['classes'][t]) if cls < 100 ] else: raw_data['dets'][t] = np.atleast_2d([ det[4:8] for det in read_data[time_key] ]).astype(float) if not is_gt: raw_data['tracker_confidences'][t] = np.atleast_1d([ det[3] for det in read_data[time_key] ]).astype(float) except IndexError: self._raise_index_error(is_gt, self.sub_benchmark, seq) except ValueError: self._raise_value_error(is_gt, self.sub_benchmark, seq) # no detection in this timestep else: if (not is_gt) or (self.sub_benchmark not in self.box_gt_benchmarks): raw_data['dets'][t] = [] else: raw_data['dets'][t] = np.empty((0, 4)).astype(float) raw_data['ids'][t] = np.empty(0).astype(int) raw_data['classes'][t] = np.empty(0).astype(int) if not is_gt: raw_data['tracker_confidences'][t] = np.empty(0).astype( float) # check for overlapping masks if all_valid_masks: masks_merged = all_valid_masks[0] for mask in all_valid_masks[1:]: if mask_utils.area( mask_utils.merge([masks_merged, mask], intersect=True)) != 0.0: err = 'Overlapping masks in frame %d' % t raise TrackEvalException(err) masks_merged = mask_utils.merge([masks_merged, mask], intersect=False) if is_gt: key_map = { 'ids': 'gt_ids', 'classes': 'gt_classes', 'dets': 'gt_dets' } else: key_map = { 'ids': 'tracker_ids', 'classes': 'tracker_classes', 'dets': 'tracker_dets' } for k, v in key_map.items(): raw_data[v] = raw_data.pop(k) raw_data['num_timesteps'] = num_timesteps raw_data['frame_size'] = self.seq_sizes[seq] raw_data['seq'] = seq return raw_data
def _load_raw_file(self, tracker, seq, is_gt): """Load a file (gt or tracker) in the KITTI MOTS format If is_gt, this returns a dict which contains the fields: [gt_ids, gt_classes] : list (for each timestep) of 1D NDArrays (for each det). [gt_dets]: list (for each timestep) of lists of detections. [gt_ignore_region]: list (for each timestep) of masks for the ignore regions if not is_gt, this returns a dict which contains the fields: [tracker_ids, tracker_classes] : list (for each timestep) of 1D NDArrays (for each det). [tracker_dets]: list (for each timestep) of lists of detections. """ # File location if self.data_is_zipped: if is_gt: zip_file = os.path.join(self.gt_fol, 'data.zip') else: zip_file = os.path.join(self.tracker_fol, tracker, self.tracker_sub_fol + '.zip') file = seq + '.txt' else: zip_file = None if is_gt: file = self.config["GT_LOC_FORMAT"].format( gt_folder=self.gt_fol, seq=seq) else: file = os.path.join(self.tracker_fol, tracker, self.tracker_sub_fol, seq + '.txt') # Ignore regions if is_gt: crowd_ignore_filter = {2: ['10']} else: crowd_ignore_filter = None # Load raw data from text file read_data, ignore_data = self._load_simple_text_file( file, crowd_ignore_filter=crowd_ignore_filter, is_zipped=self.data_is_zipped, zip_file=zip_file, force_delimiters=' ') # Convert data to required format num_timesteps = self.seq_lengths[seq] data_keys = ['ids', 'classes', 'dets'] if is_gt: data_keys += ['gt_ignore_region'] raw_data = {key: [None] * num_timesteps for key in data_keys} for t in range(num_timesteps): time_key = str(t) # list to collect all masks of a timestep to check for overlapping areas all_masks = [] if time_key in read_data.keys(): try: raw_data['dets'][t] = [{ 'size': [int(region[3]), int(region[4])], 'counts': region[5].encode(encoding='UTF-8') } for region in read_data[time_key]] raw_data['ids'][t] = np.atleast_1d([ region[1] for region in read_data[time_key] ]).astype(int) raw_data['classes'][t] = np.atleast_1d([ region[2] for region in read_data[time_key] ]).astype(int) all_masks += raw_data['dets'][t] except IndexError: self._raise_index_error(is_gt, tracker, seq) except ValueError: self._raise_value_error(is_gt, tracker, seq) else: raw_data['dets'][t] = [] raw_data['ids'][t] = np.empty(0).astype(int) raw_data['classes'][t] = np.empty(0).astype(int) if is_gt: if time_key in ignore_data.keys(): try: time_ignore = [{ 'size': [int(region[3]), int(region[4])], 'counts': region[5].encode(encoding='UTF-8') } for region in ignore_data[time_key]] raw_data['gt_ignore_region'][t] = mask_utils.merge( [mask for mask in time_ignore], intersect=False) all_masks += [raw_data['gt_ignore_region'][t]] except IndexError: self._raise_index_error(is_gt, tracker, seq) except ValueError: self._raise_value_error(is_gt, tracker, seq) else: raw_data['gt_ignore_region'][t] = mask_utils.merge( [], intersect=False) # check for overlapping masks if all_masks: masks_merged = all_masks[0] for mask in all_masks[1:]: assert mask_utils.area(mask_utils.merge([masks_merged, mask], intersect=True)) == 0.0, \ "Objects with overlapping masks in frame " + str(t) masks_merged = mask_utils.merge([masks_merged, mask], intersect=False) if is_gt: key_map = { 'ids': 'gt_ids', 'classes': 'gt_classes', 'dets': 'gt_dets' } else: key_map = { 'ids': 'tracker_ids', 'classes': 'tracker_classes', 'dets': 'tracker_dets' } for k, v in key_map.items(): raw_data[v] = raw_data.pop(k) raw_data["num_timesteps"] = num_timesteps raw_data['seq'] = seq return raw_data
def _rle_satellite_match(particles, satellites, match_thresh=0.5): r""" Match satellites in an image to their corresponding particles. Convert particle and satellite masks to RLE format. For each satellite, compute the intersection (fraction of satellite mask overlapping with particle mask) score with all particle masks. If the maximum intersection is above *match_thresh*, the satellite is considered to match with that particle. Otherwise, the satellite is considered unmatched. Parameters ----------- particles, satellites: InstanceSet or Instances object Contains the masks for the powder particles and satellites, respectively. match_thresh: float Float between 0 and 1. If intersection score for potential matches is not above this threshold, then the satellite will not match with a particle. Returns ---------- results: dict Dictionary containing the results in the following format: {'satellite_matches': n_match x 2 array. satellite_matches[i] contains [satellite_idx, particle_idx], the integer indices of the satellite, and particle that the satellite matches with, respectively. 'satellites_unmatched': n_satellite_unmatched element array containing the indices of unmatched satellites. 'particles_unmatched': n_particles_unmatched element array containing the indices of unmatched particles. 'intersection_scores': n_match element array of intersection scores for each of the matches in satellite_matches. 'match_pairs': dictionary. Keys of the dictionary are integer indices of particles that matched with satellites. Values of the dictionary are lists of integer indices of satellites that the particle matched with. Note that a particle can match with multiple satellites, but satellites can only match with a single particle. } """ particles = masks_to_rle(particles) satellites = masks_to_rle(satellites) satellite_matches = [] intersection_scores = [] particles_matched_bool = np.zeros(len(particles), dtype=np.bool) satellites_unmatched = [] for satellite_idx, satellite_mask in enumerate(satellites): intersects = RLE.area([RLE.merge([satellite_mask, pmask], intersect=True) for pmask in particles]) \ / RLE.area(satellite_mask) iscore_amax = np.argmax(intersects) iscore_max = intersects[iscore_amax] if iscore_max > match_thresh: satellite_matches.append([satellite_idx, iscore_amax]) particles_matched_bool[iscore_amax] = True intersection_scores.append(iscore_max) else: satellites_unmatched.append(satellite_idx) particles_unmatched = np.array( [i for i, matched in enumerate(particles_matched_bool) if not matched], np.int) satellite_matches = np.asarray(satellite_matches, np.int) satellites_unmatched = np.asarray(satellites_unmatched, np.int) intersection_scores = np.asarray(intersection_scores) match_pairs = {x: [] for x in np.unique(satellite_matches[:, 1])} for match in satellite_matches: match_pairs[match[1]].append(match[0]) results = { 'satellite_matches': satellite_matches, 'satellites_unmatched': satellites_unmatched, 'particles_unmatched': particles_unmatched, 'intersection_scores': intersection_scores, 'match_pairs': match_pairs } return results
def ann_to_mask(segm, h, w): rles = maskUtils.frPyObjects(segm, h, w) rle = maskUtils.merge(rles) m = maskUtils.decode(rle) return m
def _load_raw_file(self, tracker, seq, is_gt): """Load a file (gt or tracker) in the MOTS Challenge format If is_gt, this returns a dict which contains the fields: [gt_ids, gt_classes] : list (for each timestep) of 1D NDArrays (for each det). [gt_dets]: list (for each timestep) of lists of detections. [gt_ignore_region]: list (for each timestep) of masks for the ignore regions if not is_gt, this returns a dict which contains the fields: [tracker_ids, tracker_classes] : list (for each timestep) of 1D NDArrays (for each det). [tracker_dets]: list (for each timestep) of lists of detections. """ # Only loaded when run to reduce minimum requirements from pycocotools import mask as mask_utils # File location if self.data_is_zipped: if is_gt: zip_file = os.path.join(self.gt_fol, 'data.zip') else: zip_file = os.path.join(self.tracker_fol, tracker, self.tracker_sub_fol + '.zip') file = seq + '.txt' else: zip_file = None if is_gt: file = self.config["GT_LOC_FORMAT"].format( gt_folder=self.gt_fol, seq=seq) else: file = os.path.join(self.tracker_fol, tracker, self.tracker_sub_fol, seq + '.txt') # Ignore regions if is_gt: crowd_ignore_filter = {2: ['10']} else: crowd_ignore_filter = None # Load raw data from text file read_data, ignore_data = self._load_simple_text_file( file, crowd_ignore_filter=crowd_ignore_filter, is_zipped=self.data_is_zipped, zip_file=zip_file, force_delimiters=' ') # Convert data to required format num_timesteps = self.seq_lengths[seq] data_keys = ['ids', 'classes', 'dets'] if is_gt: data_keys += ['gt_ignore_region'] raw_data = {key: [None] * num_timesteps for key in data_keys} # Check for any extra time keys extra_time_keys = [ x for x in read_data.keys() if x not in [str(t + 1) for t in range(num_timesteps)] ] if len(extra_time_keys) > 0: if is_gt: text = 'Ground-truth' else: text = 'Tracking' raise TrackEvalException( text + ' data contains the following invalid timesteps in seq %s: ' % seq + ', '.join([str(x) + ', ' for x in extra_time_keys])) for t in range(num_timesteps): time_key = str(t + 1) # list to collect all masks of a timestep to check for overlapping areas all_masks = [] if time_key in read_data.keys(): try: raw_data['dets'][t] = [{ 'size': [int(region[3]), int(region[4])], 'counts': region[5].encode(encoding='UTF-8') } for region in read_data[time_key]] raw_data['ids'][t] = np.atleast_1d([ region[1] for region in read_data[time_key] ]).astype(int) raw_data['classes'][t] = np.atleast_1d([ region[2] for region in read_data[time_key] ]).astype(int) all_masks += raw_data['dets'][t] except IndexError: self._raise_index_error(is_gt, tracker, seq) except ValueError: self._raise_value_error(is_gt, tracker, seq) else: raw_data['dets'][t] = [] raw_data['ids'][t] = np.empty(0).astype(int) raw_data['classes'][t] = np.empty(0).astype(int) if is_gt: if time_key in ignore_data.keys(): try: time_ignore = [{ 'size': [int(region[3]), int(region[4])], 'counts': region[5].encode(encoding='UTF-8') } for region in ignore_data[time_key]] raw_data['gt_ignore_region'][t] = mask_utils.merge( [mask for mask in time_ignore], intersect=False) all_masks += [raw_data['gt_ignore_region'][t]] except IndexError: self._raise_index_error(is_gt, tracker, seq) except ValueError: self._raise_value_error(is_gt, tracker, seq) else: raw_data['gt_ignore_region'][t] = mask_utils.merge( [], intersect=False) # check for overlapping masks if all_masks: masks_merged = all_masks[0] for mask in all_masks[1:]: if mask_utils.area( mask_utils.merge([masks_merged, mask], intersect=True)) != 0.0: raise TrackEvalException( 'Tracker has overlapping masks. Tracker: ' + tracker + ' Seq: ' + seq + ' Timestep: ' + str(t)) masks_merged = mask_utils.merge([masks_merged, mask], intersect=False) if is_gt: key_map = { 'ids': 'gt_ids', 'classes': 'gt_classes', 'dets': 'gt_dets' } else: key_map = { 'ids': 'tracker_ids', 'classes': 'tracker_classes', 'dets': 'tracker_dets' } for k, v in key_map.items(): raw_data[v] = raw_data.pop(k) raw_data['num_timesteps'] = num_timesteps raw_data['seq'] = seq return raw_data
def __getitem__(self, index): """ Returns: image (ndarray[C, H, W]) boxes [xyxy] """ image_id = self.image_ids[index] targets = self.coco.get_annots(image_id) image = self.coco.get_file_name(image_id) # image = Image.open(os.path.join(self.image_dir, image)).convert('RGB') image = image.replace('\\', '/') # print(os.path.join(self.image_dir, image)) image = cv2.imread(os.path.join(self.image_dir, image), cv2.COLOR_BGR2RGB) # print(image) h, w, _ = image.shape boxes = [] labels = [] masks = [] crowds = [] areas = [] for target in targets: boxes.append(target['bbox']) # labels.append(target['category_id']) # print(target['category_id']) labels.append(self.label_map.get(target['category_id']) - 1) crowds.append(target['iscrowd']) areas.append(target['area']) if self.use_mask: if target['segmentation'] is not None: segment = target['segmentation'] if isinstance(segment, list): rles = mask.frPyObjects(segment, h, w) rle = mask.merge(rles) elif isinstance(segment['counts'], list): rle = mask.frPyObjects(segment, h, w) else: rle = segment masks.append(mask.decode(rle)) masks = np.vstack(masks).reshape(-1, h, w) boxes = np.asarray(boxes, dtype=np.float32) # xywh to xyxy boxes[:, 2] = boxes[:, 0] + boxes[:, 2] boxes[:, 3] = boxes[:, 1] + boxes[:, 3] labels = np.asarray(labels, dtype=np.int64) crowds = np.asarray(crowds, dtype=np.int64) # masks = np.vstack(masks).reshape(-1, h, w) # image = image.transpose((2, 0, 1)) if self.use_mask: targets = { 'boxes': boxes, 'masks': masks, 'labels': labels, 'crowds': crowds, } else: targets = { 'boxes': boxes, 'labels': labels, 'crowds': crowds, } if self.transforms is not None: image, targets = self.transforms(image, targets) # image = np.array(image).transpose(2, 0, 1) # image = image / 255.0 # image = torch.as_tensor(image, dtype=torch.float32) if self.mode == 'train': return image, targets else: return image, targets, h, w
def main(): parser = argparse.ArgumentParser() parser.add_argument("infile", type=str, help="Input predictions") parser.add_argument("outfile", type=str, help="Path to write predictions") args = parser.parse_args() params = vars(args) image_index = get_image_id_info_index() in_predictions = json.load(open(params['infile'])) out_predictions = [] # Since each image has multiple predictions, but we always predict the same saliency mask, group them first image_id_to_predictions = defaultdict(list) for inpred in in_predictions: image_id = inpred['image_id'] image_id_to_predictions[image_id].append(inpred) # --- Load Location of Text images---------------------------------------------------------------------------------- image_id_to_textdets = dict() image_ids = image_id_to_predictions.keys() print 'Loading text regions in images...' for image_id in tqdm(image_ids): # fold = image_index[image_id]['fold'] fold = 'test2017' text_anno_path = osp.join(Paths.ANNO_EXTRA_ROOT, '{}/{}.json'.format(fold, image_id)) g_anno = json.load(open(text_anno_path)) text_dets = [] if 'fullTextAnnotation' in g_anno: # Aggregate all text blocks in this image for page in g_anno['fullTextAnnotation']['pages']: for block in page['blocks']: vrts = bb_to_verts(block['boundingBox']) text_dets.append(vrts) image_id_to_textdets[image_id] = text_dets # --- Process ------------------------------------------------------------------------------------------------------ print 'Processing predictions...' for image_id, inpreds in tqdm(image_id_to_predictions.items()): image_path = image_index[image_id]['image_path'] h, w = get_image_size(image_path) # What's the RLE for the text boxes in this image? text_dets = image_id_to_textdets[image_id] if len(text_dets) > 0: verts = None # Get a list of (x_i, y_i) indicating vertices of all boxes in the image for _det in text_dets: # Convert from 10x1 vector to 5x2 matrix det = _det.reshape([5, 2]) if verts is None: verts = det else: verts = np.concatenate([verts, det]) # Find the convex hull of these points hull = ConvexHull(verts) hull_x, hull_y = verts[hull.vertices, 0], verts[hull.vertices, 1] hull_vrts = np.concatenate([hull_x[:, None], hull_y[:, None]], axis=1) # N x 2 matrix hull_vrts = np.concatenate([hull_vrts, hull_vrts[0, None] ]) # Append first point to the end hull_vrts = np.ndarray.flatten(hull_vrts) # Convert to rle rles = mask_utils.frPyObjects([ hull_vrts.tolist(), ], h, w) rle = mask_utils.merge(rles) # Perform CRF smoothing image_path = image_index[image_id]['image_path'] im = Image.open(image_path) try: rle = refine_rle(im, rle, inference_steps=40, relax_precision=True) except ValueError: pass del im else: # Predict the entire image bimask = np.ones((h, w), order='F', dtype='uint8') rle = mask_utils.encode(bimask) del bimask for inpred in inpreds: image_id = inpred['image_id'] attr_id = inpred['attr_id'] score = inpred['score'] out_predictions.append({ 'image_id': image_id, 'attr_id': attr_id, 'segmentation': rle, 'score': score }) print out_path = params['outfile'] print 'Writing {} predictions to {}'.format(len(out_predictions), out_path) json.dump(out_predictions, open(out_path, 'wb'), indent=2)
def crop_covered_segments(segments, width, height, iou_threshold=0.0, ratio_tolerance=0.001, area_threshold=1, return_masks=False): """ Find all segments occluded by others and crop them to the visible part only. Input segments are expected to be sorted from background to foreground. Args: segments: 1d list of segment RLEs (in COCO format) width: width of the image height: height of the image iou_threshold: IoU threshold for objects to be counted as intersected By default is set to 0 to process any intersected objects ratio_tolerance: an IoU "handicap" value for a situation when an object is (almost) fully covered by another one and we don't want make a "hole" in the background object area_threshold: minimal area of included segments Returns: A list of input segments' parts (in the same order as input): [ [[x1,y1, x2,y2 ...], ...], # input segment #0 parts mask1, # input segment #1 mask (if source segment is mask) [], # when source segment is too small ... ] """ from pycocotools import mask as mask_utils segments = [[s] for s in segments] input_rles = [mask_utils.frPyObjects(s, height, width) for s in segments] for i, rle_bottom in enumerate(input_rles): area_bottom = sum(mask_utils.area(rle_bottom)) if area_bottom < area_threshold: segments[i] = [] if not return_masks else None continue rles_top = [] for j in range(i + 1, len(input_rles)): rle_top = input_rles[j] iou = sum(mask_utils.iou(rle_bottom, rle_top, [0, 0]))[0] if iou <= iou_threshold: continue area_top = sum(mask_utils.area(rle_top)) area_ratio = area_top / area_bottom # If a segment is fully inside another one, skip this segment if abs(area_ratio - iou) < ratio_tolerance: continue # Check if the bottom segment is fully covered by the top one. # There is a mistake in the annotation, keep the background one if abs(1 / area_ratio - iou) < ratio_tolerance: rles_top = [] break rles_top += rle_top if not rles_top and not isinstance(segments[i][0], dict) \ and not return_masks: continue rle_bottom = rle_bottom[0] bottom_mask = mask_utils.decode(rle_bottom).astype(np.uint8) if rles_top: rle_top = mask_utils.merge(rles_top) top_mask = mask_utils.decode(rle_top).astype(np.uint8) bottom_mask -= top_mask bottom_mask[bottom_mask != 1] = 0 if not return_masks and not isinstance(segments[i][0], dict): segments[i] = mask_to_polygons(bottom_mask, area_threshold=area_threshold) else: segments[i] = bottom_mask return segments
def compute_MOTS_metrics_per_sequence(seq_name, gt_seq, results_seq, max_frames, class_id, ignore_class, overlap_function): results_obj = MOTSResults() results_obj.total_num_frames = max_frames + 1 seq_trajectories = defaultdict(list) # To count number of track ids gt_track_ids = set() tr_track_ids = set() # Statistics over the current sequence seqtp = 0 seqfn = 0 seqfp = 0 seqitr = 0 n_gts = 0 n_trs = 0 # Iterate over frames in this sequence for f in range(max_frames + 1): g = [] dc = [] t = [] if f in gt_seq: for obj in gt_seq[f]: if obj.class_id == ignore_class: dc.append(obj) elif obj.class_id == class_id: g.append(obj) gt_track_ids.add(obj.track_id) if f in results_seq: for obj in results_seq[f]: if obj.class_id == class_id: t.append(obj) tr_track_ids.add(obj.track_id) # Handle ignore regions as one large ignore region dc = SegmentedObject(mask=rletools.merge([d.mask for d in dc], intersect=False), class_id=ignore_class, track_id=ignore_class) tracks_valid = [False for _ in range(len(t))] # counting total number of ground truth and tracker objects results_obj.n_gt += len(g) results_obj.n_tr += len(t) n_gts += len(g) n_trs += len(t) # tmp variables for sanity checks and MODSP computation tmptp = 0 tmpfp = 0 tmpfn = 0 tmpc = 0 # this will sum up the overlaps for all true positives tmpcs = [0] * len(g) # this will save the overlaps for all true positives # the reason is that some true positives might be ignored # later such that the corrsponding overlaps can # be subtracted from tmpc for MODSP computation # To associate, simply take for each ground truth the (unique!) detection with IoU>0.5 if it exists # all ground truth trajectories are initially not associated # extend groundtruth trajectories lists (merge lists) for gg in g: seq_trajectories[gg.track_id].append(-1) num_associations = 0 for row, gg in enumerate(g): for col, tt in enumerate(t): c = overlap_function(gg, tt) if c > 0.5: tracks_valid[col] = True results_obj.total_cost += c tmpc += c tmpcs[row] = c seq_trajectories[g[row].track_id][-1] = t[col].track_id # true positives are only valid associations results_obj.tp += 1 tmptp += 1 num_associations += 1 # associate tracker and DontCare areas # ignore tracker in neighboring classes nignoredtracker = 0 # number of ignored tracker detections for i, tt in enumerate(t): overlap = overlap_function(tt, dc, "a") if overlap > 0.5 and not tracks_valid[i]: nignoredtracker += 1 # count the number of ignored tracker objects results_obj.n_itr += nignoredtracker # false negatives = non-associated gt instances # tmpfn += len(g) - num_associations results_obj.fn += len(g) - num_associations # false positives = tracker instances - associated tracker instances # mismatches (mme_t) tmpfp += len(t) - tmptp - nignoredtracker results_obj.fp += len(t) - tmptp - nignoredtracker # tmpfp = len(t) - tmptp - nignoredtp # == len(t) - (tp - ignoredtp) - ignoredtp # self.fp += len(t) - tmptp - nignoredtp # update sequence data seqtp += tmptp seqfp += tmpfp seqfn += tmpfn seqitr += nignoredtracker # sanity checks # - the number of true positives minus ignored true positives # should be greater or equal to 0 # - the number of false negatives should be greater or equal to 0 # - the number of false positives needs to be greater or equal to 0 # otherwise ignored detections might be counted double # - the number of counted true positives (plus ignored ones) # and the number of counted false negatives (plus ignored ones) # should match the total number of ground truth objects # - the number of counted true positives (plus ignored ones) # and the number of counted false positives # plus the number of ignored tracker detections should # match the total number of tracker detections if tmptp < 0: print(tmptp) raise NameError("Something went wrong! TP is negative") if tmpfn < 0: print(tmpfn, len(g), num_associations) raise NameError("Something went wrong! FN is negative") if tmpfp < 0: print(tmpfp, len(t), tmptp, nignoredtracker) raise NameError("Something went wrong! FP is negative") if tmptp + tmpfn != len(g): print("seqname", seq_name) print("frame ", f) print("TP ", tmptp) print("FN ", tmpfn) print("FP ", tmpfp) print("nGT ", len(g)) print("nAss ", num_associations) raise NameError("Something went wrong! nGroundtruth is not TP+FN") if tmptp + tmpfp + nignoredtracker != len(t): print(seq_name, f, len(t), tmptp, tmpfp) print(num_associations) raise NameError("Something went wrong! nTracker is not TP+FP") # compute MODSP MODSP_f = 1 if tmptp != 0: MODSP_f = tmpc / float(tmptp) results_obj.MODSP += MODSP_f assert len(seq_trajectories) == len(gt_track_ids) results_obj.n_gt_trajectories = len(gt_track_ids) results_obj.n_tr_trajectories = len(tr_track_ids) # compute MT/PT/ML, fragments, idswitches for all groundtruth trajectories if len(seq_trajectories) != 0: for g in seq_trajectories.values(): # all frames of this gt trajectory are not assigned to any detections if all([this == -1 for this in g]): results_obj.ML += 1 continue # compute tracked frames in trajectory last_id = g[0] # first detection (necessary to be in gt_trajectories) is always tracked tracked = 1 if g[0] >= 0 else 0 for f in range(1, len(g)): if last_id != g[f] and last_id != -1 and g[f] != -1: results_obj.id_switches += 1 if f < len(g) - 1 and g[f - 1] != g[f] and last_id != -1 and g[f] != -1 and g[f + 1] != -1: results_obj.fragments += 1 if g[f] != -1: tracked += 1 last_id = g[f] # handle last frame; tracked state is handled in for loop (g[f]!=-1) if len(g) > 1 and g[f - 1] != g[f] and last_id != -1 and g[f] != -1: results_obj.fragments += 1 # compute MT/PT/ML tracking_ratio = tracked / float(len(g)) if tracking_ratio > 0.8: results_obj.MT += 1 elif tracking_ratio < 0.2: results_obj.ML += 1 else: # 0.2 <= tracking_ratio <= 0.8 results_obj.PT += 1 return results_obj