Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aspect angle feature #8

Open
wants to merge 4 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions detection_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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 ""

Expand All @@ -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)

Expand Down
22 changes: 13 additions & 9 deletions detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand Down
6 changes: 3 additions & 3 deletions file_handlers/beamLookUp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions file_handlers/v3/v3_file_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -458,4 +458,4 @@ def v3_getAllFramesData(fhand, version, cls):
cls.DATA_SHAPE = cls.FRAMES.shape

cls.FRAMES = cls.constructImages(cls.FRAMES)
return
return
4 changes: 2 additions & 2 deletions file_handlers/v3/v3_frame_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -645,4 +645,4 @@ def getBool(x):
return False




30 changes: 23 additions & 7 deletions fish_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]))
Expand All @@ -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 = []
Expand All @@ -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:
Expand All @@ -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"

Expand All @@ -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]
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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)
Expand All @@ -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]
Expand Down