An example of how you use Groundlight to collect information from frames in a video and reconstruct the video with additional annotations. Also a good place to start for dealing with any timeseries problem
- Clone the repo and branch.
- Ensure you have the uv package manager installed. Guide here.
- Install the dependencies with
uv sync. - Build your project on top!
This repo is currently configured to do intelligence on a sample video of a Boston Dynamics robots doing sumersaults. We want to know how long the robot is upsidedown for. See the demo directory for a concrete example of how to use the framework. This includes a notebook demo.ipynb that shows how to use the framework from e2e.
- Put your video (.mp4) in to the
datafolder. You can see we currently have the sample video there. - Use the
split_video.pyscript to split the video into frames. Each frame will be saved to thedata/framesfolder. If your video is long, consider using the--minutesflag if you only need to process the first N minutes.
uv run framework/split_video.py data/boston_dynamics.mp4
# OR if your video is long, you can choose to only process the first N minutes
uv run framework/split_video.py data/boston_dynamics.mp4 --minutes 10This will create a folder at data/frames with all the frames of the video:
data/
frames/
frame_0.jpg
frame_1.jpg
...
frame_1000.jpg
...
-
Update the
.envfile with your Groundlight API token. Just create the token and paste it in. Mind the instructions in the.envfile for the type of account you should use. -
Review the
framework/frames.pyfile. This is a base class for handling each frame of your video and collecting metadata about it. You'll have to inherit from it for your specific use case. See thedemo/robot_frames.pyfile for a concrete example. At minimum, you'll have to implement theprocess_framemethod, which defines the metadata you want to collect about the frame. Optionally, you can also implement theupdate_framemethod, which will update the metadata by re-querying the Groundlight API for an updated result (e.g. human labeling or reprediction). Refrain from putting stateful logic in one's frame class - e.g. logic that depends on the results of previous or future frames, as this will prevent you from taking advantage of the multithreading capabilities of the framework. Put stateful logic in one's analysis function instead (described below). -
Warm up your detectors with the
warm_up_frame_classmethod. This randomly samples from your frames and sends them to GL to warm up the relevant detectors.
from robot_frames import RobotFrame
from framework.utils import warm_up_frame_class
warm_up_frame_class(frame_class=RobotFrame, proportion=0.1)- Process your frames using the prefetcher, which sends the remaining frames to GL to process:
from tqdm.auto import tqdm
from robot_frames import RobotFrame
from framework.prefetcher import FramePrefetcher
from framework.utils import (
get_first_frame_index,
get_last_frame_index,
)
start_index = get_first_frame_index()
end_index = get_last_frame_index()
indicies = list(range(start_index, end_index + 1))
prefetcher = FramePrefetcher(
frame_class=RobotFrame,
indicies=indicies,
action="process",
)
for index in tqdm(indicies, desc="Processing frames"):
frame = prefetcher.get_frame(index)- Update your frames with the latest answers from GL (e.g. reprediction or human labeling):
from tqdm.auto import tqdm
from robot_frames import RobotFrame
from framework.prefetcher import FramePrefetcher
prefetcher = FramePrefetcher(
frame_class=RobotFrame,
indicies=indicies,
action="update",
)
for index in tqdm(indicies, desc="Updating frames"):
frame = prefetcher.get_frame(index)- Check what proportion of your frames have answers:
from framework.utils import proportion_with_answer
from robot_frames import RobotFrame
proportion = proportion_with_answer(
frame_class=RobotFrame,
indices=indicies,
has_answer_function=RobotFrame.has_answer
)
print(f"Proportion of frames with answers: {proportion}")-
Define your analysis function. This is doing your "application logic", e.g. tracking, counting, aggregating, etc. See the
demo/robot_analysis_function.pyfile for a concrete example. Put your stateful logic here. Your analysis function can be a class, allowing you to maintain arbitrarily complex state across frames. -
Run your analysis function on your frames, producing a new video and whatever other outputs you want.
from robot_analysis_function import RobotUpsideDownAnalysis
from robot_frames import RobotFrame
from framework.process_frames import process_frames
analysis_class = RobotUpsideDownAnalysis()
process_frames(
run_name="robot_upside_down_detection",
indices=indicies,
output_path="output.mp4",
analysis_function=analysis_class.analyze_frame,
frame_class=RobotFrame,
input_video_path="../data/boston_dynamics.mp4",
)