{"id":15020681,"url":"https://github.com/pyronear/pyro-engine","last_synced_at":"2026-04-17T12:01:34.633Z","repository":{"id":43062511,"uuid":"292482058","full_name":"pyronear/pyro-engine","owner":"pyronear","description":"Wildfire detection on edge devices","archived":false,"fork":false,"pushed_at":"2026-04-14T05:40:43.000Z","size":13790,"stargazers_count":15,"open_issues_count":28,"forks_count":7,"subscribers_count":5,"default_branch":"develop","last_synced_at":"2026-04-14T07:28:36.180Z","etag":null,"topics":["docker","image-classification","onnxruntime","python","raspberry-pi","wildfire"],"latest_commit_sha":null,"homepage":"https://pyronear.org/pyro-engine","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pyronear.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"pyronear","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2020-09-03T06:08:11.000Z","updated_at":"2026-04-09T09:28:34.000Z","dependencies_parsed_at":"2023-09-22T22:17:10.819Z","dependency_job_id":"0a116193-f3ee-4957-b35b-79b36fcaf861","html_url":"https://github.com/pyronear/pyro-engine","commit_stats":{"total_commits":169,"total_committers":6,"mean_commits":"28.166666666666668","dds":0.2899408284023669,"last_synced_commit":"a6187311cdadb893eb24f1193516bd371cb4a522"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/pyronear/pyro-engine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyronear%2Fpyro-engine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyronear%2Fpyro-engine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyronear%2Fpyro-engine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyronear%2Fpyro-engine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pyronear","download_url":"https://codeload.github.com/pyronear/pyro-engine/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyronear%2Fpyro-engine/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31928229,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-17T10:35:34.458Z","status":"ssl_error","status_checked_at":"2026-04-17T10:35:09.472Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","image-classification","onnxruntime","python","raspberry-pi","wildfire"],"created_at":"2024-09-24T19:55:25.779Z","updated_at":"2026-04-17T12:01:34.569Z","avatar_url":"https://github.com/pyronear.png","language":"Python","readme":"![PyroNear Logo](docs/source/_static/img/pyronear-logo-dark.png)\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/pyronear/pyro-engine/actions?query=workflow%3Atests\"\u003e\n    \u003cimg alt=\"CI Status\" src=\"https://img.shields.io/github/actions/workflow/status/pyronear/pyro-engine/tests.yml?branch=develop\u0026label=CI\u0026logo=github\u0026style=flat-square\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/pyronear/pyro-engine/actions?query=workflow%3Adocs\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/pyronear/pyro-engine/docs.yml?branch=main\u0026label=docs\u0026logo=read-the-docs\u0026style=flat-square\" alt=\"Documentation Status\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/pyronear/pyro-engine\"\u003e\n    \u003cimg src=\"https://img.shields.io/codecov/c/github/pyronear/pyro-engine.svg?logo=codecov\u0026style=flat-square\" alt=\"Test coverage percentage\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/ambv/black\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square\" alt=\"black\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.codacy.com/gh/pyronear/pyro-engine/dashboard?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=pyronear/pyro-engine\u0026amp;utm_campaign=Badge_Grade\"\u003e\u003cimg src=\"https://app.codacy.com/project/badge/Grade/108f5fe8a7ac4f40a7bbd1985e26d5f9\"/\u003e\u003c/a\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://pypi.org/project/pyroengine/\"\u003e\n    \u003cimg src=\"https://img.shields.io/pypi/v/pyroengine.svg?logo=python\u0026logoColor=fff\u0026style=flat-square\" alt=\"PyPi Version\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://hub.docker.com/r/pyronear/pyro-engine\"\u003e\n    \u003cimg alt=\"DockerHub version\" src=\"https://img.shields.io/docker/v/pyronear/pyro-engine/latest?label=Docker\u0026logo=Docker\u0026logoColor=white\"\u003e\n  \u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/python-3.11%2B-blue?style=flat-square\u0026logo=python\u0026logoColor=fff\" alt=\"Supported Python versions\"\u003e\n  \u003cimg src=\"https://img.shields.io/pypi/l/pyroengine.svg?style=flat-square\" alt=\"license\"\u003e\n\u003c/p\u003e\n\n# PyroEngine repository\n\nThis repository contains two main components used in Pyronear wildfire detection deployments:  `pyroengine` and `pyro_camera_api`\n\n| Directory          | Description                                                               |\n| ------------------ | ------------------------------------------------------------------------- |\n| `pyroengine/`      | Detection engine that runs the wildfire model on edge devices             |\n| `pyro_camera_api/` | Camera control API that exposes a unified interface for multiple adapters |\n| `scripts/`         | Shell scripts for deployment and debugging                                |\n| `src/`             | Helper scripts for camera control, focus, calibration and experiments     |\n| `tests/`           | Test suite for the detection engine                                       |\n\nBoth components can run independently or together in the same deployment.\n\n---\n\n## PyroEngine: wildfire detection on edge devices\n\nPyroEngine provides a high level interface to use deep learning models in production while being connected to the alert API.\n\n### Quick example\n\n```python\nfrom pyroengine.core import Engine\nfrom PIL import Image\n\nengine = Engine()\n\nim = Image.open(\"path/to/your/image.jpg\").convert(\"RGB\")\n\nprediction = engine.predict(im)\n```\n\n---\n\n## PyroCamera API: unified camera control\n\nThe `pyro_camera_api` package provides a REST API and a Python client to control cameras and retrieve images in a unified way.\n\nThe API supports multiple camera adapters through a common abstraction:\n\n* `reolink` adapter, for Reolink PTZ or static cameras\n* `linovision` adapter, for Linovision/Hikvision PTZ cameras using ISAPI\n* `rtsp` adapter, for RTSP streams\n* `url` adapter, for HTTP snapshot URLs\n* `mock` adapter, for development and tests\n\nEach adapter has its own class and inherits from the same base interface. The system selects the correct implementation at runtime based on the `adapter` field in `credentials.json`. The API routes are the same for all camera types. PTZ cameras use pose and movement, other cameras ignore pose parameters without failing.\n\n### Simple API usage example\n\n```python\nfrom pyro_camera_api_client import Client\n\nclient = Client(\"http://localhost:8000\")\n\n# Move a PTZ camera to a pose\nclient.move(\"ptz_camera_1\", pose=2)\n\n# Get a snapshot as bytes\nimage_bytes = client.snapshot(\"url_camera_1\")\n```\n\n---\n\n## Setup\n\nPython 3.11 or higher and `pip` or `conda` are required.\n\n### Developer installation\n\n```bash\ngit clone https://github.com/pyronear/pyro-engine.git\npip install -e pyro-engine/.\n```\n\n### Environment variables\n\nDeployments usually rely on a `.env` file. A `.env.example` is provided at the root of the repository as a reference:\n\n```text\nAPI_URL=https://api.pyronear.org\nCAM_USER=my_dummy_login\nCAM_PWD=my_dummy_pwd\nMEDIAMTX_SERVER_IP=1.2.3.4\nPYRO_ENGINE_VERSION=latest\n```\n\n`PYRO_ENGINE_VERSION` controls which Docker image tag is pulled for both services (defaults to `latest` if unset).\n\n### Data directory\n\nA `./data` directory is expected with at least:\n\n* `credentials.json`, see [Camera configuration ](#camera-configuration-and-backends) to understand how to fill it\n* optionally `model.onnx` to override weights from Hugging Face\n* optionally `config.json` to override model configuration\n\n---\n\n## Camera configuration and adapters\n\nThe camera configuration is stored in `credentials.json`. Each key represents a camera identifier and the entry defines how to access and control it.\n\nThe important field for the camera API is:\n\n* `adapter` which selects the camera implementation\n\nOther fields such as `type`, `pose_ids`, `poses`, or `bbox_mask_url` are used by the engine and the API.\n\n`pose_ids` contains the pose IDs from the pyro-api database. For static cameras it is a list with a single element; for PTZ cameras each entry corresponds positionally to the matching physical preset in `poses`. If `bbox_mask_url` is set, occlusion mask files are fetched at `{bbox_mask_url}_{pose_id}.json`.\n\nBelow is one generic example for each adapter: `url`, `rtsp`, `reolink` static, `reolink` PTZ and `mock`.\n```json\n{\n  \"url_camera_1\": {\n    \"name\": \"url_camera_1\",\n    \"adapter\": \"url\",\n    \"url\": \"http://user:password@camera-host:1234/cgi-bin/snapshot.cgi\",\n    \"pose_ids\": [10],\n    \"id\": \"10\",\n    \"bbox_mask_url\": \"\",\n    \"poses\": [],\n    \"token\": \"JWT_TOKEN_HERE\",\n    \"type\": \"static\"\n  },\n\n  \"rtsp_camera_1\": {\n    \"name\": \"rtsp_camera_1\",\n    \"adapter\": \"rtsp\",\n    \"rtsp_url\": \"rtsp://user:password@camera-host:554/live/STREAM_ID\",\n    \"pose_ids\": [11],\n    \"id\": \"11\",\n    \"bbox_mask_url\": \"https://example.com/occlusion-masks/rtsp_camera_1\",\n    \"poses\": [],\n    \"token\": \"JWT_TOKEN_HERE\",\n    \"type\": \"static\"\n  },\n\n  \"192.168.1.11\": {\n    \"name\": \"reolink_static_1\",\n    \"adapter\": \"reolink\",\n    \"type\": \"static\",\n    \"pose_ids\": [12],\n    \"id\": \"12\",\n    \"poses\": [],\n    \"bbox_mask_url\": \"https://example.com/occlusion-masks/reolink_static_1\",\n    \"token\": \"JWT_TOKEN_HERE\"\n  },\n\n  \"192.168.1.12\": {\n    \"name\": \"reolink_ptz_1\",\n    \"adapter\": \"reolink\",\n    \"type\": \"ptz\",\n    \"id\": \"13\",\n    \"poses\": [0, 1, 2, 3],\n    \"pose_ids\": [20, 21, 22, 23],\n    \"bbox_mask_url\": \"https://example.com/occlusion-masks/reolink_ptz_1\",\n    \"token\": \"JWT_TOKEN_HERE\"\n  },\n\n  \"192.168.1.13\": {\n    \"name\": \"linovision_ptz_1\",\n    \"adapter\": \"linovision\",\n    \"type\": \"ptz\",\n    \"poses\": [0, 1, 2, 3],\n    \"pose_ids\": [30, 31, 32, 33],\n    \"azimuth_offset_deg\": 90,\n    \"bbox_mask_url\": \"https://example.com/occlusion-masks/linovision_ptz_1\",\n    \"token\": \"JWT_TOKEN_HERE\"\n  },\n\n  \"mock_camera_1\": {\n    \"name\": \"mock_camera_1\",\n    \"adapter\": \"mock\",\n    \"type\": \"static\",\n    \"pose_ids\": [0],\n    \"id\": \"14\",\n    \"poses\": [],\n    \"bbox_mask_url\": \"\",\n    \"token\": \"JWT_TOKEN_HERE\"\n  }\n}\n\n```\n\nThese examples are only for illustration. Real deployments use real URLs and real tokens generated by the alert API. For Linovision/Hikvision, `username`/`password` override the global `CAM_USER`/`CAM_PWD`, `azimuth_offset_deg` aligns camera coordinates to real azimuths, and `snapshot_channel`/`ptz_channel` let you pick the right ISAPI channels.\n\n---\n\n## Documentation\n\nThe full package documentation is available here:\n\n[https://pyronear.org/pyro-engine/](https://pyronear.org/pyro-engine/)\n\nIt covers engine usage, configuration and deployment details.\n\n---\n\n## Contributing\n\nPlease refer to [`CONTRIBUTING`](CONTRIBUTING.md) if you wish to contribute to this project.\n\n---\n\n## License\n\nDistributed under the Apache 2 License. See [`LICENSE`](LICENSE) for more information.\n","funding_links":["https://github.com/sponsors/pyronear"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyronear%2Fpyro-engine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpyronear%2Fpyro-engine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyronear%2Fpyro-engine/lists"}