Joining the modalities in space and time¶
The point of the release is that four independently collected modalities can
be put on the same spatial and temporal footing. This page says which columns
do that, what their precision is, and where the traps are. A runnable example
is at examples/join_camera_acoustic_by_site_hour.py (download the dataset
first, as shown in Darwin Core tables).
Temporal keys¶
| Column | Tables | Meaning |
|---|---|---|
eventDate, eventTime |
all Occurrence tables | Local date and time of the record. Camera-trap values are the corrected timestamps, not the camera clock. An eventDate interval (start/end) means the record is only known to lie in that span |
corrected_timestamp, correction_method, correction_confidence |
images.csv (camera traps) |
Per-image corrected capture time, the method that produced it (18 methods, supplement S5), and its confidence |
timestampPrecision, timeEligible |
arrival traces | second or day; whether the record can enter inter-arrival statistics |
Three temporal traps
- Camera clocks were wrong on 93.9% of images (defect 1). Use the corrected values; the burned-in overlay in the image shows the raw clock.
- The 2025-08-11 drone flights had a date but no time (defect 15). Their
times were recovered from the video container creation times, with the
clock convention settled against the field log (
provenance/drone_time_recovery_20250811.jsonin the drone dataset); the DJI convention is inferred, so treat those times as minute-level. - Acoustic Events are recording windows, not instants: a 5- or 15-minute WAV. An acoustic Occurrence's time is the recording start.
Campaign windows: June 30 – July 3 (campaign 1), August 4 – 28 (campaign 2),
September 8 (campaign 3). The deployment Events carry each sensor's actual
eventDate interval, which is the right denominator for effort.
Spatial keys¶
| Modality | locationID |
Coordinates | Uncertainty |
|---|---|---|---|
| Camera traps | Site code TW01 … TW11 |
Deployment Event: camera position. Occurrences inherit the site; animal positions are not inferred in the released table | 5 m |
| Bioacoustics | Site code | Deployment Event: recorder position | 50 m |
| Drones | Flight identifier (20250818_parrot_01) |
Deployment Event: flight centroid; MediaEvent: video position; Occurrences: ground-projected box position where georef_status = ok |
100 m (flight), per-row for projections |
| Citizen science | none | Park-level point on the observation session | 5,000 m |
Fixed sensors are joined by site code and campaign, because several sensors moved between campaigns (the TW07 recorder, the TW09 camera after August 18; defect 6). Drones and citizen science have no site code and are joined by coordinates or by date.
Site codes are shared across modalities. In campaign 2, camera traps and recorders were co-located at TW02, TW06, and TW09; the supplemental material (S2, S3) lists every deployment with its coordinates and dates.
The Event hierarchy¶
All tables share one hierarchy rooted at TheWilds_2025. Walking
parentEventID from an Occurrence's eventID reaches the deployment (or
flight, or session), then the modality's Project, then the study root. That is
how an Occurrence finds its campaign, its sensor configuration, and its
deployment coordinates.
Example: camera and acoustic co-occurrence by site-hour¶
import pandas as pd
def load(root):
ev = pd.read_csv(f"{root}/events.csv", dtype=str, keep_default_na=False)
oc = pd.read_csv(f"{root}/occurrences.csv", dtype=str, keep_default_na=False)
return ev, oc
# Download first:
# huggingface-cli download --repo-type dataset ABC-Center/smartwilds_metadata --local-dir smartwilds_metadata
cam_ev, cam = load("smartwilds_metadata/smartwilds-camera-traps/data")
aru_ev, aru = load("smartwilds_metadata/smartwilds-bioacoustics/data")
# 1. Temporal key: corrected local timestamp, truncated to the hour.
for df in (cam, aru):
df["t"] = pd.to_datetime(df.eventDate + " " + df.eventTime, errors="coerce")
df["hour"] = df.t.dt.floor("h")
# 2. Spatial key: the site code on fixed sensors (TW01 ... TW11), plus the
# campaign window from the deployment Event, because a few sensors moved
# between campaigns.
def campaign(ev):
dep = ev[ev.eventType == "SensorDeployment"].set_index("eventID")
return dep.eventDate.str.slice(0, 7) # "2025-06" or "2025-08"
cam["campaign"] = cam.eventID.map(cam_ev.set_index("eventID").parentEventID).map(campaign(cam_ev))
aru["campaign"] = aru.eventID.map(aru_ev.set_index("eventID").parentEventID).map(campaign(aru_ev))
# 3. Co-occurrence per site-hour: camera detections next to acoustic detections.
cam_h = cam.dropna(subset=["hour"]).groupby(["campaign", "locationID", "hour"]).size().rename("camera_occurrences")
aru_h = aru.dropna(subset=["hour"]).groupby(["campaign", "locationID", "hour"]).size().rename("acoustic_occurrences")
joined = pd.concat([cam_h, aru_h], axis=1).fillna(0).astype(int)
both = joined[(joined.camera_occurrences > 0) & (joined.acoustic_occurrences > 0)]
print(f"site-hours with both modalities: {len(both):,} of {len(joined):,}")
print(both.reset_index().groupby("locationID").size().rename("site_hours_with_both").to_string())
On the current tables this reports 403 site-hours (of 2,299 with any detection) in which both a camera and a recorder at the same site produced Occurrences, at TW02, TW06, and TW09.
Arrival traces¶
For workload or systems questions, analysis/arrival_traces_v2/ in the
study-level dataset already has the campaign-2 streams at two layers with
checksums and a contract describing units and inclusion rules; see
Arrival traces.