-
Notifications
You must be signed in to change notification settings - Fork 6
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
Example 'aeon/analysis/' refactor #245
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,67 @@ | ||
import cv2 | ||
import math | ||
|
||
import numpy as np | ||
import pandas as pd | ||
import aeon.io.video as video | ||
|
||
|
||
Comment on lines
+3
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
def frames(data): | ||
''' | ||
Extracts the raw frames corresponding to the provided video metadata. | ||
|
||
:param DataFrame data: | ||
A pandas DataFrame where each row specifies video acquisition path and frame number. | ||
:return: | ||
An object to iterate over numpy arrays for each row in the DataFrame, | ||
containing the raw video frame data. | ||
''' | ||
capture = None | ||
filename = None | ||
index = 0 | ||
try: | ||
for frameidx, path in zip(data._frame, data._path): | ||
if filename != path: | ||
if capture is not None: | ||
capture.release() | ||
capture = cv2.VideoCapture(path) | ||
filename = path | ||
index = 0 | ||
|
||
if frameidx != index: | ||
capture.set(cv2.CAP_PROP_POS_FRAMES, frameidx) | ||
index = frameidx | ||
success, frame = capture.read() | ||
if not success: | ||
raise ValueError('Unable to read frame {0} from video path "{1}".'.format(frameidx, path)) | ||
yield frame | ||
index = index + 1 | ||
finally: | ||
if capture is not None: | ||
capture.release() | ||
Comment on lines
+18
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The capture = cv2.VideoCapture(path)
+ if not capture.isOpened():
+ raise IOError('Unable to open video file "{0}".'.format(path))
filename = path |
||
|
||
|
||
def export(frames, file, fps, fourcc=None): | ||
''' | ||
Exports the specified frame sequence to a new video file. | ||
|
||
:param iterable frames: An object to iterate over the raw video frame data. | ||
:param str file: The path to the exported video file. | ||
:param fps: The frame rate of the exported video. | ||
:param optional fourcc: | ||
Specifies the four character code of the codec used to compress the frames. | ||
''' | ||
writer = None | ||
try: | ||
for frame in frames: | ||
if writer is None: | ||
if fourcc is None: | ||
fourcc = cv2.VideoWriter_fourcc('m','p','4','v') | ||
writer = cv2.VideoWriter(file, fourcc, fps, (frame.shape[1], frame.shape[0])) | ||
writer.write(frame) | ||
finally: | ||
if writer is not None: | ||
writer.release() | ||
|
||
|
||
def gridframes(frames, width, height, shape=None): | ||
''' | ||
|
@@ -99,7 +158,7 @@ def collatemovie(clipdata, fun): | |
:return: The sequence of processed frames representing the collated movie. | ||
''' | ||
clipcount = len(clipdata.groupby('clip_sequence').frame_sequence.count()) | ||
allframes = video.frames(clipdata.sort_values(by=['frame_sequence', 'clip_sequence'])) | ||
allframes = frames(clipdata.sort_values(by=['frame_sequence', 'clip_sequence'])) | ||
return groupframes(allframes, clipcount, fun) | ||
|
||
def gridmovie(clipdata, width, height, shape=None): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import numpy as np | ||
|
||
|
||
def distancetravelled(angle, radius=4.0): | ||
''' | ||
Calculates the total distance travelled on the wheel, by taking into account | ||
its radius and the total number of turns in both directions across time. | ||
|
||
:param Series angle: A series of magnetic encoder measurements. | ||
:param float radius: The radius of the wheel, in metric units. | ||
:return: The total distance travelled on the wheel, in metric units. | ||
''' | ||
maxvalue = int(np.iinfo(np.uint16).max >> 2) | ||
jumpthreshold = maxvalue // 2 | ||
turns = angle.astype(int).diff() | ||
clickup = (turns < -jumpthreshold).astype(int) | ||
clickdown = (turns > jumpthreshold).astype(int) * -1 | ||
turns = (clickup + clickdown).cumsum() | ||
distance = 2 * np.pi * radius * (turns + angle / maxvalue) | ||
distance = distance - distance[0] | ||
return distance | ||
Comment on lines
+4
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The def distancetravelled(angle, radius=4.0):
+ if not isinstance(angle, pd.Series):
+ raise TypeError("angle must be a pandas Series")
+ if not isinstance(radius, (int, float)):
+ raise TypeError("radius must be a numeric value")
''' |
||
|
||
def activepatch(wheel, in_patch): | ||
''' | ||
Computes a decision boundary for when a patch is active based on wheel movement. | ||
|
||
:param Series wheel: A pandas Series containing the cumulative distance travelled on the wheel. | ||
:param Series in_patch: A Series of type bool containing whether the specified patch may be active. | ||
:return: A pandas Series specifying for each timepoint whether the patch is active. | ||
''' | ||
exit_patch = in_patch.astype(np.int8).diff() < 0 | ||
in_wheel = (wheel.diff().rolling('1s').sum() > 1).reindex(in_patch.index, method='pad') | ||
epochs = exit_patch.cumsum() | ||
return in_wheel.groupby(epochs).apply(lambda x:x.cumsum()) > 0 | ||
Comment on lines
+23
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The def activepatch(wheel, in_patch):
+ if not isinstance(wheel, pd.Series):
+ raise TypeError("wheel must be a pandas Series")
+ if not isinstance(in_patch, pd.Series):
+ raise TypeError("in_patch must be a pandas Series")
''' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Functions related to training multianimal sleap ID model from Aeon videos would live here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CodeRabbit
There is a minor issue with the code formatting. It's a good practice to always end your file with a newline. This is because some Unix tools might not recognize or play well with the last line if newline is missing.