Analyzing classified person-sightings in GoPro wearable-camera imagery collected along routes in four Indian cities.
The tables below are regenerated by scripts/10_analysis.py (via make figures/make tables).
| City | Images | Pedestrians | Female share [95% CI] | Sex Ratio (F/1000 M) |
|---|---|---|---|---|
| Mumbai | 2,809 | 12,331 | 19.1% [17.4%, 20.8%] | 236 |
| Navi Mumbai | 2,189 | 5,186 | 18.1% [16.0%, 20.2%] | 221 |
| Bangalore | 2,015 | 2,311 | 27.4% [25.7%, 29.1%] | 377 |
| Delhi | 2,417 | 3,020 | 19.8% [17.3%, 22.2%] | 246 |
| Mode | Mumbai | Navi Mumbai | Bangalore | Delhi |
|---|---|---|---|---|
| Pedestrians | 19.1% | 18.1% | 27.4% | 19.8% |
| Two-wheelers | 8.2% | 5.3% | 8.8% | 5.9% |
| City | Primary | Secondary | Tertiary | Residential |
|---|---|---|---|---|
| Mumbai | 20.3% | 16.9% | 18.6% | 20.3% |
| Navi Mumbai | 16.7% | 15.9% | 18.1% | 23.3% |
| Bangalore | 28.8% | 22.8% | 25.1% | 29.4% |
| Delhi | 16.5% | 16.8% | 19.6% | 23.4% |
Women-coded pedestrian sightings are below parity in the collected imagery in every city, and the female share is lower among two-wheeler riders than among pedestrians. Census sex ratios are population quantities and are not directly comparable to this route-image sample.
The project is a linear pipeline from sampling design to published results:
geo-sampling → itineraries (allocator) → GoPro collection → EXIF/GPS/frame extraction
→ face-frame extraction → human annotation (Label Studio)
→ parse annotations → assign GPS → enrich with road type → analysis_data
→ figures + tables + maps
The collection half is frozen: it has been run once in the field and on the raw footage, and its outputs (extracted frames, EXIF, GPS index, annotations) are treated as immutable inputs. The analysis half is fair game: it transforms those frozen inputs into the published dataset and outputs and can be re-run freely.
| Stage | Script(s) | Status |
|---|---|---|
| Geo-sampling / itineraries (allocator) | scripts/gen_data/<city>/run_*_sampler.py |
frozen |
| Video processing: EXIF, GPS, frame extraction | 00_process_videos.py |
frozen |
| Face-detection frame extraction | 01_extract_face_frames.py |
frozen |
| Coverage QA / frame compression | 02_plot_video_coverage.py, 03_compress_frames.py |
frozen |
| GPS lookup index / EXIF recovery | 04_build_gps_index.py, rebuild_csvs_from_exif.py |
frozen |
| Human annotation (Label Studio) | external | frozen |
| Parse annotations | 05_parse_annotations.py |
fair game |
| Assign GPS to frames (ts/lat/lon) | 06_assign_frame_gps.py |
fair game |
| Enrich with road type (itinerary + OSM) | 07_enrich_with_geo.py |
fair game |
| Build analysis dataset | 08_build_analysis_data.py |
fair game |
| EDA / publication figures+tables / maps | 09_eda.py, 10_analysis.py, 11_make_maps.py |
fair game |
| Descriptive clustered inference | inference.py |
fair game |
| LLM annotation comparison (optional diagnostic) | 12_validate_annotations.py |
fair game |
| Inter-rater reliability | 13_interrater_reliability.py |
fair game |
| Descriptive pattern mining | 14_descriptive_patterns.py |
fair game |
The analysis half starts from the frozen GPS index and annotations and never re-touches videos or frames:
# One city, end to end (analysis_data + its figures/tables/maps), using cached GPS index:
make analyze CITY=mumbai
# Regenerate figures/tables/maps across cities from existing analysis_data:
make figures CITIES=mumbai,navi_mumbai
# Tables only:
make tables CITIES=mumbai,navi_mumbaiEquivalent direct invocation:
.venv/bin/python scripts/run_pipeline.py --city mumbai --skip-rebuild-gps# Re-process videos and re-extract frames (requires video_dir in cities.yaml):
.venv/bin/python scripts/run_pipeline.py --city mumbai --process-videosThe primary estimand is the share of classified pedestrian sightings coded as women:
sum(women pedestrians) / sum(all pedestrians). This definition is also used in the
Google Street View analysis. Combined pedestrian/two-wheeler estimates and image-level
means are secondary.
The extracted images are face-triggered frames along collected routes, not a probability sample of streets or residents. Results therefore describe visible classified sightings in the collected imagery. Confidence intervals use a collection-day cluster sandwich with a t distribution on G−1 degrees of freedom; they quantify variability across the observed fieldwork days and do not imply population representativeness.
Counts recorded as 10+ are interval-censored. The primary tables use 11, the known
minimum, and report sensitivity to replacement values 15, 20, and 30.
The optional OpenAI comparison is a diagnostic, not a gold-standard validation. Install it
with uv sync --all-extras; the script defaults to the pinned image-capable model snapshot
gpt-4o-2024-11-20 and performs no request unless --submit or --check is supplied.
# System tools (macOS)
brew install ffmpeg exiftool
# Python environment (uv-managed .venv):
make installmake install installs the pinned analysis and development dependencies from uv.lock.
07_enrich_with_geo.py can attach an OSM "ground truth" road type alongside the
itinerary road type. It is skipped automatically unless the city's PBF is present.
Download the region from Geofabrik into data/osm/
using the filename in cities.yaml, e.g.:
data/osm/maharashtra-latest.osm.pbf # mumbai, navi_mumbai
data/osm/karnataka-latest.osm.pbf # bangalore
When present, tableS1_road_type.tex gains an OSM ground-truth section.
scripts/
gen_data/{city}/ Sampling-design scripts (frozen)
00_process_videos.py ... Pipeline scripts (see table above)
inference.py descriptive collection-day clustered inference
run_pipeline.py Analysis-half orchestrator
data/
{city}/
exif/ Raw EXIF text files (frozen)
gps_index/ GPS lookup parquet files (frozen)
labelstudio/ Label Studio JSON exports (frozen)
face_frame_metadata.csv Face-detection frame log
analysis_data.parquet Final analysis dataset (primary annotator)
analysis_data_long.parquet Long format (all annotators)
annotation_task/ Extracted frames for annotation
osm/ Optional OSM PBFs for ground-truth road type
sampling/
{city}/itineraries/ Itinerary road-type classifications
figs/ Generated figures and maps
tabs/ Generated LaTeX tables
cities.yaml Per-city config (video_dir, osm_file, timezone)
Makefile Analysis-half workflow targets
data/{city}/analysis_data.parquet- Primary analysis datasetdata/{city}/analysis_data_long.parquet- Long format (all annotators)
fig2_distribution.pdf- Proportion female distributionfig3_multipanel.pdf- Multi-panel summary (mode, road type, POI, time)fig4_weekday_weekend.pdf- Weekday vs weekend comparisonfig5_pedestrian_crowdsize.pdf- Female share by pedestrian crowd sizemap_locations_{city}.pdf/.html- Data collection locationsmap_sexratio_{city}.pdf/.html- Female share by ~500m cellmap_overview_india.pdf- All cities on one India basemapeda_*.pdf- Exploratory analysis plots
table1_city_summary.tex- City-level summarytableS1_road_type.tex- By road type (itinerary + optional OSM ground truth)tableS2_temporal.tex- Temporal patternstableS3_poi_infrastructure.tex- POI and infrastructuretableS4_irr.tex- Inter-rater reliability (viamake irr)descriptive_patterns.md- Accompaniment, place rankings, joint regression
| Metric | Mumbai | Navi Mumbai |
|---|---|---|
| GPS coverage | 97.4% | 99.4% |
| Itinerary match | 66.3% | 72.1% |
| Hour range (IST) | 7-19 | 9-18 |
Bangalore and Delhi are annotated and included in the analysis outputs. Hyderabad has sampling itineraries and partially processed footage (face frames extracted); annotation/analysis for it is in progress.
Per-frame counts are top-coded at 10 ("10+" recorded as 11), and collection spans roughly 06:00-22:00 IST only.
MIT