#!/usr/bin/env python3 """依頼154(pan149.py の写し:カメラは cams154(寄り 救急の待合・正面玄関から救急へ行く道を足す)、静止画は8つ×2つの時刻)。もとの説明:依頼149(pan147.py の写しを直した:カメラは cams149、静止画は6つ×2つの時刻、エレベーター・トイレ)。もとの説明:依頼147(pan143.py の写しを直した:カメラは cams147、静止画7枚、題に人の動き)。もとの説明:依頼143:h1 の静止画(8枚)と1日の早回しの動画に、輪・パネル・凡例・上の帯を付ける(1920×1080)。依頼141 の pan141.py と同じ作り (輪・凡例・パネルの形は依頼140 の pan140.py)。違うのは:病院 h1、ロボットの状態(台の前で待っている・積む所へ移る)、 行き先の名前を短く、道(緑・青(表)・青(裏口)・赤)、引きは西半分と東半分、問題を 廊下/部屋の中 に分けて帯に出す。 python3 pan143.py stills <回のフォルダ> <出力フォルダ> python3 pan143.py video <回のフォルダ> flowday <出力.mp4> [並べる数] python3 pan143.py frame <回のフォルダ> flowday <コマ番号> <出力.png> """ import csv, json, math, os, subprocess, sys, tempfile from multiprocessing import Pool from PIL import Image, ImageDraw sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import pan140 as P from cams154 import cams as cams143, still_cams W, H, SW = P.W, P.H, P.SW SHORT = {"救急室の処置室": "救急室", "内科外来の処置室": "内科(表)", "内科外来の処置室(裏口)": "内科(裏口)", "薬剤部の払い出し口": "薬剤部", "検査部の検体受付": "検体受付", "外来化学療法室の受付": "化学療法室"} ROUTE_JA = {"green": "緑", "blue": "青(表)", "blue_back": "青(裏口)", "red": "赤"} STATE = [("待ち", "台の前で待っている"), ("行き", "行き"), ("帰り", "帰り"), ("移る", "積む所へ移る")] CAMJA = {c[0]: c[6] + ("" if c[0] == "top_all" else "・75°見下ろし・左30°") for c in cams143()} CAMJA.update({n: CAMJA[c[0]] for n, c, tk in still_cams()}) RUN = None DATA = {} class PathCam: def __init__(self, proj, row, w, h, name): self.name = name self.w, self.h = w, h self.p = proj self.v = [[float(row["v%d%d" % (r, k)]) for k in range(4)] for r in range(4)] self.sx, self.sy = SW / w, H / h px = P.Cam.px ppm = P.Cam.ppm def counts(ev, t0, t): c = {k: 0 for k in range(1, 7)} c["fast"] = c["wait2"] = c["room"] = 0 for e in ev: te = float(e["t_s"]) if te < t0 - 1e-6 or te > t + 1e-6: continue k = int(e["type"]) if e["dir"] == "待ち": if k == 2: c["wait2"] += 1 continue c[k] += 1 if e.get("area") == "部屋の中" and k <= 5: c["room"] += 1 if k == 2 and e.get("note") == "速いまま": c["fast"] += 1 return c def panel(im, row, ev, t, t0, title0, camja, summ, wide): d = ImageDraw.Draw(im) d.rectangle([SW, 0, W, H], fill=P.PANEL_BG) x0, xr = SW + 40, W - 40 f_s, f_m, f_mb, f_big, f_num = P.font(False, 24), P.font(False, 27), P.font(True, 29), P.font(True, 54), P.font(True, 46) d.text((x0, 24), "病院 h1・人の動き・%s・%.1fm/秒・種%d" % (summ["plan"], summ["speed_mps"], summ["seed"]), font=f_s, fill=P.GRAY) d.text((x0, 56), "色 %s・車いすの場所 %s" % (P.SCHEME_JA["A"], "あり" if summ.get("wheelchair_marks") == "on" else "なし"), font=f_s, fill=P.GRAY) d.text((x0, 88), "カメラ %s" % camja, font=P.font(False, 22 if len(camja) > 18 else 24), fill=P.GRAY) d.text((x0, 122), P.clock(t), font=f_big, fill=P.INK) y = 206 d.text((x0, y), "ロボット・スタッフがしていること", font=f_mb, fill=P.INK) y += 52 d.text((x0, y), "ロボット 1台", font=f_m, fill=P.INK) y += 44 cur = row["leg_dir"] for key, ja in STATE: on = key == cur d.text((x0 + 8, y), ja, font=f_m, fill=P.INK if on else (150, 150, 148)) if on: d.ellipse([xr - 40, y + 2, xr - 4, y + 38], fill=P.WHITE, outline=(80, 80, 80), width=3) if key in ("行き", "帰り", "移る") and row["leg_from"]: d.text((xr - 52, y + 20), "%s→%s" % (SHORT.get(row["leg_from"], row["leg_from"]), SHORT.get(row["leg_to"], row["leg_to"])), font=f_s, fill=P.GRAY, anchor="rm") elif key == "待ち" and row["leg_from"]: d.text((xr - 52, y + 20), SHORT.get(row["leg_from"], row["leg_from"]), font=f_s, fill=P.GRAY, anchor="rm") y += 40 y += 10 d.text((x0, y), "スタッフ", font=f_m, fill=P.INK) y += 44 rs = int(row["rescue_staff"]) d.text((x0 + 8, y), "ロボットの助け", font=f_m, fill=P.INK if rs else (150, 150, 148)) for i in range(rs): d.rectangle([xr - 36 - i * 44, y + 3, xr - 4 - i * 44, y + 35], fill=P.BLUE) y += 52 d.line([x0, y, xr, y], fill=P.LINE, width=2) y += 14 d.text((x0, y + 8), "大廊下の人(x0〜70)", font=f_m, fill=P.INK) d.text((xr, y + 28), row["hall_people"], font=f_num, fill=P.INK, anchor="rm") y += 66 d.line([x0, y, xr, y], fill=P.LINE, width=2) y += 14 d.text((x0, y), title0, font=f_mb, fill=P.INK) y += 46 c = counts(ev, t0, t) for k in range(1, 6): d.ellipse([x0 + 6, y + 6, x0 + 30, y + 30], outline=P.COLORS[k], width=4) d.text((x0 + 44, y + 18), "%d %s" % (k, P.TYPES[k]), font=f_m, fill=P.INK, anchor="lm") d.text((xr, y + 18), str(c[k]), font=P.font(True, 34), fill=P.INK, anchor="rm") y += 42 d.ellipse([x0 + 6, y + 6, x0 + 30, y + 30], outline=P.COLORS[2], width=4) d.text((x0 + 44, y + 18), "台の前で待っている間のひやり", font=f_s, fill=P.INK, anchor="lm") d.text((xr, y + 18), str(c["wait2"]), font=P.font(True, 34), fill=P.INK, anchor="rm") y += 48 d.rectangle([x0 + 9, y + 5, x0 + 27, y + 23], outline=P.FAST_COL, width=3) d.rectangle([x0 + 4, y, x0 + 32, y + 28], outline=P.FAST_COL, width=2) d.text((x0 + 44, y + 14), "うち速いままのひやり(走っている間) %d" % c["fast"], font=f_s, fill=P.INK, anchor="lm") y += 36 d.rectangle([x0 + 5, y + 1, x0 + 31, y + 27], outline=(255, 210, 0), width=4) d.line([x0 + 9, y + 5, x0 + 27, y + 23], fill=P.OVER_COL, width=5) d.line([x0 + 9, y + 23, x0 + 27, y + 5], fill=P.OVER_COL, width=5) d.text((x0 + 44, y + 14), "止まりきれなかった %d" % c[6], font=f_s, fill=P.INK, anchor="lm") y += 38 note = "輪=問題の起きた所(10秒)" + ("・白い輪=ロボット" if wide else "") d.text((x0, min(y + 2, H - 34)), note, font=P.font(False, 21), fill=P.GRAY) return c def legend_bottom(im): box = im.crop((0, H - 104, SW, H)) P.legend(box) im.paste(box, (0, H - 104)) def load(run, cam): rows = [r for r in csv.DictReader(open(os.path.join(run, "look139", "look_frames.csv"), encoding="utf-8")) if r["kind"] == "path" and r["cam"] == cam] paths = [r for r in csv.DictReader(open(os.path.join(run, "look139", "look_path.csv"), encoding="utf-8")) if r["cam"] == cam] base = os.path.dirname(os.path.dirname(run)) meta = [r for r in csv.DictReader(open(os.path.join(base, "camspath154_meta.csv"), encoding="utf-8")) if r["cam"] == cam] assert len(rows) == len(paths), (len(rows), len(paths)) if cam == "flowday": assert len(rows) == len(meta), (len(rows), len(meta)) for a, b in zip(rows, meta): assert abs(float(a["t_s"]) - float(b["t_s"])) < 0.051, (a["t_s"], b["t_s"]) j = json.load(open(os.path.join(run, "look139", "cam_%s.json" % cam), encoding="utf-8")) ev = list(csv.DictReader(open(os.path.join(run, "events.csv"), encoding="utf-8"))) summ = json.load(open(os.path.join(run, "summary.json"), encoding="utf-8")) trips = {r["trip_no"]: r for r in csv.DictReader(open(os.path.join(run, "trips.csv"), encoding="utf-8"))} return dict(rows=rows, paths=paths, meta=meta, proj=j["projectionMatrix"], w=j["width"], h=j["height"], ev=ev, summ=summ, cam=cam, trips=trips) def compose(i): D = DATA r, pr = D["rows"][i], D["paths"][i] t = float(r["t_s"]) src = Image.open(os.path.join(RUN, "look139", r["file"])).convert("RGB").resize((SW, H), Image.LANCZOS) im = Image.new("RGB", (W, H), P.PANEL_BG) im.paste(src, (0, 0)) m = D["meta"][i] a = float(m["near_a"]) wide = a < 0.5 cam = PathCam(D["proj"], pr, D["w"], D["h"], "w_" if wide else "n_") P.WIDE.add("w_") P.overlay(im, cam, D["ev"], t, P.font(True, 22), (float(r["robot_x"]), float(r["robot_z"]))) legend_bottom(im) sp = m["speed"] trip = r["trip_no"] if r["trip_no"] not in ("", "0") else "" sd = "西半分" if m["side"] == "west" else "東半分" camja = ("引き・%s・75°見下ろし・左30°" % sd) if a <= 0 else ("寄り・75°見下ろし・左30°・付いて行く" if a >= 1 else "引き⇄寄りに切り替え中") c = panel(im, r, D["ev"], t, 0.0, "7時30分からの1日の問題", camja, D["summ"], wide) route = "" if trip and trip in D["trips"]: route = " %s便目・%s(%s→%s)" % (trip, ROUTE_JA.get(D["trips"][trip]["route"], ""), SHORT.get(D["trips"][trip]["from"], ""), SHORT.get(D["trips"][trip]["to"], "")) elif r["leg_dir"] == "移る": route = " 積む所へ移る(%s→%s)" % (SHORT.get(r["leg_from"], r["leg_from"]), SHORT.get(r["leg_to"], r["leg_to"])) t1 = "%s 店の時刻の%s%s" % (P.clock(t), sp, route if sp == "5倍" else "") t2 = "1日の問題(走っている間) 1 急に止まった %d・2 ひやり %d・3 人がよけさせられた %d・4 立ち往生 %d・5 横切り %d(うち部屋の中 %d)" % ( c[1], c[2], c[3], c[4], c[5], c["room"]) P_banner(im, t1, t2, "台の前で待っている間のひやり %d" % c["wait2"]) return im def P_banner(im, text1, text2, text3=""): lay = Image.new("RGBA", (SW, 140 if text3 else 104), (249, 247, 244, 225)) d = ImageDraw.Draw(lay) d.text((24, 31), text1, font=P.font(True, 34 if len(text1) < 40 else 30), fill=P.INK, anchor="lm") d.text((24, 80), text2, font=P.font(False, 22), fill=P.INK, anchor="lm") if text3: d.text((24, 116), text3, font=P.font(False, 22), fill=P.INK, anchor="lm") im.paste(lay, (0, 0), lay) def still(run, cam, out, what): D = load(run, cam) assert len(D["rows"]) == 1, (cam, len(D["rows"])) r, pr = D["rows"][0], D["paths"][0] t = float(r["t_s"]) src = Image.open(os.path.join(run, "look139", r["file"])).convert("RGB").resize((SW, H), Image.LANCZOS) im = Image.new("RGB", (W, H), P.PANEL_BG) im.paste(src, (0, 0)) wide = not cam.startswith("n_") pc = PathCam(D["proj"], pr, D["w"], D["h"], "w_" if wide else "n_") P.WIDE.add("w_") P.overlay(im, pc, D["ev"], t, P.font(True, 22), (float(r["robot_x"]), float(r["robot_z"]))) legend_bottom(im) panel(im, r, D["ev"], t, 0.0, "7時30分からの1日の問題", CAMJA[cam], D["summ"], wide) P_banner(im, "%s %s" % (P.clock(t), CAMJA[cam].split("・")[0] + "・" + CAMJA[cam].split("・")[1] if "・" in CAMJA[cam] else CAMJA[cam]), what) f = os.path.join(out, "still154_%s.png" % cam) im.save(f) return f, t def init(run, cam): global RUN, DATA RUN = run DATA = load(run, cam) def seg(args): run, cam, lo, hi, dst = args init(run, cam) p = subprocess.Popen([P.FFMPEG, "-y", "-loglevel", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", "%dx%d" % (W, H), "-framerate", "25", "-i", "-", "-c:v", "libx264", "-pix_fmt", "yuv420p", "-crf", "18", "-g", "50", dst], stdin=subprocess.PIPE) for i in range(lo, hi): p.stdin.write(compose(i).tobytes()) p.stdin.close() p.wait() return dst def main(): mode, run = sys.argv[1], sys.argv[2] if mode == "stills": out = sys.argv[3] os.makedirs(out, exist_ok=True) base = os.path.dirname(os.path.dirname(run)) whats = {r["cam"]: r["what"] for r in csv.DictReader(open(os.path.join(base, "camspath154_stills.csv"), encoding="utf-8"))} for c, _, _ in still_cams(): print(*still(run, c, out, whats[c])) return cam = sys.argv[3] if mode == "frame": init(run, cam) compose(int(sys.argv[4])).save(sys.argv[5]) print(sys.argv[5], DATA["rows"][int(sys.argv[4])]["t_s"]) return dst = sys.argv[4] nproc = int(sys.argv[5]) if len(sys.argv) > 5 else 8 n = len(load(run, cam)["rows"]) tmp = tempfile.mkdtemp(prefix="pan154_", dir=os.path.dirname(os.path.abspath(dst))) k = max(1, math.ceil(n / (nproc * 4))) jobs = [(run, cam, lo, min(n, lo + k), os.path.join(tmp, "s%04d.mp4" % (lo // k))) for lo in range(0, n, k)] with Pool(nproc) as pool: parts = pool.map(seg, jobs, chunksize=1) lst = os.path.join(tmp, "list.txt") open(lst, "w").write("".join("file '%s'\n" % q for q in parts)) subprocess.check_call([P.FFMPEG, "-y", "-loglevel", "error", "-f", "concat", "-safe", "0", "-i", lst, "-c", "copy", "-movflags", "+faststart", dst]) for q in parts: os.remove(q) os.remove(lst); os.rmdir(tmp) print(dst, n, "コマ", n / 25.0, "秒") if __name__ == "__main__": main()