mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
feat(intro): v2 — cities, accelerating sweep, detections, thriller sound design
- City engine: Esri ground, OSM buildings that rise with the scan, road ribbons carrying modelled traffic coloured by congestion, NYC DOT cameras, real ADS-B, MBTA GTFS lines + realtime vehicles, LAX runways with approach strobes, Fort Bragg restricted volumes, Everest terrain with contours. - Indexing keeps accelerating into a 260-capture global sweep while the planet whips a full turn. - The eye opens: aperture blades, sensor array waking, optics calibration, radar sweep, orbiting coordinate ring. - Camera wall with YOLOv8n detections and a city-pulse trace. - Ends on GOD'S EYE VIEW / OMNISCIENCE. - audio/sfx.py: sound design only, built to sit under a music track. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01KqP857pippaUs6konSDfe8
This commit is contained in:
30 files changed
+8921
-1442
No files matched your search
@@ -0,0 +1,82 @@
|
||||
"""MBTA static GTFS -> city/boston/transit.json (subway, Mattapan, commuter rail, bus).
|
||||
usage: python3 build_transit.py <dir with routes.txt shapes.txt trips.txt>"""
|
||||
import csv, json, math, os, sys
|
||||
from collections import defaultdict
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import SCENES, enu
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
G = sys.argv[1]
|
||||
H = SCENES['boston'][2]; M = H + 1000
|
||||
|
||||
def rdp(pts, eps):
|
||||
if len(pts) < 3: return pts
|
||||
keep = [False] * len(pts); keep[0] = keep[-1] = True
|
||||
stack = [(0, len(pts) - 1)]
|
||||
while stack:
|
||||
a, b = stack.pop()
|
||||
ax, ay = pts[a]; bx, by = pts[b]; dx, dy = bx - ax, by - ay; L = math.hypot(dx, dy)
|
||||
best, bi = -1, -1
|
||||
for i in range(a + 1, b):
|
||||
px, py = pts[i]
|
||||
d = abs(dy * (px - ax) - dx * (py - ay)) / L if L else math.hypot(px - ax, py - ay)
|
||||
if d > best: best, bi = d, i
|
||||
if best > eps:
|
||||
keep[bi] = True; stack += [(a, bi), (bi, b)]
|
||||
return [p for p, k in zip(pts, keep) if k]
|
||||
|
||||
def length(p): return sum(math.hypot(p[i+1][0]-p[i][0], p[i+1][1]-p[i][1]) for i in range(len(p)-1))
|
||||
|
||||
routes = {}
|
||||
for r in csv.DictReader(open(os.path.join(G, 'routes.txt'), encoding='utf-8-sig')):
|
||||
t = int(r['route_type'])
|
||||
if t not in (0, 1, 2, 3): continue
|
||||
if 'Replacement' in r['route_desc'] or r['route_id'].startswith('Shuttle'): continue
|
||||
name = r['route_short_name'] or r['route_long_name']
|
||||
if t == 3 and r['route_short_name'] and r['route_long_name']:
|
||||
name = f"{r['route_short_name']} {r['route_long_name']}"
|
||||
routes[r['route_id']] = {'name': name, 'color': '#' + (r['route_color'] or '888888').upper(), 'type': t}
|
||||
|
||||
shape_rd = {}
|
||||
for t in csv.DictReader(open(os.path.join(G, 'trips.txt'), encoding='utf-8-sig')):
|
||||
if t['route_id'] in routes and t['shape_id']:
|
||||
shape_rd.setdefault(t['shape_id'], (t['route_id'], t['direction_id']))
|
||||
|
||||
shapes = defaultdict(list)
|
||||
for s in csv.DictReader(open(os.path.join(G, 'shapes.txt'), encoding='utf-8-sig')):
|
||||
if s['shape_id'] in shape_rd:
|
||||
shapes[s['shape_id']].append((int(s['shape_pt_sequence']), float(s['shape_pt_lat']), float(s['shape_pt_lon'])))
|
||||
|
||||
best = {} # (route, dir) -> (len_in_square, segments)
|
||||
for sid, pts in shapes.items():
|
||||
pts.sort()
|
||||
xy = [enu('boston', la, lo) for _, la, lo in pts]
|
||||
if not any(abs(x) <= H and abs(y) <= H for x, y in xy): continue # must pass within the square
|
||||
segs, cur = [], []
|
||||
for i, (x, y) in enumerate(xy):
|
||||
if abs(x) <= M and abs(y) <= M:
|
||||
if not cur and i > 0: cur.append(xy[i-1]) # carry edge point
|
||||
cur.append((x, y))
|
||||
elif cur:
|
||||
cur.append((x, y)); segs.append(cur); cur = []
|
||||
if cur: segs.append(cur)
|
||||
segs = [rdp(s, 2.0) for s in segs if len(s) > 1]
|
||||
L = sum(length(s) for s in segs)
|
||||
key = shape_rd[sid]
|
||||
if key not in best or L > best[key][0]: best[key] = (L, segs)
|
||||
|
||||
out_shapes, seen = [], set()
|
||||
for (rid, d), (L, segs) in sorted(best.items()):
|
||||
for s in segs:
|
||||
flat = tuple(round(v, 1) for p in s for v in p)
|
||||
if (rid, flat) in seen: continue
|
||||
seen.add((rid, flat))
|
||||
out_shapes.append([rid] + list(flat))
|
||||
used = {s[0] for s in out_shapes}
|
||||
out = {'routes': {k: v for k, v in routes.items() if k in used}, 'shapes': out_shapes,
|
||||
'source': 'MBTA GTFS ' + open(os.path.join(G, 'feed_info.txt')).read().splitlines()[1].split(',"')[1].split('"')[0]}
|
||||
json.dump(out, open(os.path.join(ROOT, 'city', 'boston', 'transit.json'), 'w'), separators=(',', ':'))
|
||||
bytype = defaultdict(int)
|
||||
for k in used: bytype[routes[k]['type']] += 1
|
||||
print('routes', len(used), dict(bytype), 'shapes', len(out_shapes), 'pts', sum((len(s)-1)//2 for s in out_shapes))
|
||||
print('rail routes:', sorted(k for k in used if routes[k]['type'] != 3))
|
||||
print('missing requested:', [k for k in ['Red','Orange','Blue','Green-B','Green-C','Green-D','Green-E','Mattapan'] if k not in used])
|
||||
@@ -0,0 +1,15 @@
|
||||
import math
|
||||
SCENES = {
|
||||
'nyc': (40.7400, -73.9900, 3500),
|
||||
'boston': (42.3555, -71.0605, 3200),
|
||||
'lax': (33.9425, -118.4081, 3500),
|
||||
'bragg': (35.1580, -79.0090, 6000),
|
||||
}
|
||||
def enu(slug, lat, lon):
|
||||
lat0, lon0, _ = SCENES[slug]
|
||||
x = (lon - lon0) * math.cos(math.radians(lat0)) * 111320
|
||||
y = (lat - lat0) * 110540
|
||||
return round(x, 1), round(y, 1)
|
||||
def inside(slug, x, y, margin=0.0):
|
||||
h = SCENES[slug][2] + margin
|
||||
return abs(x) <= h and abs(y) <= h
|
||||
@@ -0,0 +1,76 @@
|
||||
"""YOLOv8n (ONNX, CPU) over cctv/cam_*.jpg and cctv/nyc/*.jpg -> cctv/detections.json.
|
||||
Model auto-downloads to tools/live/models/yolov8n.onnx (git-ignored)."""
|
||||
import glob, json, os, sys, time, requests
|
||||
import numpy as np, onnxruntime as ort
|
||||
from PIL import Image, ImageDraw
|
||||
from collections import Counter
|
||||
HERE = os.path.dirname(os.path.abspath(__file__)); ROOT = os.path.abspath(os.path.join(HERE, '..', '..'))
|
||||
MODEL = os.path.join(HERE, 'models', 'yolov8n.onnx')
|
||||
MODEL_URL = 'https://huggingface.co/Kalray/yolov8/resolve/main/yolov8n.onnx'
|
||||
KEEP = {0: 'person', 1: 'bicycle', 2: 'car', 3: 'motorcycle', 5: 'bus', 7: 'truck', 9: 'traffic light', 11: 'stop sign'}
|
||||
CONF, IOU, S = 0.16, 0.45, 640
|
||||
if not os.path.exists(MODEL):
|
||||
os.makedirs(os.path.dirname(MODEL), exist_ok=True)
|
||||
open(MODEL, 'wb').write(requests.get(MODEL_URL, timeout=120).content)
|
||||
sess = ort.InferenceSession(MODEL, providers=['CPUExecutionProvider'])
|
||||
inp = sess.get_inputs()[0].name
|
||||
|
||||
def nms(b, s, thr):
|
||||
order = s.argsort()[::-1]; keep = []
|
||||
while order.size:
|
||||
i = order[0]; keep.append(i)
|
||||
xx0 = np.maximum(b[i, 0], b[order[1:], 0]); yy0 = np.maximum(b[i, 1], b[order[1:], 1])
|
||||
xx1 = np.minimum(b[i, 2], b[order[1:], 2]); yy1 = np.minimum(b[i, 3], b[order[1:], 3])
|
||||
inter = np.clip(xx1 - xx0, 0, None) * np.clip(yy1 - yy0, 0, None)
|
||||
a = (b[:, 2] - b[:, 0]) * (b[:, 3] - b[:, 1])
|
||||
iou = inter / (a[i] + a[order[1:]] - inter + 1e-9)
|
||||
order = order[1:][iou <= thr]
|
||||
return keep
|
||||
|
||||
def detect(path):
|
||||
im = Image.open(path).convert('RGB'); W, H = im.size
|
||||
r = min(S / W, S / H); nw, nh = round(W * r), round(H * r); px, py = (S - nw) // 2, (S - nh) // 2
|
||||
canvas = Image.new('RGB', (S, S), (114, 114, 114)); canvas.paste(im.resize((nw, nh), Image.BILINEAR), (px, py))
|
||||
x = np.asarray(canvas, dtype=np.float32).transpose(2, 0, 1)[None] / 255.0
|
||||
p = sess.run(None, {inp: x})[0][0].T # 8400 x 84
|
||||
sc = p[:, 4:]; cls = sc.argmax(1); conf = sc.max(1)
|
||||
m = (conf >= CONF) & np.isin(cls, list(KEEP))
|
||||
p, cls, conf = p[m], cls[m], conf[m]
|
||||
if not len(p): return []
|
||||
b = np.stack([p[:, 0] - p[:, 2] / 2, p[:, 1] - p[:, 3] / 2, p[:, 0] + p[:, 2] / 2, p[:, 1] + p[:, 3] / 2], 1)
|
||||
out = []
|
||||
for c in np.unique(cls): # class-wise NMS
|
||||
idx = np.where(cls == c)[0]
|
||||
for k in nms(b[idx], conf[idx], IOU):
|
||||
j = idx[k]; x0, y0, x1, y1 = b[j]
|
||||
x0 = min(max((x0 - px) / r / W, 0), 1); x1 = min(max((x1 - px) / r / W, 0), 1)
|
||||
y0 = min(max((y0 - py) / r / H, 0), 1); y1 = min(max((y1 - py) / r / H, 0), 1)
|
||||
if x1 - x0 > 0 and y1 - y0 > 0:
|
||||
out.append([round(float(x0), 4), round(float(y0), 4), round(float(x1), 4), round(float(y1), 4), round(float(conf[j]), 3), KEEP[int(c)]])
|
||||
return sorted(out, key=lambda d: -d[4])
|
||||
|
||||
COL = {'person': (255, 60, 60), 'car': (60, 200, 255), 'truck': (255, 180, 0), 'bus': (255, 0, 255), 'bicycle': (0, 255, 120),
|
||||
'motorcycle': (180, 255, 0), 'traffic light': (255, 255, 255), 'stop sign': (255, 0, 0)}
|
||||
def render(path, dets, dst):
|
||||
im = Image.open(path).convert('RGB'); W, H = im.size; d = ImageDraw.Draw(im)
|
||||
for x0, y0, x1, y1, c, n in dets:
|
||||
d.rectangle([x0 * W, y0 * H, x1 * W, y1 * H], outline=COL[n], width=2)
|
||||
d.text((x0 * W + 2, max(0, y0 * H - 11)), f'{n} {c:.2f}', fill=COL[n])
|
||||
im.save(dst, quality=90)
|
||||
|
||||
if __name__ == '__main__':
|
||||
files = sorted(glob.glob(os.path.join(ROOT, 'cctv', 'cam_*.jpg'))) + sorted(glob.glob(os.path.join(ROOT, 'cctv', 'nyc', '*.jpg')))
|
||||
res, tot, t0 = {}, Counter(), time.time()
|
||||
for f in files:
|
||||
rel = os.path.relpath(f, os.path.join(ROOT, 'cctv'))
|
||||
res[rel] = detect(f); tot.update(d[5] for d in res[rel])
|
||||
dt = time.time() - t0
|
||||
json.dump(res, open(os.path.join(ROOT, 'cctv', 'detections.json'), 'w'), separators=(',', ':'))
|
||||
print('images', len(files), 'secs', round(dt, 1), 'ms/img', round(1000 * dt / max(1, len(files))))
|
||||
print('totals', dict(tot.most_common()), 'all', sum(tot.values()))
|
||||
print('nyc totals', dict(Counter(d[5] for k, v in res.items() if k.startswith('nyc/') for d in v)))
|
||||
picks = sys.argv[1:] or [max((k for k in res if k.startswith('nyc/')), key=lambda k: len(res[k])),
|
||||
max((k for k in res if not k.startswith('nyc/')), key=lambda k: len(res[k])),
|
||||
max(res, key=lambda k: sum(d[5] == 'person' for d in res[k]))]
|
||||
for i, k in enumerate(picks, 1):
|
||||
render(os.path.join(ROOT, 'cctv', k), res[k], os.path.join(HERE, f'det_check_{i}.jpg')); print('render', i, k, len(res[k]))
|
||||
@@ -0,0 +1,72 @@
|
||||
"""Poll live ADS-B (adsb.lol, fallback adsb.fi) around each scene 3x, 20 s apart.
|
||||
Writes city/<slug>/air.json with latest sample per aircraft + short track history."""
|
||||
import json, os, sys, time, requests
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import SCENES, enu
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
RAD = {'nyc': 25, 'lax': 25, 'bragg': 25, 'boston': 20}
|
||||
HELI_TYPES = {'EC35','EC30','EC45','EC55','EC20','EC25','EC75','EC13','EC40','AS50','AS55','AS65','AS32','AS3B','H125','H130','H135','H145','H160','H175','B06','B06T','B407','B412','B427','B429','B430','B47G','B505','B212','B222','B230','B105','BK17','R22','R44','R66','S76','S92','S61','S64','S70','H60','UH60','MH60','HH60','H47','CH47','H53','A109','A119','A139','A169','A189','AW09','MI8','MI17','UH1','UH1Y','AH1','AH64','H64','V22','MV22','CABR','G2CA','EN28','EN48','H269','H500','MD52','MD60','NH90','LYNX','PUMA','GAZL','EH10','H1','B505'}
|
||||
H = {'User-Agent': 'gods-eye-view-intro/1.0 (film research)'}
|
||||
def fetch(slug):
|
||||
lat, lon, _ = SCENES[slug]; nm = RAD[slug]
|
||||
for url, key in ((f'https://api.adsb.lol/v2/point/{lat}/{lon}/{nm}', 'ac'),
|
||||
(f'https://opendata.adsb.fi/api/v2/lat/{lat}/lon/{lon}/dist/{nm}', 'aircraft')):
|
||||
try:
|
||||
r = requests.get(url, headers=H, timeout=20); r.raise_for_status(); j = r.json()
|
||||
ac = j.get('ac') or j.get('aircraft') or []
|
||||
now = j.get('now') or time.time() * 1000
|
||||
return url, now / 1000 if now > 1e11 else now, ac
|
||||
except Exception as e:
|
||||
print(slug, 'fail', url, e, file=sys.stderr)
|
||||
return None, time.time(), []
|
||||
def num(v):
|
||||
try: return float(v)
|
||||
except Exception: return None
|
||||
def conv(slug, a):
|
||||
lat, lon = num(a.get('lat')), num(a.get('lon'))
|
||||
if lat is None or lon is None:
|
||||
lp = a.get('lastPosition') or {}
|
||||
lat, lon = num(lp.get('lat')), num(lp.get('lon'))
|
||||
if lat is None: return None
|
||||
x, y = enu(slug, lat, lon)
|
||||
ab = a.get('alt_baro')
|
||||
if ab == 'ground': alt = 0.0
|
||||
else:
|
||||
v = num(ab)
|
||||
if v is None: v = num(a.get('alt_geom'))
|
||||
alt = round(v * 0.3048, 1) if v is not None else None
|
||||
trk = num(a.get('track')); trk = num(a.get('true_heading')) if trk is None else trk
|
||||
gs = num(a.get('gs')); gs = round(gs * 0.514444, 1) if gs is not None else None
|
||||
vr = num(a.get('baro_rate')); vr = num(a.get('geom_rate')) if vr is None else vr
|
||||
vr = round(vr * 0.00508, 2) if vr is not None else None
|
||||
return [x, y, alt, trk, gs, vr, (a.get('flight') or '').strip(), a.get('t') or '', a.get('category') or '']
|
||||
def main():
|
||||
polls = {s: [] for s in SCENES}
|
||||
for i in range(3):
|
||||
t0 = time.time()
|
||||
for s in SCENES:
|
||||
url, t, ac = fetch(s); polls[s].append((url, t, ac)); print(i, s, len(ac), url, flush=True)
|
||||
if i < 2: time.sleep(max(0, 20 - (time.time() - t0)))
|
||||
summary = {}
|
||||
for s in SCENES:
|
||||
latest, tracks, src = {}, {}, set()
|
||||
tlast = 0
|
||||
for url, t, ac in polls[s]:
|
||||
if url: src.add(url.split('/')[2])
|
||||
tlast = max(tlast, t)
|
||||
for a in ac:
|
||||
hx = a.get('hex'); row = conv(s, a)
|
||||
if not hx or row is None: continue
|
||||
if (num(a.get('seen_pos')) or 0) > 60: continue
|
||||
latest[hx] = row
|
||||
tr = tracks.setdefault(hx, [])
|
||||
p = [row[0], row[1], row[2]]
|
||||
if not tr or tr[-1][:2] != p[:2]: tr.append(p)
|
||||
heli = [h for h, r in latest.items() if r[8] == 'A7' or r[7] in HELI_TYPES]
|
||||
out = {'t': int(tlast), 'source': sorted(src), 'radius_nm': RAD[s], 'hex': list(latest.keys()),
|
||||
'a': list(latest.values()), 'tracks': {h: v for h, v in tracks.items() if len(v) > 1}}
|
||||
json.dump(out, open(os.path.join(ROOT, 'city', s, 'air.json'), 'w'), separators=(',', ':'))
|
||||
summary[s] = {'aircraft': len(latest), 'with_track': len(out['tracks']), 'heli': len(heli),
|
||||
'heli_list': [[latest[h][6], latest[h][7], latest[h][8], latest[h][2]] for h in heli]}
|
||||
print(json.dumps(summary, indent=1))
|
||||
if __name__ == '__main__': main()
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Live MBTA vehicle positions -> city/boston/vehicles.json (square + 1 km)."""
|
||||
import json, os, sys, requests
|
||||
from collections import Counter
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import enu, inside
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
j = requests.get('https://cdn.mbta.com/realtime/VehiclePositions_enhanced.json', timeout=30).json()
|
||||
rows, total = [], 0
|
||||
for e in j['entity']:
|
||||
v = e.get('vehicle') or {}; p = v.get('position') or {}
|
||||
if 'latitude' not in p: continue
|
||||
total += 1
|
||||
x, y = enu('boston', p['latitude'], p['longitude'])
|
||||
if not inside('boston', x, y, 1000): continue
|
||||
rid = (v.get('trip') or {}).get('route_id', '')
|
||||
lab = (v.get('vehicle') or {}).get('label') or (v.get('vehicle') or {}).get('id', '')
|
||||
sp = p.get('speed'); rows.append([x, y, p.get('bearing'), round(sp, 2) if sp is not None else None, rid, lab])
|
||||
out = {'t': int(j['header']['timestamp']), 'source': 'cdn.mbta.com VehiclePositions_enhanced.json', 'v': rows}
|
||||
json.dump(out, open(os.path.join(ROOT, 'city', 'boston', 'vehicles.json'), 'w'), separators=(',', ':'))
|
||||
c = Counter(r[4].split('-')[0] if r[4].startswith(('Green', 'CR')) else ('bus' if r[4][:1].isdigit() or r[4][:2] in ('SL','CT') else r[4]) for r in rows)
|
||||
print('t', out['t'], 'feed total', total, 'in square+1km', len(rows), dict(c))
|
||||
@@ -0,0 +1,53 @@
|
||||
"""NYC DOT TMC cameras -> city/nyc/cameras.json + current stills for ~24 nearest cams."""
|
||||
import hashlib, io, json, os, sys, time, requests
|
||||
import numpy as np
|
||||
from PIL import Image, ImageOps
|
||||
from collections import Counter
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import enu, inside
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
OUT = os.path.join(ROOT, 'cctv', 'nyc'); DBG = os.path.join(os.path.dirname(__file__), 'nyc_rejects')
|
||||
os.makedirs(OUT, exist_ok=True); os.makedirs(DBG, exist_ok=True)
|
||||
H = {'User-Agent': 'gods-eye-view-intro/1.0'}
|
||||
cat = requests.get('https://webcams.nyctmc.org/api/cameras', headers=H, timeout=30).json()
|
||||
cams = []
|
||||
for c in cat:
|
||||
if str(c.get('isOnline')).lower() != 'true': continue
|
||||
try: lat, lon = float(c['latitude']), float(c['longitude'])
|
||||
except Exception: continue
|
||||
x, y = enu('nyc', lat, lon)
|
||||
if inside('nyc', x, y): cams.append((x, y, c['id'], c['name'].strip(), lat, lon))
|
||||
json.dump([[x, y, i, n] for x, y, i, n, _, _ in cams], open(os.path.join(ROOT, 'city', 'nyc', 'cameras.json'), 'w'), separators=(',', ':'))
|
||||
print('catalog', len(cat), 'online in square', len(cams))
|
||||
cams.sort(key=lambda c: c[0]**2 + c[1]**2)
|
||||
for f in os.listdir(OUT):
|
||||
if f.startswith('cam_') and f.endswith('.jpg'): os.remove(os.path.join(OUT, f))
|
||||
idx, rejects, hashes, raw_imgs = [], Counter(), Counter(), []
|
||||
fetched_utc = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
|
||||
for x, y, cid, name, lat, lon in cams[:80]:
|
||||
if len(raw_imgs) >= 40: break
|
||||
try:
|
||||
r = requests.get(f'https://webcams.nyctmc.org/api/cameras/{cid}/image', headers=H, timeout=20)
|
||||
r.raise_for_status(); im = Image.open(io.BytesIO(r.content)).convert('RGB')
|
||||
except Exception as e:
|
||||
rejects['error'] += 1; continue
|
||||
h = hashlib.md5(r.content).hexdigest(); hashes[h] += 1
|
||||
g = np.asarray(im.convert('L'), dtype=np.float32)
|
||||
raw_imgs.append(dict(x=x, y=y, id=cid, name=name, lat=lat, lon=lon, im=im, h=h, mean=float(g.mean()), std=float(g.std()), size=im.size, n=len(r.content)))
|
||||
n = 0
|
||||
for d in raw_imgs:
|
||||
reason = None
|
||||
if hashes[d['h']] > 1: reason = 'duplicate/placeholder'
|
||||
elif d['mean'] < 14: reason = 'black'
|
||||
elif d['std'] < 10: reason = 'flat'
|
||||
elif d['size'][0] < 200: reason = 'tiny'
|
||||
if reason:
|
||||
rejects[reason] += 1; d['im'].save(os.path.join(DBG, f"{reason.split('/')[0]}_{d['id'][:8]}.jpg")); continue
|
||||
if n >= 24: break
|
||||
n += 1; fn = f'cam_{n:03d}.jpg'
|
||||
ImageOps.fit(d['im'], (640, 360), Image.LANCZOS).save(os.path.join(OUT, fn), quality=88)
|
||||
idx.append({'file': fn, 'id': d['id'], 'name': d['name'], 'x': d['x'], 'y': d['y'], 'lat': d['lat'], 'lon': d['lon'],
|
||||
'orig_size': list(d['size']), 'fetched_utc': fetched_utc, 'source': 'NYC DOT TMC webcams.nyctmc.org'})
|
||||
print(fn, d['name'], d['size'], round(d['mean']), round(d['std']), round((d['x']**2+d['y']**2)**.5))
|
||||
json.dump(idx, open(os.path.join(OUT, 'index.json'), 'w'), indent=1)
|
||||
print('saved', n, 'rejects', dict(rejects), 'tried', len(raw_imgs))
|
||||
Reference in new issue
Block a user