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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.DS_Store
**/.DS_Store
.idea
.vscode
__pycache__
158 changes: 158 additions & 0 deletions SaSS/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Semantic Adaptive Surveillance System

SaSS (Semantic Adaptive Surveillance System) is a CSML use case for semantic surveillance.
It demonstrates how collaborative state machines coordinate edge cameras, incident detection,
adaptive media collection, and cloud-based incident management while integrating BLIP image captioning
and the CHAD dataset through external HTTP services.

## Project Structure

SaSS/
├── README.md
├── main.pkl
├── config/
│ └── base.pkl
├── machines/
│ ├── edgeCamera.pkl
│ ├── incidentDetector.pkl
│ ├── incidentManager.pkl
│ ├── mediaCapacityArbitrator.pkl
│ └── cloudViewer.pkl
├── policies/
│ ├── correlationRules.pkl
│ ├── escalationPolicy.pkl
│ └── priorityPolicy.pkl
├── topology/
│ └── deployment.pkl
└── services/
├── caption_service.py
├── support_services.py
├── run_all.sh
└── requirements.txt


## Running

### 1. Download the CHAD dataset

Get the dataset from https://github.com/TeCSAR-UNCC/CHAD :
- **CHAD_Videos** — https://drive.google.com/file/d/13am4hfhicErcozAYgkmQm02_K-cCmtkQ/view
- **CHAD_Meta** — https://drive.google.com/file/d/1g01Ay86cuPmKGsrfxddxwA1uG_7bFFmu/view

### 2. Start the three outside services

Create a virtual environment and install the deps once:

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r services/requirements.txt
```

Then start each service in its own terminal, replace `<DATASET>` with the folder where you unpacked CHAD


```bash
# Terminal 1 — caption service (BLIP + CHAD; owns the shared cursor) :9100
python3 services/caption_service.py \
--port 9100 \
--chad-videos <DATASET>/CHAD_Videos \
--chad-labels <DATASET>/CHAD_Meta/anomaly_labels
```

```bash
# Terminal 2 — media burst service (reads CHAD frames, aligns to cursor) :9200
python3 services/support_services.py \
--service burst \
--port 9200 \
--chad-videos <DATASET>/CHAD_Videos
```

```bash
# Terminal 3 — incident store (persists incidents + media refs) :9300
python3 services/support_services.py \
--service store \
--port 9300
```

Start the caption service (9100) **before** the burst service (9200): the burst
service calls the caption service's `GET /cursor` for time alignment.

(`services/run_all.sh` launches all three at once if you prefer:
`CHAD_VIDEOS=... CHAD_LABELS=... ./services/run_all.sh`.)

### 3. Run the application

Run the full application (all cameras of zone A + the zone-A detector +
the shared arbitrator and cloud viewer):

```bash
RUN=zoneA-cam-0,zoneA-cam-1,zoneA-cam-2,zoneA-cam-3,detector-A,arbitrator,operatorViewer \
MAIN_URI=file://$PWD/SaSS/main.pkl \
ETCD_CONTEXT_URL=http://localhost:2379 \
./gradlew run 2>&1 | tee run.log
```

To bring up more zones, add their instances to `RUN`, e.g. also
`zoneB-cam-0,...,detector-B` and `zoneC-cam-0,...,detector-C`. The
`incidentManager` instances are **not** listed in `RUN` — they are created
dynamically at runtime by the detectors when incidents are detected.

For focused debugging you can start a single instance, e.g. just one camera:

```bash
RUN=zoneA-cam-0 \
MAIN_URI=file://$PWD/SaSS/main.pkl \
ETCD_CONTEXT_URL=http://localhost:2379 \
./gradlew run
```

## Dataset

**CHAD (Charlotte Anomaly Dataset)** — multi-camera, person/pose-annotated,
four simultaneous views of the same scene. Files are named
`<camera_number>_<video_number>_<existence_of_anomalous_frame>.mp4` with `<camera>` in {1,2,3,4}.
The four camera views are mapped onto the four cameras of one zone (`zoneX-cam-0..3`),
enabling meaningful within-zone k-of-n correlation.


## Captioner

`caption_service.py` mirrors the BLIP model and inference pipeline from [BLIP_CAM](https://github.com/zawawiAI/BLIP_CAM).
It exposes a `POST /caption` endpoint that returns BLIP captions and anomaly scores derived from the CHAD ground-truth labels.
CUDA is used automatically if available.


## Escalation:frame burst

On *entering* a video mode the camera fires one `Invoke` to the media burst service and then waits.
A burst is defined in `config/base.pkl` by (count, step):

| mode | count | step | meaning |
|-----------|-------|------|----------------------------|
| snapshot | 1 | 1 | one frame at the cursor |
| videoLow | 5 | 8 | sparse evidence |
| videoHigh | 20 | 2 | dense evidence |

Both the CSM layer and the services follow the same (count, step) rule.


## Services (ports)

- `caption_service.py` (:9100) — `POST /caption`, `GET /cursor`. BLIP + CHAD.
- `support_services.py --service burst` (:9200) — `POST /burst`: reads real CHAD
frames at the shared cursor, writes JPEGs under `--media-root`, returns a
`mediaRef` + frame paths. No video stream.
- `support_services.py --service store` (:9300) — `POST /store`: persists incident
metadata **and** media refs for the Cloud Viewer.

## Note

To communicate with the Python-based external services, the HTTP service adapter (`ServiceImplementation.kt`)
needs to be modified to exchange JSON payloads instead of the runtime's default binary serialization.

37 changes: 37 additions & 0 deletions SaSS/config/base.pkl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import "https://raw.githubusercontent.com/CollaborativeStateMachines/Cirrina/refs/heads/develop/src/main/resources/pkl/csm/csml.pkl"

// Tunable input parameters of the SaSS.
params = new Mapping {
// --- Detection (Incident Detector) ---
["anomalyGateThreshold"] = "0.001" // per-camera anomaly score gate (0..1)
["detectionThreshold"] = "0.001" // incident confidence threshold (crossing => detected)
["correlationWindowMs"] = "5000" // within-zone correlation window
["correlationK"] = "1" // k in k-of-n (min cameras of the same zone)

// --- Incident lifecycle (Incident Manager) ---
["reassessIntervalMs"] = "5000" // X: re-assess a detected incident every X ms

// --- Capacity (Media Capacity Arbitrator) ---
["maxConcurrentVideo"] = "4" // global cap on concurrent video escalations

// --- Semantic heartbeat (continuous, low-bandwidth observation cadence) ---
// Captions are emitted periodically in EVERY mode (this is the heartbeat).
["captionIntervalMs"] = "1200" // one observation per camera per ~1.2s

// --- Escalation as a FRAME BURST (NOT a continuous stream) ---
// On entering a video mode the camera requests ONE burst of frames and then
// waits; it does not stream. A burst is defined by (count, step):
// count = how many frames to fetch
// step = stride between fetched frames (in source frames)
// The media gateway extracts exactly these frames from CHAD, time-aligned to
// the camera's current cursor (shared via the caption service).
["snapshotBurstCount"] = "1"
["snapshotBurstStep"] = "1"
["videoLowBurstCount"] = "5" // sparse evidence
["videoLowBurstStep"] = "8"
["videoHighBurstCount"] = "20" // dense evidence
["videoHighBurstStep"] = "2"

// Cooldown after a burst is released/preempted, before returning to caption.
["cooldownMs"] = "2000"
}
54 changes: 54 additions & 0 deletions SaSS/machines/cloudViewer.pkl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import "https://raw.githubusercontent.com/CollaborativeStateMachines/Cirrina/refs/heads/develop/src/main/resources/pkl/csm/csml.pkl"

// ---------------------------------------------------------------------------
// cloudViewer
// Single shared, PASSIVE instance. It subscribes to two kinds of events and
// persists them via the external incident store. It never participates in
// real-time control: it only receives information and stores it for auditing,
// monitoring, and offline review. Subscription delivers information to clients;
// it does not give them control over the system.
// ---------------------------------------------------------------------------

sm = new csml.StateMachine {
states {
["listening"] = new csml.Initial {
on {
// ---- incident lifecycle updates ----
["incidentNotification"] = new csml.Transition {
yields {
new csml.Invoke {
type = "incidentStoreService"
input {
["incidentId"] = "$incidentId"
["siteId"] = "$siteId"
["zoneId"] = "$zoneId"
["cameraIds"] = "$cameraIds"
["tags"] = "$tags"
["confidence"] = "$confidence"
["status"] = "$status"
}
}
new csml.Ctr { counter = "operatorViewer.incidentStored" }
}
}
// ---- frame-burst evidence captured on escalation ----
["mediaReady"] = new csml.Transition {
yields {
new csml.Invoke {
type = "incidentStoreService"
input {
["incidentId"] = "$incidentId"
["cameraId"] = "$cameraId"
["zoneId"] = "$zoneId"
["mode"] = "$mode"
["mediaRef"] = "$mediaRef"
["status"] = "'media'"
}
}
new csml.Ctr { counter = "operatorViewer.mediaStored" }
}
}
}
}
}
}
Loading