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
4 changes: 2 additions & 2 deletions client/pyroclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,12 @@ def fetch_detections(self) -> Response:
timeout=self.timeout,
)

def label_sequence(self, sequence_id: int, is_wildfire: bool) -> Response:
def label_sequence(self, sequence_id: int, is_wildfire: str) -> Response:
"""Update the label of a sequence made by a camera

>>> from pyroclient import client
>>> api_client = Client("MY_USER_TOKEN")
>>> response = api_client.label_sequence(1, is_wildfire=True)
>>> response = api_client.label_sequence(1, is_wildfire="wildfire_smoke")

Args:
sequence_id: ID of the associated sequence entry
Expand Down
2 changes: 2 additions & 0 deletions client/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ def cam_token():
"elevation": 1582,
"lat": 44.765181,
"lon": 4.51488,
"ip_address": "165.165.165.165",
"livestream_activated": False,
"is_trustable": True,
}
response = requests.post(urljoin(API_URL, "cameras"), json=payload, headers=admin_headers, timeout=5)
Expand Down
2 changes: 1 addition & 1 deletion client/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def test_agent_workflow(test_cam_workflow, agent_token):
agent_client = Client(agent_token, "http://localhost:5050", timeout=10)
response = agent_client.fetch_latest_sequences().json()
assert len(response) == 1
response = agent_client.label_sequence(response[0]["id"], True)
response = agent_client.label_sequence(response[0]["id"], "wildfire_smoke")
assert response.status_code == 200, response.__dict__


Expand Down
2 changes: 1 addition & 1 deletion scripts/dbdiagram.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Table "Sequence" as S {
"id" int [not null]
"camera_id" int [ref: > C.id, not null]
"azimuth" float [not null]
"is_wildfire" bool
"is_wildfire" AnnotationType
"started_at" timestamp [not null]
"last_seen_at" timestamp [not null]
Indexes {
Expand Down
6 changes: 5 additions & 1 deletion scripts/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ def main(args):
"lat": 44.7,
"lon": 4.5,
"azimuth": 110,
"ip_address": "165.165.165.165",
"livestream_activated": False,
}
cam_id = api_request("post", f"{args.endpoint}/cameras/", agent_auth, payload)["id"]

Expand Down Expand Up @@ -152,7 +154,9 @@ def main(args):
== 1
)
# Label the sequence
api_request("patch", f"{args.endpoint}/sequences/{sequence['id']}/label", agent_auth, {"is_wildfire": True})
api_request(
"patch", f"{args.endpoint}/sequences/{sequence['id']}/label", agent_auth, {"is_wildfire": "wildfire_smoke"}
)
# Check the sequence's detections
dets = api_request("get", f"{args.endpoint}/sequences/{sequence['id']}/detections", agent_auth)
assert len(dets) == 3
Expand Down
27 changes: 26 additions & 1 deletion src/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,29 @@ class Role(str, Enum):
USER = "user"


class AnnotationType(str, Enum):
WILDFIRE_SMOKE = "wildfire_smoke"
OTHER_SMOKE = "other_smoke"
ANTENNA = "antenna"
BUILDING = "building"
CLIFF = "cliff"
DARK = "dark"
DUST = "dust"
HIGH_CLOUD = "high_cloud"
LOW_CLOUD = "low_cloud"
LENS_FLARE = "lens_flare"
LENS_DROPLET = "lens_droplet"
LIGHT = "light"
RAIN = "rain"
TRAIL = "trail"
ROAD = "road"
SKY = "sky"
TREE = "tree"
WATER_BODY = "water_body"
DOUBT = "doubt"
OTHER = "other"


class User(SQLModel, table=True):
__tablename__ = "users"
id: int = Field(None, primary_key=True)
Expand All @@ -47,6 +70,8 @@ class Camera(SQLModel, table=True):
elevation: float = Field(..., gt=0, lt=10000, nullable=False)
lat: float = Field(..., gt=-90, lt=90)
lon: float = Field(..., gt=-180, lt=180)
ip_address: str
livestream_activated: bool = False
is_trustable: bool = True
last_active_at: Union[datetime, None] = None
last_image: Union[str, None] = None
Expand All @@ -69,7 +94,7 @@ class Sequence(SQLModel, table=True):
id: int = Field(None, primary_key=True)
camera_id: int = Field(..., foreign_key="cameras.id", nullable=False)
azimuth: float = Field(..., ge=0, lt=360)
is_wildfire: Union[bool, None] = None
is_wildfire: Union[AnnotationType, None] = None
started_at: datetime = Field(..., nullable=False)
last_seen_at: datetime = Field(..., nullable=False)

Expand Down
2 changes: 2 additions & 0 deletions src/app/schemas/cameras.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class CameraCreate(CameraEdit):
description="angle between left and right camera view",
json_schema_extra={"examples": [120.0]},
)
ip_address: str
livestream_activated: bool = False
is_trustable: bool = Field(True, description="whether the detection from this camera can be trusted")


Expand Down
4 changes: 2 additions & 2 deletions src/app/schemas/detections.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
from pydantic import BaseModel, Field

from app.core.config import settings
from app.models import Detection
from app.models import AnnotationType, Detection

__all__ = ["Azimuth", "DetectionCreate", "DetectionLabel", "DetectionUrl"]


class DetectionLabel(BaseModel):
is_wildfire: bool
is_wildfire: AnnotationType


class Azimuth(BaseModel):
Expand Down
4 changes: 2 additions & 2 deletions src/app/schemas/sequences.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from pydantic import BaseModel

from app.models import Sequence
from app.models import AnnotationType, Sequence

__all__ = ["SequenceUpdate", "SequenceWithCone"]

Expand All @@ -18,7 +18,7 @@ class SequenceUpdate(BaseModel):


class SequenceLabel(BaseModel):
is_wildfire: bool
is_wildfire: AnnotationType


class SequenceWithCone(Sequence):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import sqlalchemy as sa
from alembic import op

# Ajoute ton identifiant de révision et dépendance si besoin
revision = "42dzeg392dhu"
down_revision = "4265426f8438"
branch_labels = None
depends_on = None


def upgrade() -> None:
# 1. Créer la table sequences (doit précéder la FK)
op.create_table(
"sequences",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("camera_id", sa.Integer(), sa.ForeignKey("camera.id"), nullable=False),
sa.Column("azimuth", sa.Float(), nullable=False),
sa.Column("is_wildfire", sa.Boolean(), nullable=True), # sera modifié par la 4e migration
sa.Column("started_at", sa.DateTime(), nullable=False),
sa.Column("last_seen_at", sa.DateTime(), nullable=False),
)

# 2. Ajouter les colonnes manquantes
op.add_column("camera", sa.Column("last_image", sa.String(), nullable=True))
op.add_column("organization", sa.Column("telegram_id", sa.String(), nullable=True))
op.add_column("detection", sa.Column("sequence_id", sa.Integer(), nullable=True))
op.add_column("detection", sa.Column("bboxes", sa.String(length=5000), nullable=False)) # adapter à settings

# 3. Ajouter la contrainte FK après la création de la table sequences
op.create_foreign_key(
"fk_detection_sequence",
"detection",
"sequences",
["sequence_id"],
["id"],
)

# 4. Créer la table webhooks
op.create_table(
"webhooks",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("url", sa.String(), nullable=False, unique=True),
)


def downgrade() -> None:
op.drop_table("webhooks")
op.drop_constraint("fk_detection_sequence", "detection", type_="foreignkey")
op.drop_column("detection", "bboxes")
op.drop_column("detection", "sequence_id")
op.drop_column("organization", "telegram_id")
op.drop_column("camera", "last_image")
op.drop_table("sequences")
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Add Slack Hook
Revision ID: 2853acd1fc32
Revises: 4265426f8438
Create Date: 2025-06-25 17:20:14.959429
"""

from typing import Sequence, Union

import sqlalchemy as sa
import sqlmodel
from alembic import op

# revision identifiers, used by Alembic.
revision: str = "2853acd1fc32"
down_revision: Union[str, None] = "42dzeg392dhu"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.add_column("organization", sa.Column("slack_hook", sqlmodel.sql.sqltypes.AutoString(), nullable=True))


def downgrade() -> None:
op.drop_column("organization", "slack_hook")
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""modify is_wilfire column

Revision ID: 307a1d6d490d
Revises: 2853acd1fc32
Create Date: 2025-08-20 16:47:05.346210

"""

from typing import Sequence, Union

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision: str = "307a1d6d490d"
down_revision: Union[str, None] = "2853acd1fc32"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

# Define the new ENUM type
annotation_type_enum = sa.Enum(
"WILDFIRE_SMOKE",
"OTHER_SMOKE",
"ANTENNA",
"BUILDING",
"CLIFF",
"DARK",
"DUST",
"HIGH_CLOUD",
"LOW_CLOUD",
"LENS_FLARE",
"LENS_DROPLET",
"LIGHT",
"RAIN",
"TRAIL",
"ROAD",
"SKY",
"TREE",
"WATER_BODY",
"DOUBT",
"OTHER",
name="annotationtype",
)


def upgrade():
# Create the enum type in the database
annotation_type_enum.create(op.get_bind(), checkfirst=True)

# Use raw SQL with a CASE expression for the conversion
op.execute("""
ALTER TABLE sequences
ALTER COLUMN is_wildfire
TYPE annotationtype
USING CASE
WHEN is_wildfire = TRUE THEN 'WILDFIRE_SMOKE'::annotationtype
ELSE 'OTHER'::annotationtype
END
""")


def downgrade():
# Revert the column back to a boolean (or previous enum if applicable)
op.execute("""
ALTER TABLE sequences
ALTER COLUMN is_wildfire
TYPE boolean
USING CASE
WHEN is_wildfire = 'WILDFIRE_SMOKE' THEN TRUE
ELSE FALSE
END
""")

# Drop the enum type from the DB
annotation_type_enum.drop(op.get_bind(), checkfirst=True)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""create news columns in cameras table

Revision ID: 47005ff54a94
Revises: 307a1d6d490d
Create Date: 2025-08-28 11:05:46.058307

"""

from typing import Sequence, Union

import sqlalchemy as sa
import sqlmodel
from alembic import op

# revision identifiers, used by Alembic.
revision: str = "47005ff54a94"
down_revision: Union[str, None] = "307a1d6d490d"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.add_column("camera", sa.Column("ip_address", sqlmodel.sql.sqltypes.AutoString(), nullable=False))
op.add_column("camera", sa.Column("livestream_activated", sa.Boolean(), nullable=False))


def downgrade() -> None:
op.drop_column("camera", "ip_address")
op.drop_column("camera", "livestream_activated")
6 changes: 5 additions & 1 deletion src/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
"elevation": 110.6,
"lat": 3.6,
"lon": -45.2,
"ip_address": "165.165.165.165",
"livestream_activated": False,
"is_trustable": True,
"last_active_at": datetime.strptime("2023-11-07T15:07:19.226673", dt_format),
"last_image": None,
Expand All @@ -87,6 +89,8 @@
"elevation": 110.6,
"lat": 3.6,
"lon": -45.2,
"ip_address": "165.165.165.165",
"livestream_activated": True,
"is_trustable": False,
"last_active_at": None,
"last_image": None,
Expand Down Expand Up @@ -138,7 +142,7 @@
"id": 1,
"camera_id": 1,
"azimuth": 43.7,
"is_wildfire": True,
"is_wildfire": "wildfire_smoke",
"started_at": datetime.strptime("2023-11-07T15:08:19.226673", dt_format),
"last_seen_at": datetime.strptime("2023-11-07T15:28:19.226673", dt_format),
},
Expand Down
Loading
Loading