Skip to content
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
11 changes: 11 additions & 0 deletions check_camera_blur/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.venv
.mypy_cache
**/*.egg-info
**/.mypy_cache/
**/.venv/
**/__pycache__/
**/.pytest_cache
**/dist/
*.swp
*.pyc
output
4 changes: 4 additions & 0 deletions check_camera_blur/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This file is a directive to pyenv (https://github.com/pyenv/pyenv) to set matching version of Python in this directory.
# If you don't use pyenv, you can safely delete this file.
# The roboto CLI requires Python 3.9 or higher.
3.12
17 changes: 17 additions & 0 deletions check_camera_blur/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
ARG PYTHON_MAJOR=3
ARG PYTHON_MINOR=12
ARG OS_VARIANT=slim-bookworm
FROM --platform=linux/amd64 public.ecr.aws/docker/library/python:${PYTHON_MAJOR}.${PYTHON_MINOR}-${OS_VARIANT}

COPY requirements.runtime.txt ./
RUN python -m pip install --upgrade pip setuptools && python -m pip install -r requirements.runtime.txt

COPY src/check_camera_blur/ ./check_camera_blur

# Force the stdout and stderr streams to be unbuffered to decrease latency when viewing real-time action logs
# and to maximum context captured in logs if this action fails.
ENV PYTHONUNBUFFERED=0

# Pass args to your script at runtime by setting the CMD.
# Those args can be parsed by argparse, for example.
ENTRYPOINT [ "python", "-m", "check_camera_blur" ]
14 changes: 14 additions & 0 deletions check_camera_blur/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# check_camera_blur

Checks if a camera is out of focus. If so, it tags the file and creates Events to highlight corresponding time intervals.

## Getting started

1. Setup a virtual environment specific to this project and install development dependencies, including the `roboto` SDK: `./scripts/setup.sh`
2. Build Docker image: `./scripts/build.sh`
3. Run Action image locally: `./scripts/run.sh <path-to-input-data-directory>`
4. Deploy to Roboto Platform: `./scripts/deploy.sh`

## Action configuration file

This Roboto Action is configured in `action.json`. Refer to Roboto's latest documentation for the expected structure.
19 changes: 19 additions & 0 deletions check_camera_blur/action.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "check_camera_blur",
"description": "Checks if a camera is out of focus. If so, it tags the file and creates Events to highlight corresponding time intervals.",
"compute_requirements": {
"vCPU": 512,
"memory": 1024,
"storage": 21
},
"parameters": [
{
"name": "BLUR_THRESHOLD",
"required": false,
"description": "The threshold parameter defines the sensitivity for detecting blurriness in an image",
"default": 20
}
],

"requires_downloaded_inputs": false
}
7 changes: 7 additions & 0 deletions check_camera_blur/requirements.dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Python packages to install into the this directory's virtual environment
# for the purpose of development, testing, and deployment.

# Install all required runtime dependencies in local virtual environment.
-r requirements.runtime.txt

# Add additional Python packages to install here.
4 changes: 4 additions & 0 deletions check_camera_blur/requirements.runtime.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Python packages to install within the Docker image associated with this Action.
roboto
numpy
opencv-python-headless
14 changes: 14 additions & 0 deletions check_camera_blur/scripts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash

set -euo pipefail

SCRIPTS_ROOT=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)
PACKAGE_ROOT=$(dirname "${SCRIPTS_ROOT}")

build_subcommand=(build)
# if buildx is installed, use it
if docker buildx version &> /dev/null; then
build_subcommand=(buildx build --platform linux/amd64 --output type=image)
fi

docker "${build_subcommand[@]}" -f $PACKAGE_ROOT/Dockerfile -t check_camera_blur:latest $PACKAGE_ROOT
50 changes: 50 additions & 0 deletions check_camera_blur/scripts/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env bash

set -euo pipefail

SCRIPTS_ROOT=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)
PACKAGE_ROOT=$(dirname "${SCRIPTS_ROOT}")

# Early exit if virtual environment does not exist and/or roboto is not yet installed
if [ ! -f "$PACKAGE_ROOT/.venv/bin/roboto" ]; then
echo "Virtual environment with roboto SDK does not exist. Please run ./scripts/setup.sh first."
exit 1
fi

# Set org_id to $ROBOTO_ORG_ID if defined, else the first argument passed to this script
org_id=${ROBOTO_ORG_ID:-}
if [ $# -gt 0 ]; then
org_id=$1
fi

roboto_exe="$PACKAGE_ROOT/.venv/bin/roboto"

echo "Pushing check_camera_blur:latest to Roboto's private registry"
image_push_args=(
--suppress-upgrade-check
images push
--quiet
)
if [[ -n $org_id ]]; then
image_push_args+=(--org $org_id)
fi
image_push_args+=(check_camera_blur:latest)
image_push_ret_code=0
image_uri=$($roboto_exe "${image_push_args[@]}")
image_push_ret_code=$?

if [ $image_push_ret_code -ne 0 ]; then
echo "Failed to push check_camera_blur:latest to Roboto's private registry"
exit 1
fi

echo "Creating check_camera_blur action"
create_args=(
--from-file $PACKAGE_ROOT/action.json
--image $image_uri
--yes
)
if [[ -n $org_id ]]; then
create_args+=(--org $org_id)
fi
$roboto_exe actions create "${create_args[@]}"
40 changes: 40 additions & 0 deletions check_camera_blur/scripts/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash

set -euo pipefail

SCRIPTS_ROOT=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)
PACKAGE_ROOT=$(dirname "${SCRIPTS_ROOT}")

# Set input_dir to one of: the first argument passed to this script, $ROBOTO_INPUT_DIR, or the package root
input_dir=${ROBOTO_INPUT_DIR:-$PACKAGE_ROOT}
if [ $# -gt 0 ]; then
input_dir=$1
fi

# Set output_dir variable to $ROBOTO_OUTPUT_DIR if defined, else set it to "output/" in the package root (creating if necessary)
output_dir=${ROBOTO_OUTPUT_DIR:-$PACKAGE_ROOT/output}
mkdir -p $output_dir

# Assert both directories are absolute paths
if [[ ! "$input_dir" = /* ]]; then
echo "Input directory '$input_dir' must be specified as an absolute path"
exit 1
fi

if [[ ! "$output_dir" = /* ]]; then
echo "Output directory '$output_dir' must be specified as an absolute path"
exit 1
fi

docker run --rm -it \
-u $(id -u):$(id -g) \
-v ~/.roboto/config.json:/roboto.config.json \
-v $input_dir:/input \
-v $output_dir:/output \
-e ROBOTO_CONFIG_FILE=/roboto.config.json \
-e ROBOTO_INPUT_DIR=/input \
-e ROBOTO_OUTPUT_DIR=/output \
-e ROBOTO_DATASET_ID=NOT_SET \
-e ROBOTO_INVOCATION_ID=NOT_SET \
-e ROBOTO_ORG_ID=NOT_SET \
check_camera_blur:latest
15 changes: 15 additions & 0 deletions check_camera_blur/scripts/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash

set -euo pipefail

SCRIPTS_ROOT=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)
PACKAGE_ROOT=$(dirname "${SCRIPTS_ROOT}")

venv_dir="$PACKAGE_ROOT/.venv"

# Create a virtual environment
python -m venv --clear --upgrade-deps $venv_dir

# Install dev deps
pip_exe="$venv_dir/bin/pip"
$pip_exe install --upgrade -r $PACKAGE_ROOT/requirements.dev.txt
Empty file.
37 changes: 37 additions & 0 deletions check_camera_blur/src/check_camera_blur/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import os
import cv2
from roboto import ActionRuntime, Event
from check_camera_blur.utils import to_cv2_img, get_blur_events

runtime = ActionRuntime.from_env()
inputs = runtime.get_input()

BLUR_THRESHOLD = int(os.getenv("ROBOTO_PARAM_BLUR_THRESHOLD", "20"))

def is_blurry(img, threshold=BLUR_THRESHOLD):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
return cv2.Laplacian(gray, cv2.CV_64F).var() < threshold

for file, path in inputs.files:

topic = file.get_topic("/camera")
topic_data = topic.get_data()
blur_info = {}

for i, msg in enumerate(topic_data):
img = to_cv2_img(msg)
blur_info[msg["log_time"]] = is_blurry(img)

if any(blur_info.values()):
file.put_tags(["camera_blur", "needs_review"])

events = get_blur_events(blur_info)

for start_time, end_time in events:
Event.create(
start_time=start_time,
end_time=end_time,
name="Blurry Segment",
description="Continuous blurry image segment detected.",
topic_ids=[topic.topic_id],
caller_org_id=runtime.org_id)
28 changes: 28 additions & 0 deletions check_camera_blur/src/check_camera_blur/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import numpy as np
import cv2

def to_cv2_img(msg):
return cv2.imdecode(np.frombuffer(msg["data"], np.uint8), cv2.IMREAD_COLOR)

def get_blur_events(blur_info):
"""
Scan the `blur_info` dictionary (key is timestamp, value is bool indicating blur)
and return a list of event tuples (start_time, end_time) for each blurry stretch.
"""
event_tuples = []
segment_start = None # None -> currently *not* inside a blur section

for timestamp, is_blur in blur_info.items():
if is_blur:
# Enter (or stay in) a blurry stretch
segment_start = segment_start or timestamp
elif segment_start:
# We just left a blurry stretch → close it
event_tuples.append((segment_start, timestamp))
segment_start = None # reset

# If the last segment ends while still blurry -> close it
if segment_start:
event_tuples.append((segment_start, max(blur_info.keys()))) # Using max timestamp as end

return event_tuples