From f2f1741a779b15839fae97e62e86484e031e79eb Mon Sep 17 00:00:00 2001 From: Jan Grewe Date: Fri, 17 Jan 2025 15:07:11 +0100 Subject: [PATCH] [merger] move code to merger class --- fixtracks/util.py | 245 +++++++++++----------------------------------- 1 file changed, 57 insertions(+), 188 deletions(-) diff --git a/fixtracks/util.py b/fixtracks/util.py index d0a5874..b18918f 100644 --- a/fixtracks/util.py +++ b/fixtracks/util.py @@ -14,6 +14,7 @@ class ProducerSignals(QObject): # start = pyqtSignal(float) # running = pyqtSignal() progress = pyqtSignal(float) + progress2 = pyqtSignal((str, float, float)) class ImageReader(QRunnable): @@ -97,6 +98,9 @@ class Merger(QRunnable): self._right_cut = right_cut self._result = None self._stopRequest = False + self._merged = None + self._current_task = "" + self._mergeprogress = 0.0 for df in [self._left_data, self._right_data]: if not self.check_dataframe(df): self.signals.error.emit("Merger.__init__: Error checking DataFrame structure!") @@ -132,166 +136,6 @@ class Merger(QRunnable): numpy.ndarray 2D array, Coordinates of the bounding box for each detection. Shape: (num_detections, 4) x1, y1, x2, y2 """ - key_columns = [c for c in df.columns if "key_" in c] - box_columns = [c for c in df.columns if "box_" in c] - num_detections = len(df) - num_keypoints = len(key_columns) - dimensions = 2 - keypoints = np.empty((num_detections, num_keypoints, dimensions)) - visibility = np.empty((num_detections, num_keypoints)) - boxcoordinates = np.empty((num_detections, 4)) - - for i, row in df.iterrows(): - for j, k in enumerate(key_columns): - key_data = row[k] - l = list(map(float, list(key_data[1:-1].split(",")))) - keypoints[i, j, :] = l - for j, b in enumerate(box_columns): - boxcoordinates[i, j] = row[b] - if isinstance(row["visible"], str): - vis = list(map(float, row["visible"][1:-1].split())) - visibility[i, :] = vis - else: - visibility[i, :] = row["visible"] - return keypoints, visibility, boxcoordinates - - def sort_detections(self, keypoints, threshold, left=True): - """Categorize the detections into those that are easy (not in the visual overlap zone) and those that are tricky, i.e. right across the threshold. - Detections beyond threshold can be discarded, those across the threshold need to be treated separately. - - Parameters - ---------- - keypoints : np.ndarray - 3d array of keypoint coordinates (num detections, num keypoints, (x,y)) - threshold : int - the threshold line at which the data should be merged - left : bool, optional - whether or not the data is from the left side, controls how the threshold is interpreted, by default True - - Returns - ------- - np.ndarray - The indices of the easy detections - np.ndarray - The tricky detections - """ - if left: - easyindeces = np.where(np.all(keypoints[:,:,0] < threshold, axis=1))[0] - trickyindices = np.where(np.any((keypoints[:,:,0] >= threshold) & - (keypoints[:,:,0] < threshold), axis=1))[0] - else: - easyindeces = np.where(np.all(keypoints[:,:,0] >= threshold, axis=1))[0] - trickyindices = np.where(np.any((keypoints[:,:,0] < threshold) & - (keypoints[:,:,0] >= threshold), axis=1))[0] - return easyindeces, trickyindices - - @pyqtSlot() - def stop_request(self): - self._stopRequest = True - - @pyqtSlot() - def run(self): - max_frames = len(self._left_data) + len(self._right_data) - - logging.debug("Cutting left detections to limit %i", self.left_cut) - self.signals.progress.emit(0.1) - lkeypoints, lquality, lbox = self.to_numpy(self._left_data) - self.signals.progress.emit(0.2) - lframes = self._left_data.frame.values - led, ltd = self.sort_detections(lkeypoints, self.left_cut, left=True) - self.signals.progress.emit(0.3) - logging.debug("Cutting right detections to limit %i", self._right_cut_cut) - rkeypoints, rquality, rbox = self.to_numpy(self.right_data) - rframes = self.right_data.frame.values - red, rtd = self.sort_detections(rkeypoints, self.right_cut, left=False) - rkeypoints[:, :, 0] += (self.left_cut - self.right_cut) - - # here we need to decide what to do with these data points, trust the left, or trust the right perspective? - # we could also discard them. unless it is a lot of data points, not much harm will be done... - # next step after handling the tricky ones is to export the data again to pandas? nixtrack? - # 1. the right coordinates have to adapted! x - right_threshold + left_threshold! - - embed() - exit() - - # logging.debug("Merger: running merge for %i frames", max_frames) - # self._stopRequest = False - # max_frames = max(self._left_data.frame.max(), self._right_data.frame.max()) - # step = max_frames // 100 - # self._result = pd.DataFrame(columns=self._left_data.columns) - - # for frame in range(max_frames): - # if self._stopRequest: - # break - - # lf = self._left_data[self._left_data.frame == frame] - # rf = self._right_data[self._right_data.frame == frame] - # merge_frame(lf, rf, self._left_cut, self._right_cut, self._result) - # if frame % step == 0: - # self.signals.progress.emit(frame/max_frames) - # time.sleep(0.01) - self._signals.finished.emit(True and (not self._stopRequest)) - - @property - def signals(self): - return self._signals - - @property - def result(self): - return self._result - -def merge_frames(left, right, leftcut, rightcut, destination): - # for - pass - -def check_frame(frame, cut, left=True): - """checks whether the detected object is (partially) in the overlapping zone of the two cameras. - A frame is 'ok' if the box is not in the danger zone. - - Parameters - ---------- - frame : pd.Series, - a row of the DataFrame - cut : int - The cut x-position - left : bool, optional - whether we are looking at the right or left frame, by default True - - Returns - ------- - bool - whether or not the - """ - if left: - return not (frame.box_x1 > cut or frame.box_x2 > cut) # any of the box coordinates is beyond the cut - else: - return not (frame.box_x1 < cut or frame.box_x2 < cut) - - -def merge_detections(left_data:pd.DataFrame, right_data: pd.DataFrame, left_cut: int, right_cut: int) ->pd.DataFrame: - """Merge the key-point detections based on the left and right video. Key-points with x-coordinates beyond each of the limits (left and right cut) are discarded. - How to merge the detections WITHIN one of the fish detections??? - - Parameters - ---------- - left_data : pd.DataFrame - Detections based on the left video - right_data : pd.DataFrame - Detections based on the right video. - left_cut : int - Where to cut off and discard the detected key points. - right_cut : int - Where to cut off and discard the detected key points. - - Returns - ------- - pd.DataFrame - merged detections of left and right data - """ - def check_dataframe(df): - return True - - def to_numpy(df): logging.info("Converting to numpy ...") key_columns = [c for c in df.columns if "key_" in c] box_columns = [c for c in df.columns if "box_" in c] @@ -314,10 +158,10 @@ def merge_detections(left_data:pd.DataFrame, right_data: pd.DataFrame, left_cut: visibility[i, :] = vis else: visibility[i, :] = row["visible"] - logging.debug("Converting to numpy done!") + logging.info("Converting to numpy done!") return keypoints, visibility, boxcoordinates - def sort_detections(keypoints, threshold, left=True): + def sort_detections(self, keypoints, threshold, left=True): """Categorize the detections into those that are easy (not in the visual overlap zone) and those that are tricky, i.e. right across the threshold. Detections beyond threshold are ignored, those across the threshold need to be treated separately. @@ -347,8 +191,9 @@ def merge_detections(left_data:pd.DataFrame, right_data: pd.DataFrame, left_cut: trickyindices = np.where(np.any((keypoints[:,:,0] < threshold) & (keypoints[:,:,0] >= threshold), axis=1))[0] return easyindeces, trickyindices - - def select_and_transform(df, keypoints, boxes, quality, frames, valid_detections, left_threshold=None, right_threshold=None): + + def select_and_transform(self, df, keypoints, boxes, quality, frames, valid_detections, + left_threshold=None, right_threshold=None): keypoints = keypoints[valid_detections, :, :] boxes = boxes[valid_detections, :] quality = quality[valid_detections, :] @@ -360,8 +205,8 @@ def merge_detections(left_data:pd.DataFrame, right_data: pd.DataFrame, left_cut: return df, keypoints, quality, boxes, frames - - def to_dataframe(old_left, old_right, lkeypoints, rkeypoints, lboxes, rboxes, lqualities, rqualities, lframes, rframes): + def to_dataframe(self, old_left, old_right, lkeypoints, rkeypoints, lboxes, rboxes, + lqualities, rqualities, lframes, rframes): frames = np.concatenate([lframes, rframes]) sorting = np.argsort(frames) frames = frames[sorting] @@ -391,31 +236,53 @@ def merge_detections(left_data:pd.DataFrame, right_data: pd.DataFrame, left_cut: df = pd.DataFrame(d) return df + def save(self, filename): + if self._merged is None: + logging.error("Saving/pickling merged dataFrame is None!") + return + logging.info("Saving/pickling merged file to %s" % filename) + with open(filename, 'rb') as f: + pickle.dump(self._merged, f) + @pyqtSlot() + def stop_request(self): + self._stopRequest = True - logging.info("Cutting left detections to limit %i", left_cut) - if not check_dataframe(left_data) or not check_dataframe(right_data): - logging.error("Left or right dataframe structure does not match my expectations") - return None - logging.info("Converting to numpy %i", left_cut) - lkeypoints, lquality, lbox = to_numpy(left_data) - rkeypoints, rquality, rbox = to_numpy(right_data) - lframes = left_data.frame.values - rframes = right_data.frame.values - logging.info("Filtering detections") - left_easy, _ = sort_detections(lkeypoints, left_cut, left=True) - right_easy, _ = sort_detections(rkeypoints, right_cut, left=False) - logging.info("Merging and transformation") - ldf, lkeypoints, lquality, lboxes, lframes = select_and_transform(left_data, lkeypoints, lbox, lquality, lframes, left_easy) - rdf, rkeypoints, rquality, rboxes, rframes = select_and_transform(right_data, rkeypoints, rbox, rquality, rframes, right_easy, left_cut, right_cut) - export_df = to_dataframe(ldf, rdf, lkeypoints, rkeypoints, lboxes, rboxes, lquality, rquality, lframes, rframes) + @pyqtSlot() + def run(self): + logging.info("Cutting left detections to limit %i", self._left_cut) + self.signals.progress2.emit("Merging", self._mergeprogress, 0.) + if not self.check_dataframe(self._left_data) or not self.check_dataframe(self._right_data): + logging.error("Left or right dataframe structure does not match my expectations") + return None + logging.info("Converting to numpy... %s", "Left camera") + lkeypoints, lquality, lbox = self.to_numpy(self._left_data) + logging.info("Converting to numpy... %s", "Right camera") + rkeypoints, rquality, rbox = self.to_numpy(self._right_data) + lframes = self._left_data.frame.values + rframes = self._right_data.frame.values + logging.info("Filtering detections") + left_easy, _ = self.sort_detections(lkeypoints, self._left_cut, left=True) + right_easy, _ = self.sort_detections(rkeypoints, self._right_cut, left=False) + logging.info("Merging and transformation") + ldf, lkeypoints, lquality, lboxes, lframes = self.select_and_transform(self._left_data, lkeypoints, lbox, + lquality, lframes, left_easy) + rdf, rkeypoints, rquality, rboxes, rframes = self.select_and_transform(self._right_data, rkeypoints, rbox, + rquality, rframes, right_easy, + self._left_cut, self._right_cut) + self._merged = self.to_dataframe(ldf, rdf, lkeypoints, rkeypoints, lboxes, rboxes, lquality, rquality, + lframes, rframes) + + logging.info("Merging done!") + self._signals.finished.emit(True and (not self._stopRequest)) - filename = "test.pkl" - logging.info("Saving/pickling merged file to %s" % filename) - with open(filename, 'rb') as f: - pickle.dump(export_df, f) + @property + def signals(self): + return self._signals - logging.info("Merging done!") + @property + def result(self): + return self._result def main(): @@ -423,7 +290,9 @@ def main(): left = pd.read_csv("../data/left_tracks.csv", sep=";", index_col=0) logging.info("Loading data right") right = pd.read_csv("../data/right_tracks.csv", sep=";", index_col=0) - merge_detections(left, right, 2000, 300) + # merge_detections(left, right, 2000, 300) + merger = Merger(left, right, 2000, 300 ) + merger.run() if __name__ == "__main__":