From a5c087922b02fc3a015c380413822b75cf7480fd Mon Sep 17 00:00:00 2001 From: Karl Gjelland Date: Tue, 7 Nov 2023 22:51:13 +0100 Subject: [PATCH 1/4] Update v3_file_info.py beamLookUp.BeamLookUp requires largeLens, not highResolution. Replaced "highResolution" with "0" in cls.firstBeamAngle = beamLookUp.BeamLookUp (Line 346), since there is no largeLens parameter in ddf3 file header. --- file_handlers/v3/v3_file_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/file_handlers/v3/v3_file_info.py b/file_handlers/v3/v3_file_info.py index 89f8137..4171355 100644 --- a/file_handlers/v3/v3_file_info.py +++ b/file_handlers/v3/v3_file_info.py @@ -343,7 +343,7 @@ def setWindowLength(configFlags, index, cls): return def setFirstBeamAngle(highResolution, cls): - cls.firstBeamAngle = beamLookUp.BeamLookUp(cls.BEAM_COUNT, highResolution)[-1] + cls.firstBeamAngle = beamLookUp.BeamLookUp(cls.BEAM_COUNT, 0)[-1] return @@ -458,4 +458,4 @@ def v3_getAllFramesData(fhand, version, cls): cls.DATA_SHAPE = cls.FRAMES.shape cls.FRAMES = cls.constructImages(cls.FRAMES) - return \ No newline at end of file + return From 4228cc6a55e7d07cf500d5c8d0295ee12b3c205d Mon Sep 17 00:00:00 2001 From: Karl Gjelland Date: Tue, 7 Nov 2023 22:56:03 +0100 Subject: [PATCH 2/4] Update beamLookUp.py largeLens condition changed to 0 = breakpoints48 and else telephoto48, to account for default no largeLens in DIDSON ddf3. --- file_handlers/beamLookUp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/file_handlers/beamLookUp.py b/file_handlers/beamLookUp.py index b4d5af1..6b85e8b 100644 --- a/file_handlers/beamLookUp.py +++ b/file_handlers/beamLookUp.py @@ -94,10 +94,10 @@ def BeamLookUp(BeamCount,largeLens, beam_angle =False): if BeamCount == 64: breakpoints = breakpoints64 if BeamCount == 48: - if largeLens == 1: - breakpoints = telephoto48 - else: + if largeLens == 0: breakpoints = breakpoints48 + else: + breakpoints = telephoto48 if BeamCount == 135: breakpoints = mp4135 if BeamCount == 18: From e7b70e4aa076661a798ba1a2463ac6ffdacfe377 Mon Sep 17 00:00:00 2001 From: Karl Gjelland Date: Tue, 7 Nov 2023 22:58:43 +0100 Subject: [PATCH 3/4] Update v3_frame_info.py Replaced self.largeLens with 0 in BeamLookUp, since there is no largeLens parameter in ddf3 header --- file_handlers/v3/v3_frame_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/file_handlers/v3/v3_frame_info.py b/file_handlers/v3/v3_frame_info.py index 8b56608..11e7e1c 100644 --- a/file_handlers/v3/v3_frame_info.py +++ b/file_handlers/v3/v3_frame_info.py @@ -575,7 +575,7 @@ def getTransformationMatrix(self): def constructImage(self): I = self.FRAME_DATA.astype(np.uint8) I = cv2.flip( I, 0 ) - allAngles = bl.BeamLookUp(self.BEAM_COUNT, self.largeLens) + allAngles = bl.BeamLookUp(self.BEAM_COUNT, 0) d0 = self.sampleStartDelay * 0.000001 * self.soundSpeed/2 dm = d0 + self.samplePeriod * self.samplesPerBeam * 0.000001 * self.soundSpeed/2 @@ -645,4 +645,4 @@ def getBool(x): return False - \ No newline at end of file + From 8f8465787a3c9c04b0c4e1cb35193cdaa9e3bc80 Mon Sep 17 00:00:00 2001 From: Karl Gjelland Date: Wed, 8 Nov 2023 22:50:55 +0100 Subject: [PATCH 4/4] Aspect is added to detections in interface, but tracking fails at finalization with "fish_manager.py", line 955, in setPathVariables self.aspect = float(np.mean(valid_dets.aspect)) AttributeError: 'list' object has no attribute 'aspect' " --- detection_list.py | 8 ++++++-- detector.py | 22 +++++++++++++--------- fish_manager.py | 30 +++++++++++++++++++++++------- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/detection_list.py b/detection_list.py index 558c44c..29a9d3b 100644 --- a/detection_list.py +++ b/detection_list.py @@ -39,7 +39,7 @@ def rowCount(self, index=None): return self.row_count def columnCount(self, index=None): - return 3 + return 4 def data(self, index, role): if role == QtCore.Qt.DisplayRole: @@ -59,6 +59,8 @@ def data(self, index, role): return round(d.angle, 1) elif col == 2: return round(d.length, 3) + elif col == 3: + return round(d.aspect, 1) else: return "" @@ -68,9 +70,11 @@ def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): if section == 0: return "Distance (m)" elif section == 1: - return "Angle (deg)" + return "Angle (°)" elif section == 2: return "Length (m)" + elif section == 3: + return "Aspect (°)" else: return '{: >4}'.format(section) diff --git a/detector.py b/detector.py index 5d41b8d..d7ad282 100644 --- a/detector.py +++ b/detector.py @@ -348,12 +348,12 @@ def saveDetectionsToFile(self, path): try: with open(path, "w") as file: - file.write("frame;length;distance;angle;corner1 x;corner1 y;corner2 x;corner2 y;corner3 x;corner3 y;corner4 x;corner4 y\n") + file.write("frame;length;distance;angle;aspect;corner1 x;corner1 y;corner2 x;corner2 y;corner3 x;corner3 y;corner4 x;corner4 y\n") for frame, dets in enumerate(self.detections): if dets is not None: for d in dets: if d.corners is not None: - file.write(lineBase1.format(frame, d.length, d.distance, d.angle)) + file.write(lineBase1.format(frame, d.length, d.distance, d.angle, d.aspect)) file.write(d.cornersToString(";")) file.write("\n") LogObject().print("Detections saved to path:", path) @@ -384,15 +384,16 @@ def loadDetectionsFromFile(self, path): length = float(split_line[1]) distance = float(split_line[2]) angle = float(split_line[3]) + aspect = float(split_line[4]) - c1 = [float(split_line[5]), float(split_line[4])] - c2 = [float(split_line[7]), float(split_line[6])] - c3 = [float(split_line[9]), float(split_line[8])] - c4 = [float(split_line[11]), float(split_line[10])] + c1 = [float(split_line[6]), float(split_line[5])] + c2 = [float(split_line[8]), float(split_line[7])] + c3 = [float(split_line[10]), float(split_line[9])] + c4 = [float(split_line[12]), float(split_line[11])] corners = np.array([c1, c2, c3, c4]) det = Detection(0) - det.init_from_file(corners, length, distance, angle) + det.init_from_file(corners, length, distance, angle, aspect) if self.detections[frame] is None: self.detections[frame] = [det] @@ -470,9 +471,10 @@ def __init__(self, label): self.length = 0 self.distance = 0 self.angle = 0 + self.aspect = 0 def __repr__(self): - return "Detection \"{}\" d:{:.1f}, a:{:.1f}".format(self.label, self.distance, self.angle) + return "Detection \"{}\" d:{:.1f}, a:{:.1f}".format(self.label, self.distance, self.angle, self.aspect) def init_from_data(self, data, detection_size, polar_transform): """ @@ -514,8 +516,9 @@ def init_from_data(self, data, detection_size, polar_transform): self.distance, self.angle = polar_transform.cart2polMetric(self.center[0], self.center[1], True) self.distance = float(self.distance) self.angle = float(self.angle / np.pi * 180 + 90) + self.aspect = float(np.arcsin(tvect[0,0]) / np.pi * 180 + 90) # aspect angle in degrees, 0 means that the length axis of the fish is perpendicular to the sound axis. - def init_from_file(self, corners, length, distance, angle): + def init_from_file(self, corners, length, distance, angle, aspect): """ Initialize detection parameters from a csv file. Data is not stored when exporting a csv file, which means it cannot be recovered here. This mainly affects the visualization of the detection. @@ -526,6 +529,7 @@ def init_from_file(self, corners, length, distance, angle): self.length = length self.distance = distance self.angle = angle + self.aspect = aspect def visualize(self, image, color, show_text, show_detection=True): if self.corners is None: diff --git a/fish_manager.py b/fish_manager.py index b566033..f90dca0 100644 --- a/fish_manager.py +++ b/fish_manager.py @@ -633,7 +633,7 @@ def saveToFile(self, path): try: with open(path, "w") as file: - file.write("id;frame;length;distance;angle;direction;corner1 x;corner1 y;corner2 x;corner2 y;corner3 x;corner3 y;corner4 x;corner4 y; detection\n") + file.write("id;frame;length;distance;angle;aspect;direction;corner1 x;corner1 y;corner2 x;corner2 y;corner3 x;corner3 y;corner4 x;corner4 y; detection\n") lines = self.getSaveLines() lines.sort(key = lambda l: (l[0].id, l[1])) @@ -647,7 +647,7 @@ def saveToFile(self, path): def getSaveLines(self): """ Iterates through all the fish and returns a list containing the fish objects, frames the fish appear in, and the following information: - ID, Frame, Length, Angle, Direction, Corner coordinates and wether the values are from a detection or a track. + ID, Frame, Length, Angle, Aspect, Direction, Corner coordinates and wether the values are from a detection or a track. Detection information are preferred over tracks. """ lines = [] @@ -664,7 +664,7 @@ def getSaveLines(self): # Values calculated from detection if detection is not None: length = fish.length if fish.length_overwritten else detection.length - line = lineBase1.format(fish.id, frame, length, detection.distance, detection.angle, fish.direction.name) + line = lineBase1.format(fish.id, frame, length, detection.distance, detection.angle, detection.aspect, fish.direction.name) if detection.corners is not None: line += self.cornersToString(detection.corners, ";") else: @@ -681,8 +681,9 @@ def getSaveLines(self): center = FishEntry.trackCenter(track) distance, angle = polar_transform.cart2polMetric(center[0], center[1], True) angle = float(angle / np.pi * 180 + 90) + aspect = np.nan # not possible to extract this from track or detection files if not stored in the files - line = lineBase1.format(fish.id, frame, length, distance, angle, fish.direction.name) + line = lineBase1.format(fish.id, frame, length, distance, angle, aspect, fish.direction.name) line += self.cornersToString([[track[0], track[1]], [track[2], track[1]], [track[2], track[3]], [track[0], track[3]]], ";") line += ";0" @@ -708,8 +709,9 @@ def loadFromFile(self, path): id = int(split_line[0]) frame = int(split_line[1]) length = float(split_line[2]) - direction = SwimDirection[split_line[5]] - track = [float(split_line[7]), float(split_line[6]), float(split_line[11]), float(split_line[10]), id] + aspect = float(split_line[5]) + direction = SwimDirection[split_line[6]] + track = [float(split_line[8]), float(split_line[7]), float(split_line[12]), float(split_line[11]), id] if id in self.all_fish: f = self.all_fish[id] @@ -804,6 +806,7 @@ class FishEntry(): def __init__(self, id, frame_in=0, frame_out=0): self.id = int(id) self.length = 0 + self.aspect = 0 self.direction = SwimDirection.NONE self.frame_in = frame_in self.frame_out = frame_out @@ -823,11 +826,19 @@ def __init__(self, id, frame_in=0, frame_out=0): self.color_ind = 0 def __repr__(self): - return "FishEntry {}: {:.1f} {}".format(self.id, self.length, self.direction.name) + return "FishEntry {}: {:.1f} {}".format(self.id, self.length, self.aspect, self.direction.name) def dirSortValue(self): return self.direction.value * 10**8 + self.id + def setAspect(self, value): + self.aspect = value + + def setMeanAspect(self): + if not self.length_overwritten: + if len(self.aspects) > 0: + self.aspect = round(float(np.mean(self.lengths)),1) + def setLength(self, value): self.length = value self.length_overwritten = True @@ -853,6 +864,7 @@ def addTrack(self, track, detection, frame): def copy(self): f = FishEntry(self.id, self.frame_in, self.frame_out) f.length = self.length + f.aspect = self.aspect f.direction = self.direction f.tracks = self.tracks.copy() f.lengths = self.lengths.copy() @@ -940,6 +952,7 @@ def setPathVariables(self, inverted, frame_time, meters_per_pixel): else: self.direction = SwimDirection.UP if end_point_distance[1] > 0 else SwimDirection.DOWN + self.aspect = float(np.mean(valid_dets.aspect)) self.mad = abs(valid_dets[-1].angle - valid_dets[0].angle) path_length = self.calculatePathLength(valid_dets) norm_dist = np.linalg.norm(end_point_distance) @@ -962,6 +975,9 @@ def calculatePathLength(self, dets): def setLengths(self): self.lengths = sorted([det.length for _, det in self.tracks.values() if det is not None]) + def setAspects(self): + self.aspects = sorted([det.aspects for _, det in self.tracks.values() if det is not None]) + @staticmethod def trackCenter(track): return [(track[2]+track[0])/2, (track[3]+track[1])/2]