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

Refactor and clean. Add tests. Use pyproject and lockfile. #44

Merged
merged 19 commits into from
Nov 11, 2024
Merged
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
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
.gitignore
.github
__pycache__

.venv
.pytest_cache
41 changes: 22 additions & 19 deletions .github/workflows/auto-format.yaml
Original file line number Diff line number Diff line change
@@ -1,37 +1,40 @@
name: auto-format
on: pull_request
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
format:
# Check if the PR is not from a fork
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Run isort to sort dependencies
uses: isort/[email protected]
with:
sort-paths: "src"
configuration: "--profile black"
# Run isort before black so that black has the final say in formatting.
- name: run black to auto-format python code
uses: psf/[email protected]

- uses: actions/setup-python@v4
with:
src: "src"
options: "--verbose --line-length 120"
- name: Check if any files were modikfied
python-version: "3.11"

- name: Install Ruff
run: pip install ruff

- name: Run Ruff format
run: ruff format src/

- name: Run Ruff lint with fixes
run: ruff check --fix src/ --exit-zero

- name: Check if any files were modified
id: git-check
run: echo ::set-output name=modified::$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)
run: echo "modified=$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)" >> $GITHUB_OUTPUT

- name: Push changes if needed
if: steps.git-check.outputs.modified == 'true'
run: |
git config --global user.name 'Auto-format Bot'
git config --global user.email '[email protected]'
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git commit -am "Automatically reformatting code with black and isort"
git push

git commit -am "Automatically reformatting code with ruff"
git push
1 change: 1 addition & 0 deletions .github/workflows/autoassign.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
steps:
- uses: actions/checkout@v4
name: checkout code

- uses: uesteibar/reviewer-lottery@v3
name: randomly assign reviewer
with:
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/publish-dockerhub.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ on:
release:
types: [published]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Log in to Docker Hub
uses: docker/login-action@v3
Expand Down
29 changes: 29 additions & 0 deletions .github/workflows/unit-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Unit Tests

on:
push:
branches: [ main ]
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Run Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v3

- name: Install python
run: uv python install 3.11

- name: Install dependencies
run: uv sync --all-extras --dev

- name: Run tests
run: uv run pytest
27 changes: 21 additions & 6 deletions DEVELOPING.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
# Groundlight Stream Processor

A containerized python application that uses the Groundlight SDK to
process frames from a video stream. This can connect to a web cam over RTSP, a local video file, or a youtube URL.
A containerized python application that uses the Groundlight SDK to process frames from a video stream.
This can connect to a web cam over RTSP, a local video file, or a youtube URL.

## Releases

Releases created in Github will automatically get built and pushed to [dockerhub](https://hub.docker.com/r/groundlight/stream/tags). These are multi-architecture builds including x86 and ARM.
Releases created in Github will automatically get built and pushed to [dockerhub](https://hub.docker.com/r/groundlight/stream/tags). These are multi-architecture builds including x86 and ARM.

## Test Builds
## Build locally

To build and test locally:
To build locally:

``` shell
docker build -t stream:local .
```

## Run
alternatively, run:
```shell
make build
```

## Run locally after building

Now you can run it

``` shell
docker run stream:local -h
```


## Testing

To run the tests, you can run:

```shell
make install-dev
make test
```
38 changes: 32 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
FROM python:3.10-slim
ADD requirements.txt /src/
WORKDIR /src
RUN pip install -r requirements.txt
ADD ./src/ /src/
ENTRYPOINT ["python","stream.py"]
FROM python:3.11-slim-bookworm

# Install dependencies
RUN apt-get update && apt-get install -y \
gcc \
libgl1 \
libglib2.0-0 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# Use uv to install python dependencies.
WORKDIR /app
COPY pyproject.toml uv.lock ./

# Mounting the uv binary like this means that it wont
# be left behind in the final version of the Image.
RUN --mount=from=ghcr.io/astral-sh/uv,source=/uv,target=/bin/uv \
uv sync --no-dev --frozen --no-install-project --no-editable --no-cache

# Add source code
COPY . /app/

# Install the project
RUN --mount=from=ghcr.io/astral-sh/uv,source=/uv,target=/bin/uv \
uv sync --no-dev --frozen --no-editable --no-cache

# Activate the virtual environment
ENV VIRTUAL_ENV=/app/.venv
ENV PATH="/app/.venv/bin:$PATH"

# Run the application
ENTRYPOINT ["python", "-m", "stream"]
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 Groundlight AI
Copyright (c) 2024 Groundlight AI

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
Expand Down
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.PHONY: build install install-dev test

build:
docker build -t stream:local .

install:
pip install -e .

install-dev:
pip install -e .[dev]

test:
pytest

relock:
uv lock
74 changes: 53 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
A containerized python application that uses the [Groundlight](https://www.groundlight.ai/) [Python SDK](https://github.com/groundlight/python-sdk) to
process frames from a video file, device, or stream.

## Table of Contents
- [Groundlight Stream Processor](#groundlight-stream-processor)
- [Table of Contents](#table-of-contents)
- [Download](#download)
- [Usage](#usage)
- [Examples](#examples)
- [Running with a Local MP4 File](#running-with-a-local-mp4-file)
- [Using a YouTube URL](#using-a-youtube-url)
- [Connecting an RTSP Stream](#connecting-an-rtsp-stream)
- [Further Reading](#further-reading)

## Download

This application is easy to use on any system with Docker installed.
Expand All @@ -11,33 +22,53 @@ This application is easy to use on any system with Docker installed.
$ docker pull groundlight/stream
```

## Useage
## Usage

Command line options are displayed like:

``` shell
$ docker run groundlight/stream --help
usage: stream.py [-h] -t TOKEN -d DETECTOR [-e ENDPOINT] [-s STREAM] [-x {infer,device,directory,rtsp,youtube,file,image_url}] [-f FPS] [-v] [-m] [-r THRESHOLD] [-p POSTMOTION] [-i MAXINTERVAL]
[-w WIDTH] [-y HEIGHT] [-c CROP]

Groundlight Stream Processor

Captures frames from a video device, file or stream and sends frames as
image queries to a configured detector using the Groundlight API
A command-line tool that captures frames from a video source and sends them to a Groundlight detector for analysis.

usage: stream [options] -t TOKEN -d DETECTOR
Supports a variety of input sources including:
- Video devices (webcams)
- Video files (mp4, etc)
- RTSP streams
- YouTube videos
- Image directories
- Image URLs

options:
-d, --detector=ID detector id to which the image queries are sent
-e, --endpoint=URL api endpoint [default: https://api.groundlight.ai/device-api]
-f, --fps=FPS number of frames to capture per second. 0 to use maximum rate possible. [default: 5]
-h, --help show this message.
-s, --stream=STREAM id, filename or URL of a video stream (e.g. rtsp://host:port/script?params OR video.mp4 OR *.jpg) [default: 0]
-x, --streamtype=TYPE (optional) type of stream. One of [device, directory, rtsp, youtube, file, image_url] [default: auto-infer]
-t, --token=TOKEN API token to authenticate with the Groundlight API
-v, --verbose enable debug logs
-w, --width=WIDTH resize images to w pixels wide (and scale height proportionately if not set explicitly)
-y, --height=HEIGHT resize images to y pixels high (and scale width proportionately if not set explicitly)
-m, --motion enable motion detection with pixel change threshold percentage (disabled by default)
-r, --threshold=THRESHOLD set detection threshold for motion detection [default: 1]
-p, --postmotion=POSTMOTION minimum number of seconds to capture for every motion detection [default: 1]
-i, --maxinterval=MAXINT maximum number of seconds before sending frames even without motion [default: 1000]
-h, --help show this help message and exit
-t TOKEN, --token TOKEN
Groundlight API token for authentication.
-d DETECTOR, --detector DETECTOR
Detector ID to send ImageQueries to.
-e ENDPOINT, --endpoint ENDPOINT
API endpoint to target. For example, could be pointed at an edge-endpoint proxy server (https://github.com/groundlight/edge-endpoint).
-s STREAM, --stream STREAM
Video source. A device ID, filename, or URL. Defaults to device ID '0'.
-x {infer,device,directory,rtsp,youtube,file,image_url}, --streamtype {infer,device,directory,rtsp,youtube,file,image_url}
Source type. Defaults to 'infer' which will attempt to set this value based on --stream.
-f FPS, --fps FPS Frames per second to capture (0 for max rate). Defaults to 1 FPS.
-v, --verbose Enable debug logging.
-m, --motion Enables motion detection, which is disabled by default.
-r THRESHOLD, --threshold THRESHOLD
Motion detection threshold (% pixels changed). Defaults to 1%.
-p POSTMOTION, --postmotion POSTMOTION
Seconds to capture after motion detected. Defaults to 1 second.
-i MAXINTERVAL, --maxinterval MAXINTERVAL
Max seconds between frames even without motion. Defaults to 1000 seconds.
-w WIDTH, --width WIDTH
Resize width in pixels.
-y HEIGHT, --height HEIGHT
Resize height in pixels.
-c CROP, --crop CROP Crop region, specified as fractions (0-1) of each dimension (e.g. '0.25,0.2,0.8,0.9').
```

Start sending frames and getting predictions and labels using your own API token and detector ID:
Expand All @@ -50,7 +81,8 @@ docker run groundlight/stream \
-f 1
```

## Running with a Local MP4 File
## Examples
### Running with a Local MP4 File

To process frames from a local MP4 file, you need to mount the file from your host machine into the Docker container. Here's how to do it:

Expand All @@ -67,7 +99,7 @@ docker run -v /path/to/video:/videos groundlight/stream \

This command mounts the `/path/to/video` directory on your host machine to the `/videos` directory inside the Docker container. The `-s` parameter is then set to the path of the MP4 file inside the container (`/videos/video.mp4`).

## Using a YouTube URL
### Using a YouTube URL
YouTube URLs can be used to send frames to a detector by passing the video URL to the `-s` parameter:

``` shell
Expand All @@ -83,7 +115,7 @@ docker run groundlight/stream \

Replace `YOUTUBE_URL` with the url of the YouTube video you are interested in.

## Connecting an RTSP Stream
### Connecting an RTSP Stream

To connect an RTSP stream from a camera or other source, you'll need the RTSP URL specific to your device. Check the instructions provided earlier in this document for obtaining the RTSP URL for your camera.

Expand Down
Loading
Loading