From 44b16b467fb7015245a44703cb8b6ff0bbaafb7b Mon Sep 17 00:00:00 2001 From: Francesco Date: Mon, 26 Jun 2023 21:49:07 +0200 Subject: [PATCH 1/5] refactor: improved code readability tapir tracker --- cellacdc/core.py | 4 +- cellacdc/gui.py | 6 +- cellacdc/myutils.py | 4 +- cellacdc/trackers/TAPIR/TAPIR_tracker.py | 99 ++++++++++++++++-------- 4 files changed, 73 insertions(+), 40 deletions(-) diff --git a/cellacdc/core.py b/cellacdc/core.py index e1de6e27..45e4d608 100755 --- a/cellacdc/core.py +++ b/cellacdc/core.py @@ -576,7 +576,7 @@ def label_3d_segm(labels): return labels -def get_objContours(obj, obj_image=None, all=False): +def get_obj_contours(obj, obj_image=None, all=False): if all: retrieveMode = cv2.RETR_CCOMP else: @@ -602,7 +602,7 @@ def smooth_contours(lab, radius=2): sigma = 2*radius + 1 smooth_lab = np.zeros_like(lab) for obj in skimage.measure.regionprops(lab): - cont = get_objContours(obj) + cont = get_obj_contours(obj) x = cont[:,0] y = cont[:,1] x = np.append(x, x[0:sigma]) diff --git a/cellacdc/gui.py b/cellacdc/gui.py index 9417ffab..b6d12fbf 100755 --- a/cellacdc/gui.py +++ b/cellacdc/gui.py @@ -18411,7 +18411,7 @@ def highlightSearchedID(self, ID, force=False, isHover=False): self.labelsLayerImg1.setOpacity(alpha/3) else: _image = self.getObjImage(obj.image, obj.bbox).astype(np.uint8) - contours = core.get_objContours(obj, obj_image=_image, all=True) + contours = core.get_obj_contours(obj, obj_image=_image, all=True) for cont in contours: self.searchedIDitemLeft.addPoints(cont[:,0], cont[:,1]) @@ -18422,7 +18422,7 @@ def highlightSearchedID(self, ID, force=False, isHover=False): else: if contours is None: _image = self.getObjImage(obj.image, obj.bbox).astype(np.uint8) - contours = core.get_objContours(obj, obj_image=_image, all=True) + contours = core.get_obj_contours(obj, obj_image=_image, all=True) for cont in contours: self.searchedIDitemRight.addPoints(cont[:,0], cont[:,1]) @@ -18759,7 +18759,7 @@ def setOverlayLabelsItems(self): if drawMode == 'Draw contours': for obj in skimage.measure.regionprops(ol_lab): _img = self.getObjImage(obj.image, obj.bbox).astype(np.uint8) - contours = core.get_objContours(obj, obj_image=_img, all=True) + contours = core.get_obj_contours(obj, obj_image=_img, all=True) for cont in contours: contoursItem.addPoints(cont[:,0], cont[:,1]) elif drawMode == 'Overlay labels': diff --git a/cellacdc/myutils.py b/cellacdc/myutils.py index c58b7891..47c66c6f 100644 --- a/cellacdc/myutils.py +++ b/cellacdc/myutils.py @@ -1372,7 +1372,7 @@ def from_lab_to_imagej_rois(lab, ImagejRoi, t=0, SizeT=1, max_ID=None): for z, lab2D in enumerate(lab): rp = skimage.measure.regionprops(lab2D) for obj in rp: - cont = core.get_objContours(obj) + cont = core.get_obj_contours(obj) t_str = str(t).zfill(len(str(SizeT))) z_str = str(z).zfill(len(str(SizeZ))) id_str = str(obj.label).zfill(len(str(max_ID))) @@ -1384,7 +1384,7 @@ def from_lab_to_imagej_rois(lab, ImagejRoi, t=0, SizeT=1, max_ID=None): else: rp = skimage.measure.regionprops(lab) for obj in rp: - cont = core.get_objContours(obj) + cont = core.get_obj_contours(obj) t_str = str(t).zfill(len(str(SizeT))) id_str = str(obj.label).zfill(len(str(max_ID))) name = f't={t_str}-id={id_str}' diff --git a/cellacdc/trackers/TAPIR/TAPIR_tracker.py b/cellacdc/trackers/TAPIR/TAPIR_tracker.py index 8d4f7088..6dba638e 100644 --- a/cellacdc/trackers/TAPIR/TAPIR_tracker.py +++ b/cellacdc/trackers/TAPIR/TAPIR_tracker.py @@ -14,7 +14,7 @@ from cellacdc import printl from cellacdc.transformation import resize_lab -from cellacdc.core import nearest_nonzero_2D +from cellacdc.core import nearest_nonzero_2D, get_obj_contours from ..CellACDC import CellACDC_tracker @@ -24,6 +24,12 @@ class SizesToResize: values = np.arange(256, 1025, 128) +class TrackingInputs: + values = ['Intensity image', 'Segmented objects'] + +class PointsToTrack: + values = ['Centroids', 'Contours'] + class tracker: def __init__( self, model_checkpoint_path: os.PathLike=TAPIR_CHECKPOINT_PATH @@ -42,7 +48,8 @@ def track( max_distance=5, save_napari_tracks=False, use_visibile_information=True, export_to=None, signals=None, export_to_extension='.csv', - track_segmented_objects=False + tracking_input: TrackingInputs='Intensity image', + which_points_to_track: PointsToTrack='Centroids' ): if video_grayscale.ndim == 4: @@ -51,6 +58,7 @@ def track( raise TypeError(msg) self._use_visibile_information = use_visibile_information + self._which_points_to_track = which_points_to_track self.segm_video = segm_video self.max_dist = max_distance num_frames = len(video_grayscale) @@ -75,10 +83,10 @@ def track( frames_rgb = self._get_frames_to_track( reversed_resized_frames, reversed_resized_segm, - track_segmented_objects + tracking_input ) query_points = self._initialize_query_points( - reversed_resized_segm, track_segmented_objects + reversed_resized_segm, tracking_input, which_points_to_track ) import matplotlib.pyplot as plt @@ -104,9 +112,9 @@ def track( def _get_frames_to_track( self, reversed_resized_frames, reversed_resized_segm, - track_segmented_objects + tracking_input ): - if track_segmented_objects: + if tracking_input == 'Segmented objects': frames = np.zeros(reversed_resized_segm.shape, dtype=np.float32) for frame_i, lab in enumerate(reversed_resized_segm): rp = skimage.measure.regionprops(lab) @@ -141,11 +149,7 @@ def _save_tracks(self, export_to): visibles_li = [] Y, X = resized_segm.shape[-2:] for tr, track in enumerate(tqdm(tracks, ncols=100)): - x, y = track[-1] - y_int, x_int = round(y), round(x) - y_int = max(0, min(y_int, Y-1)) - x_int = max(0, min(x_int, X-1)) - track_ID = resized_segm[-1, y_int, x_int] + track_ID = self._get_track_ID(resized_segm, track) for frame_i, (x, y) in enumerate(track): yc = y*self.resize_ratio_height xc = x*self.resize_ratio_width @@ -169,12 +173,9 @@ def to_napari_tracks(self, use_centroids=False): napari_tracks = [] num_frames = len(self.reversed_resized_segm) Y, X = self.reversed_resized_segm.shape[-2:] + resized_segm = self.reversed_resized_segm[::-1] for tr, track in enumerate(tqdm(self.reversed_tracks, ncols=100)): - x, y = track[0] - y_int, x_int = round(y), round(x) - y_int = max(0, min(y_int, Y-1)) - x_int = max(0, min(x_int, X-1)) - track_ID = self.reversed_resized_segm[0, y_int, x_int] + track_ID = self._get_track_ID(resized_segm, track[::-1]) for reversed_frame_i, (x, y) in enumerate(track): visible = self.reversed_visibles[tr, reversed_frame_i] if not visible and self._use_visibile_information: @@ -207,6 +208,21 @@ def _append_napari_point( xc = x*self.resize_ratio_width napari_tracks.append((track_ID, frame_i, yc, xc)) + def _get_track_ID(self, resized_segm, track, frame_i=None, max_dist=None): + if frame_i is None: + # As of now we track in reverse from last frame and we don't + # initialize additional points in later frames + # --> use last frame + frame_i = -1 + Y, X = resized_segm.shape[-2:] + if self._which_points_to_track == 'Centroids': + x, y = track[frame_i] + y_int, x_int = round(y), round(x) + y_int = max(0, min(y_int, Y-1)) + x_int = max(0, min(x_int, X-1)) + track_ID = resized_segm[frame_i, y_int, x_int] + return track_ID + def _apply_tracks(self, reversed_tracks, reversed_visibles): print('Applying tracks data...') @@ -221,11 +237,7 @@ def _apply_tracks(self, reversed_tracks, reversed_visibles): Y, X = resized_segm.shape[-2:] for tr, track in enumerate(tqdm(tracks, ncols=100)): # Get the track ID from last frame (we track in reverse) - x, y = track[-1] - y0, x0 = round(y), round(x) - y0 = max(0, min(y0, Y-1)) - x0 = max(0, min(x0, X-1)) - tracked_ID = resized_segm[-1, y0, x0] + track_ID = self._get_track_ID(resized_segm, track) for frame_i, (x, y) in enumerate(track): if frame_i == 0: continue @@ -238,6 +250,10 @@ def _apply_tracks(self, reversed_tracks, reversed_visibles): x_int = max(0, min(x_int, X-1)) idxs = (frame_i, y_int, x_int) oldID = resized_segm[idxs] + oldID = self._get_track_ID( + resized_segm, track, frame_i=frame_i, + max_dist=self.max_dist + ) if oldID == 0: oldID = nearest_nonzero_2D( resized_segm[frame_i], y, x, max_dist=self.max_dist @@ -248,10 +264,10 @@ def _apply_tracks(self, reversed_tracks, reversed_visibles): if frame_i not in old_IDs_tracks: old_IDs_tracks[frame_i] = [oldID] - tracked_IDs_tracks[frame_i] = [tracked_ID] + tracked_IDs_tracks[frame_i] = [track_ID] else: old_IDs_tracks[frame_i].append(oldID) - tracked_IDs_tracks[frame_i].append(tracked_ID) + tracked_IDs_tracks[frame_i].append(track_ID) tracked_video = self.segm_video.copy() for frame_i in old_IDs_tracks.keys(): @@ -271,22 +287,39 @@ def _apply_tracks(self, reversed_tracks, reversed_visibles): return tracked_video def _initialize_query_points( - self, reversed_resized_segm, track_segmented_objects + self, reversed_resized_segm, tracking_input, + which_points_to_track ): first_lab = reversed_resized_segm[0] first_lab_rp = skimage.measure.regionprops(first_lab) num_objs = len(first_lab_rp) - query_points = np.zeros((num_objs, 3), dtype=int) + if which_points_to_track == 'Centroids': + query_points = np.zeros((num_objs, 3), dtype=int) + else: + all_contours = [] for o, obj in enumerate(first_lab_rp): - if track_segmented_objects: - obj_edt = distance_transform_edt(obj.image) - argmax = np.argmax(obj_edt) - yc_loc, xc_loc = np.unravel_index(argmax, obj_edt.shape) - ymin, xmin, _, _ = obj.bbox - yc, xc = yc_loc+ymin, xc_loc+xmin + if which_points_to_track == 'Centroids': + if tracking_input == 'Segmented objects': + # Track the center of the edt of the object + # since edt is also the input image + obj_edt = distance_transform_edt(obj.image) + argmax = np.argmax(obj_edt) + yc_loc, xc_loc = np.unravel_index(argmax, obj_edt.shape) + ymin, xmin, _, _ = obj.bbox + yc, xc = yc_loc+ymin, xc_loc+xmin + else: + # Track the centroid of the object + yc, xc = obj.centroid + query_points[o, 1:] = int(yc), int(xc) else: - yc, xc = obj.centroid - query_points[o, 1:] = int(yc), int(xc) + contours = get_obj_contours(obj) + all_contours.append(contours) + if which_points_to_track == 'Contours': + all_contours = np.concatenate(all_contours) + nrows = len(all_contours) + query_points = np.zeros((nrows, 3), dtype=int) + query_points[:, 2] = all_contours[:,0] + query_points[:, 1] = all_contours[:,1] return query_points def url_help(): From 02c5478d91bbf805246e21817bceac744e21aa55 Mon Sep 17 00:00:00 2001 From: Francesco Date: Tue, 27 Jun 2023 22:46:38 +0200 Subject: [PATCH 2/5] feat: allow tracking contours --- cellacdc/core.py | 3 + cellacdc/trackers/TAPIR/TAPIR_tracker.py | 124 ++++++++++------------- 2 files changed, 58 insertions(+), 69 deletions(-) diff --git a/cellacdc/core.py b/cellacdc/core.py index 45e4d608..ff8a3201 100755 --- a/cellacdc/core.py +++ b/cellacdc/core.py @@ -60,6 +60,9 @@ def np_replace_values(arr, old_values, new_values): return arr def nearest_nonzero_2D(a, y, x, max_dist=None): + value = a[round(y), round(x)] + if value > 0: + return value r, c = np.nonzero(a) dist = ((r - y)**2 + (c - x)**2) if max_dist is not None: diff --git a/cellacdc/trackers/TAPIR/TAPIR_tracker.py b/cellacdc/trackers/TAPIR/TAPIR_tracker.py index 6dba638e..7849bebd 100644 --- a/cellacdc/trackers/TAPIR/TAPIR_tracker.py +++ b/cellacdc/trackers/TAPIR/TAPIR_tracker.py @@ -85,23 +85,22 @@ def track( reversed_resized_frames, reversed_resized_segm, tracking_input ) - query_points = self._initialize_query_points( + query_points, tracks_start_frames = self._initialize_query_points( reversed_resized_segm, tracking_input, which_points_to_track ) - - import matplotlib.pyplot as plt - plt.imshow(frames_rgb[0]) - plt.plot(query_points[:,2], query_points[:,1], 'r.') - plt.show() + self.tracks_start_frames = tracks_start_frames + + # import matplotlib.pyplot as plt + # plt.imshow(frames_rgb[0]) + # plt.plot(query_points[:,2], query_points[:,1], 'r.') + # plt.show() self.reversed_tracks, self.reversed_visibles = inference( frames_rgb, query_points, self.model_apply, self.params, self.state ) - tracked_video = self._apply_tracks( - self.reversed_tracks, self.reversed_visibles - ) + tracked_video = self._apply_tracks() if save_napari_tracks: self._save_napari_tracks(export_to) @@ -137,8 +136,7 @@ def _save_napari_tracks(self, export_to): df = pd.DataFrame(data=napari_tracks, columns=['ID', 'T', 'Y', 'X']) df.to_csv(napari_tracks_path, index=False) - def _save_tracks(self, export_to): - print('Saving tracks...') + def _build_tracks_table(self): tracks = self.reversed_tracks[:, ::-1] visibles = self.reversed_visibles[:, ::-1] resized_segm = self.reversed_resized_segm[::-1] @@ -147,7 +145,7 @@ def _save_tracks(self, export_to): xx = [] yy = [] visibles_li = [] - Y, X = resized_segm.shape[-2:] + segm_IDs = [] for tr, track in enumerate(tqdm(tracks, ncols=100)): track_ID = self._get_track_ID(resized_segm, track) for frame_i, (x, y) in enumerate(track): @@ -159,14 +157,23 @@ def _save_tracks(self, export_to): xx.append(xc) yy.append(yc) visibles_li.append(visible) + segm_ID = nearest_nonzero_2D( + resized_segm[frame_i], y, x, max_dist=self.max_dist + ) + segm_IDs.append(segm_ID) df = pd.DataFrame({ 'frame_i': frames, - 'track_ID': track_ID, + 'track_ID': segm_IDs, + 'segm_ID': track_IDs, 'y_point': yy, 'x_point': xx, 'visible': visibles_li }).set_index(['frame_i', 'track_ID']).sort_index() - df.to_csv(export_to) + return df + + def _save_tracks(self, export_to): + print('Saving tracks...') + self.df_tracks.to_csv(export_to) def to_napari_tracks(self, use_centroids=False): print('Building napari tracks data...') @@ -180,9 +187,6 @@ def to_napari_tracks(self, use_centroids=False): visible = self.reversed_visibles[tr, reversed_frame_i] if not visible and self._use_visibile_information: continue - y_int, x_int = round(y), round(x) - y_int = max(0, min(y_int, Y-1)) - x_int = max(0, min(x_int, X-1)) self._append_napari_point( napari_tracks, y, x, num_frames, reversed_frame_i, track_ID, use_centroids=use_centroids @@ -208,66 +212,43 @@ def _append_napari_point( xc = x*self.resize_ratio_width napari_tracks.append((track_ID, frame_i, yc, xc)) - def _get_track_ID(self, resized_segm, track, frame_i=None, max_dist=None): - if frame_i is None: - # As of now we track in reverse from last frame and we don't - # initialize additional points in later frames - # --> use last frame - frame_i = -1 + def _get_track_ID(self, resized_segm, track, max_dist=None): Y, X = resized_segm.shape[-2:] - if self._which_points_to_track == 'Centroids': - x, y = track[frame_i] - y_int, x_int = round(y), round(x) - y_int = max(0, min(y_int, Y-1)) - x_int = max(0, min(x_int, X-1)) - track_ID = resized_segm[frame_i, y_int, x_int] + x, y = track[-1] + # frame_i = self.tracks_start_frames[(round(y), round(x))] + # I still don't know how to get the start frame of each track + # because TAPIR returns a float even for the initialized query + # point of each track + frame_i = -1 + y_int, x_int = round(y), round(x) + y_int = max(0, min(y_int, Y-1)) + x_int = max(0, min(x_int, X-1)) + track_ID = resized_segm[frame_i, y_int, x_int] return track_ID - def _apply_tracks(self, reversed_tracks, reversed_visibles): + def _apply_tracks(self): print('Applying tracks data...') - # Restore correct order (we tracked backwards) - tracks = reversed_tracks[:, ::-1] - resized_segm = self.reversed_resized_segm[::-1] - visibles = reversed_visibles[:, ::-1] + self.df_tracks = self._build_tracks_table() + self.df_tracks = self.df_tracks[self.df_tracks.visible>0] # Iterate tracks and determine tracked IDs old_IDs_tracks = {} tracked_IDs_tracks = {} - Y, X = resized_segm.shape[-2:] - for tr, track in enumerate(tqdm(tracks, ncols=100)): - # Get the track ID from last frame (we track in reverse) - track_ID = self._get_track_ID(resized_segm, track) - for frame_i, (x, y) in enumerate(track): - if frame_i == 0: - continue - - visible = visibles[tr, frame_i] - if not visible and self._use_visibile_information: - continue - y_int, x_int = round(y), round(x) - y_int = max(0, min(y_int, Y-1)) - x_int = max(0, min(x_int, X-1)) - idxs = (frame_i, y_int, x_int) - oldID = resized_segm[idxs] - oldID = self._get_track_ID( - resized_segm, track, frame_i=frame_i, - max_dist=self.max_dist - ) - if oldID == 0: - oldID = nearest_nonzero_2D( - resized_segm[frame_i], y, x, max_dist=self.max_dist - ) - - if oldID == 0: - continue - - if frame_i not in old_IDs_tracks: - old_IDs_tracks[frame_i] = [oldID] - tracked_IDs_tracks[frame_i] = [track_ID] - else: - old_IDs_tracks[frame_i].append(oldID) - tracked_IDs_tracks[frame_i].append(track_ID) + for (frame_i, track_ID), df in self.df_tracks.groupby(level=(0,1)): + if track_ID == 0: + continue + + oldID = df['segm_ID'].mode().iloc[0] + if oldID == 0: + continue + + if frame_i not in old_IDs_tracks: + old_IDs_tracks[frame_i] = [oldID] + tracked_IDs_tracks[frame_i] = [track_ID] + else: + old_IDs_tracks[frame_i].append(oldID) + tracked_IDs_tracks[frame_i].append(track_ID) tracked_video = self.segm_video.copy() for frame_i in old_IDs_tracks.keys(): @@ -293,6 +274,7 @@ def _initialize_query_points( first_lab = reversed_resized_segm[0] first_lab_rp = skimage.measure.regionprops(first_lab) num_objs = len(first_lab_rp) + tracks_start_frames = {} if which_points_to_track == 'Centroids': query_points = np.zeros((num_objs, 3), dtype=int) else: @@ -311,16 +293,20 @@ def _initialize_query_points( # Track the centroid of the object yc, xc = obj.centroid query_points[o, 1:] = int(yc), int(xc) + tracks_start_frames[tuple(query_points[0][1:])] = 0 else: contours = get_obj_contours(obj) all_contours.append(contours) + for x, y in contours: + tracks_start_frames[(y, x)] = 0 if which_points_to_track == 'Contours': all_contours = np.concatenate(all_contours) nrows = len(all_contours) query_points = np.zeros((nrows, 3), dtype=int) query_points[:, 2] = all_contours[:,0] query_points[:, 1] = all_contours[:,1] - return query_points + + return query_points, tracks_start_frames def url_help(): return 'https://deepmind-tapir.github.io/' \ No newline at end of file From bbba044f061e92d4c578afaf7be13f6aac9a76c8 Mon Sep 17 00:00:00 2001 From: Francesco Date: Wed, 28 Jun 2023 09:56:29 +0200 Subject: [PATCH 3/5] chore: commit to checkout main branch --- cellacdc/trackers/TAPIR/TAPIR_tracker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cellacdc/trackers/TAPIR/TAPIR_tracker.py b/cellacdc/trackers/TAPIR/TAPIR_tracker.py index 7849bebd..17dcd083 100644 --- a/cellacdc/trackers/TAPIR/TAPIR_tracker.py +++ b/cellacdc/trackers/TAPIR/TAPIR_tracker.py @@ -49,7 +49,8 @@ def track( use_visibile_information=True, export_to=None, signals=None, export_to_extension='.csv', tracking_input: TrackingInputs='Intensity image', - which_points_to_track: PointsToTrack='Centroids' + which_points_to_track: PointsToTrack='Centroids', + number_of_points_per_object: int=8 ): if video_grayscale.ndim == 4: From 54914675266fa676c8ec9bc894087695a1c223ab Mon Sep 17 00:00:00 2001 From: Francesco Date: Sun, 2 Jul 2023 20:08:27 +0200 Subject: [PATCH 4/5] chore: added cellacdc/_tests to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b2057aa1..78b949a6 100755 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ cellacdc/scripts/test1.py cellacdc/scripts/correct_shift_X_old.py cellacdc/scripts/correct_shift_X_old2.py cellacdc/scripts/correct_shift_X_single_old.py +cellacdc/_tests cellacdc/_version.py cellacdc/.qt_for_python cellacdc/metrics/* @@ -49,7 +50,7 @@ cellacdc/_test_all_icons.py cellacdc/test1.py cellacdc/test_download_model.py cellacdc/test_segm.npy -cellacdc/_profile/\spline_to_obj/regression.ipynb +cellacdc/_profile/spline_to_obj/regression.ipynb cellacdc/deprecated cellacdc/bioformats/jars/.old_bioformats_package.jar cellacdc/java From a2e9f6d05c44a98996380725dab64b97591b5be3 Mon Sep 17 00:00:00 2001 From: Francesco Date: Sun, 2 Jul 2023 21:06:25 +0200 Subject: [PATCH 5/5] feat: TAPIR allow tracking contours --- cellacdc/trackers/TAPIR/TAPIR_tracker.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cellacdc/trackers/TAPIR/TAPIR_tracker.py b/cellacdc/trackers/TAPIR/TAPIR_tracker.py index 17dcd083..567b7c6c 100644 --- a/cellacdc/trackers/TAPIR/TAPIR_tracker.py +++ b/cellacdc/trackers/TAPIR/TAPIR_tracker.py @@ -87,7 +87,8 @@ def track( tracking_input ) query_points, tracks_start_frames = self._initialize_query_points( - reversed_resized_segm, tracking_input, which_points_to_track + reversed_resized_segm, tracking_input, which_points_to_track, + number_of_points_per_object ) self.tracks_start_frames = tracks_start_frames @@ -270,7 +271,7 @@ def _apply_tracks(self): def _initialize_query_points( self, reversed_resized_segm, tracking_input, - which_points_to_track + which_points_to_track, number_of_points_per_object ): first_lab = reversed_resized_segm[0] first_lab_rp = skimage.measure.regionprops(first_lab) @@ -296,7 +297,12 @@ def _initialize_query_points( query_points[o, 1:] = int(yc), int(xc) tracks_start_frames[tuple(query_points[0][1:])] = 0 else: - contours = get_obj_contours(obj) + contours = get_obj_contours(obj)[:-1] + if number_of_points_per_object > 1: + num_points = len(contours) + if number_of_points_per_object < num_points: + step = num_points // number_of_points_per_object + contours = contours[::step] all_contours.append(contours) for x, y in contours: tracks_start_frames[(y, x)] = 0