Skip to content

Commit

Permalink
init commit
Browse files Browse the repository at this point in the history
  • Loading branch information
pythops committed Nov 19, 2023
0 parents commit bfbb250
Show file tree
Hide file tree
Showing 16 changed files with 523 additions and 0 deletions.
60 changes: 60 additions & 0 deletions .github/workflows/cd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
name: Release
on:
push:
tags:
- "v*"
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust: [stable]
permissions:
contents: write

runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@master

- name: Install just
uses: taiki-e/install-action@just

- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable

- name: Setup build env for Linux
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt update
sudo apt install -y musl-tools perl libssl-dev make
rustup target add x86_64-unknown-linux-musl
- name: Build for Linux
if: matrix.os == 'ubuntu-latest'
run: |
just build-release-linux
cp target/x86_64-unknown-linux-musl/release/ova ova-x86_64-linux-musl
- name: Build for macos
if: matrix.os == 'macos-latest'
run: |
rustup target add aarch64-apple-darwin
just build-release-macos
cp target/x86_64-apple-darwin/release/ova ova-x86_64-macos
cp target/aarch64-apple-darwin/release/ova ova-aarch64-macos
- name: Extract release notes
if: matrix.os == 'ubuntu-latest'
id: release_notes
uses: ffurrer2/extract-release-notes@v1

- name: Release
uses: softprops/action-gh-release@v1
with:
body: ${{ steps.release_notes.outputs.release_notes }}
files: "ova*"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29 changes: 29 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
name: CI
on:
pull_request:
push:
branches:
- "*"
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: taiki-e/install-action@just
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: clippy rustfmt

- name: Install nextest
uses: taiki-e/install-action@nextest

- name: Linting
run: just lint

- name: Tests
run: just test

- name: Debug builds
run: just build
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/target
/output
docs.md
Cargo.lock
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [0.1] 19/11/2023

First release 🎉
31 changes: 31 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "ova"
version = "0.1.0"
edition = "2021"
authors = ["pythops <[email protected]>"]
description = "OpenVisionAPI client"
readme = "README.md"
homepage = "https://openvisionapi.com"
repository = "https://github.com/openvisionapi/ova"

[dependencies]
reqwest = { version = "0.11", default-features = false, features = [
"json",
"blocking",
"multipart",
"rustls-tls",
] }
clap = { version = "4", features = ["derive", "cargo"] }
serde = { version = "1", features = ["derive"] }
imageproc = "0.23"
image = "0.24"
serde_json = "1"
strum = { version = "0.25", features = ["derive"] }
anyhow = "1"
rand = "0.8"
rusttype = "0.9"

[dev-dependencies]
assert_cmd = "2"
mockito = "1"
pretty_assertions = "1"
26 changes: 26 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
default:
@just --list

test:
@cargo nextest run

lint:
@cargo clippy --workspace --all-features -- -D warnings
@cargo fmt --all -- --check

build:
@cargo build

build-release:
@cargo build --release

update:
@cargo upgrade

build-release-linux:
@cargo build --release --target=x86_64-unknown-linux-musl
@strip target/x86_64-unknown-linux-musl/release/ova

build-release-macos:
@cargo build --release --target=x86_64-apple-darwin
@cargo build --release --target=aarch64-apple-darwin
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<div align="center">
<h1> OpenVisionAPI Client </h1>
</div>

## 📦 Setup

### Binary releases

You can download the prebuilt binaries from the [release page](https://github.com/openvisionapi/ova/releases)

### Build from source

To build from the source, you need [Rust](https://www.rust-lang.org/) compiler and
[Cargo package manager](https://doc.rust-lang.org/cargo/).

Once Rust and Cargo are installed, run the following command to build:

```bash
$ cargo build --release
```

This will produce an executable file at `target/release/ova` that you can copy to a directory in your `$PATH`.

## 🚀 Usage

Print the API response in a pretty formatted json :

```bash
$ ova detection -i /path/to/image
```

Draw boxes on the detected objects:

```bash
$ ova detection -v -i /path/to/image
```

## ⚙️ Configuration

The configuration can be set up using the following environment variables:

**OVA_DETECTION_URL** : The URL to the OpenVisionAPI server. The default is `https://api.openvisionapi.com/api/v1/detection`

**OVA_OUTPUT_DIR** : The directory where to store the image. The default is `./output`

## 🤝 Contributing

Your contributions are welcome !

## ⚖️ License

AGPLv3
Binary file added assets/cat.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added font/Montserrat-Regular.ttf
Binary file not shown.
26 changes: 26 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use clap::{arg, ArgAction, Command};
use std::path::PathBuf;

pub fn cli() -> Command {
Command::new("ova")
.about("OVA Client CLI")
.subcommand_required(true)
.subcommand(
Command::new("detection")
.about("Object Detection")
.arg(
arg!(--image <image>)
.short('i')
.required(true)
.help("path to image file")
.value_parser(clap::value_parser!(PathBuf)),
)
.arg(
arg!(--visualize)
.short('v')
.help("Draw boxes on the detected objects")
.action(ArgAction::SetTrue)
.required(false),
),
)
}
41 changes: 41 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::{env, fs, path::PathBuf};
use strum::Display;

#[derive(Deserialize, Debug)]
pub struct Config {
pub detection: Detection,
pub output_dir: PathBuf,
}

#[derive(Deserialize, Debug)]
pub struct Detection {
pub url: String,
pub model: DetectionModel,
}

#[derive(Deserialize, Serialize, Debug, Display)]
pub enum DetectionModel {
#[strum(serialize = "yolov4")]
YOLOV4,
}

impl Config {
pub fn load() -> Result<Self> {
let detection = Detection {
url: env::var("OVA_DETECTION_URL")
.unwrap_or("https://api.openvisionapi.com/api/v1/detection".into()),
model: DetectionModel::YOLOV4,
};

let output_dir = PathBuf::from(env::var("OVA_OUTPUT_DIR").unwrap_or("output".to_string()));

fs::create_dir_all(&output_dir)?;

Ok(Self {
detection,
output_dir,
})
}
}
44 changes: 44 additions & 0 deletions src/detection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use crate::config::Detection as DetectionConfig;
use anyhow::Result;
use reqwest::blocking::multipart;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Debug, Deserialize, Serialize)]
pub struct Detection {
pub description: String,
pub predictions: Vec<Predicition>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Predicition {
pub bbox: Bbox,
pub label: String,
pub score: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Bbox {
pub x1: u16,
pub x2: u16,
pub y1: u16,
pub y2: u16,
}

pub fn detect(im: &PathBuf, config: &DetectionConfig) -> Result<Detection> {
let client = reqwest::blocking::Client::new();

let url = &config.url;

let files = multipart::Form::new()
.file("image", im)?
.part("model", multipart::Part::text(config.model.to_string()));

let response: Detection = client
.post(url)
.multipart(files)
.send()?
.json::<Detection>()?;

Ok(response)
}
50 changes: 50 additions & 0 deletions src/drawing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use crate::detection::Detection;
use image::Pixel;
use image::Rgba;
use image::RgbaImage;
use imageproc::drawing::draw_text_mut;
use rusttype::{Font, Scale};
use std::collections::{HashMap, HashSet};

pub fn draw(im: &mut RgbaImage, detection: Detection) {
let labels: HashSet<String> = detection
.predictions
.iter()
.map(|p| p.label.clone())
.collect();

let mut colors = HashMap::new();

for label in labels {
colors.entry(label).or_insert_with(|| {
Rgba([
rand::random::<u8>(),
rand::random::<u8>(),
rand::random::<u8>(),
64u8,
])
});
}

for prediction in detection.predictions {
let bbox = prediction.bbox;
let label = prediction.label;

for x in bbox.x1..bbox.x2 + 1 {
for y in bbox.y1..bbox.y2 + 1 {
let pixel = im.get_pixel_mut(x as u32, y as u32);
pixel.blend(&colors[&label]);
}
}

draw_text_mut(
im,
colors[&label],
bbox.x1.into(),
(bbox.y1 - 16).into(),
Scale::uniform(20.0),
&Font::try_from_bytes(include_bytes!("../font/Montserrat-Regular.ttf")).unwrap(),
label.as_str(),
);
}
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod cli;
pub mod config;
pub mod detection;
pub mod drawing;
Loading

0 comments on commit bfbb250

Please sign in to comment.