ppbbww

Pillar Point boats, birds, and waves watcher
git clone git@abtrout.com:ppbbww.git
Log | Files | Refs | README | LICENSE

commit abcf58eaa84bb49c4a8be269e7435a62606a9888
parent 897dc2fb4dcc80db65ef52a2b88b23f242f32031
Author: david cochran <about.trout@gmail.com>
Date:   Tue,  1 Apr 2025 20:06:34 -0700

remove gallery

i stopped updating it

Diffstat:
MREADME.md | 4----
Mppboatwatch/archive.py | 39++-------------------------------------
Dppboatwatch/gallery.html | 101-------------------------------------------------------------------------------
Dppboatwatch/gallery.py | 114-------------------------------------------------------------------------------
Mpyproject.toml | 2--
5 files changed, 2 insertions(+), 258 deletions(-)

diff --git a/README.md b/README.md @@ -2,10 +2,6 @@ _Pillar Point boats, birds, [~~and waves~~](https://github.com/abtrout/ppbbww/is [`ppbw`](/ppboatwatch/ppbw.py) is a Python project that samples frames from the [Mavericks](https://en.wikipedia.org/wiki/Mavericks,_California) Surfline cam and runs [facebook/detr-resnet-50](https://huggingface.co/facebook/detr-resnet-50) on them to detect various objects, e.g. boats and birds. Matches are archived in a SQLite database and announced to a shared Slack channel to be enjoyed with friends. -After running this for a while, _many neat images_ were captured, which lead to the creation of the gallery. There's a small [`curator`](/ppboatwatch/gallery.py#L77-L80) web app for hand picking gallery-worthy images from the matches database, and a script to [`generate`](/ppboatwatch/gallery.py#L83-L114) a static Jekyll site that's suitable to be deployed on my [`gh-pages`](https://github.com/abtrout/ppbbww/tree/gh-pages) branch. I curate by hand and manually push updates sometimes, ¯\\\_(ツ)_/¯. - -__[Check out the gallery here!](https://abtrout.github.io/ppbbww)__ - #### Getting started ``` diff --git a/ppboatwatch/archive.py b/ppboatwatch/archive.py @@ -10,8 +10,7 @@ CREATE TABLE IF NOT EXISTS matches ( filename, label, score, - x0, y0, x1, y1, - gallery DEFAULT NULL + x0, y0, x1, y1 )""" ADD_MATCH_SQL = """ @@ -19,24 +18,9 @@ INSERT INTO matches (ts, filename, label, score, x0, y0, x1, y1) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""" LIST_MATCHES_SQL = """ -SELECT ts, filename, label, score, x0, y0, x1, y1, gallery +SELECT ts, filename, label, score, x0, y0, x1, y1 FROM matches ORDER BY ts DESC""" -LIST_GALLERY_MATCHES_SQL = """ -SELECT ts, filename, label, score, x0, y0, x1, y1, gallery -FROM matches WHERE gallery IS NOT NULL ORDER BY ts DESC""" - -UPDATE_GALLERY_SQL = """ -UPDATE matches SET gallery = ? WHERE ts = ?""" - -GET_MATCH_PAGE_SQL = """ -SELECT ts, filename, label, score, x0, y0, x1, y1, gallery, previous_ts, next_ts -FROM (SELECT *, - LAG(ts, 1) OVER (ORDER BY ts DESC) AS next_ts, - LEAD(ts, 1) OVER (ORDER BY ts DESC) AS previous_ts - FROM matches ORDER BY ts DESC -) WHERE ts = ?""" - class Archive: def __init__(self, db_file): @@ -55,22 +39,3 @@ class Archive: with closing(self.con.cursor()) as cur: return cur.execute(LIST_MATCHES_SQL).fetchall() - def list_gallery_matches(self): - with closing(self.con.cursor()) as cur: - return cur.execute(LIST_GALLERY_MATCHES_SQL).fetchall() - - def get_match_page(self, ts): - with closing(self.con.cursor()) as cur: - return cur.execute(GET_MATCH_PAGE_SQL, (ts,)).fetchone() - - def set_gallery(self, ts): - return self.__update_gallery(ts, 1) - - def unset_gallery(self, ts): - return self.__update_gallery(ts, None) - - def __update_gallery(self, ts, value): - with closing(self.con.cursor()) as cur: - res = cur.execute(UPDATE_GALLERY_SQL, (value, ts)) - self.con.commit() - return res diff --git a/ppboatwatch/gallery.html b/ppboatwatch/gallery.html @@ -1,101 +0,0 @@ -<!doctype html> -<html> - <head> - <title>Ppbbww | {{ date_time }}</title> - <style type="text/css"> - * { - padding: 0; - margin: 0; - } - .container { - width: 90%; - max-width: 800px; - margin: 5em auto; - font-family: monospace; - font-size: 1.5em; - color: #626262; - } - .column { - float: left; - width: 33.3%; - } - .row { margin: 10px 0; } - .row:after { - content: ""; - display: table; - clear: both; - } - a { - font-weight: bold; - text-decoration: none; - color: #626262; - } - .text-left { text-align: left; } - .text-right { text-align: right; } - .text-center { text-align: center; } - .title { - font-size: 2em; - text-decoration: none; - color: #626262; - } - .button-red, .button-green { - border: 0; - border-radius: 0.75em; - padding: 0.5em 1em; - margin: 1em 0; - font-family: monospace; - font-size: 1em; - color: #f2f2f2; - } - .button-green { background-color: green; } - .button-red { background-color: red; } - </style> - </head> - <body> - <div class="container"> - <div class="row"> - <a href="/" class="title">Ppbbww</a> - </div> - <img src="/{{ filename }}" width=100% height=auto> - <div class="row"> - <div class="column text-left"> - {% if prev_ts %} - <a href="/gallery/{{ prev_ts }}">Older posts</a> - {% endif %} - &nbsp; - </div> - <div class="column text-center"> - {{ date_time }} - </div> - <div class="column text-right"> - &nbsp; - {% if next_ts %} - <a href="/gallery/{{ next_ts }}">Newer posts</a> - {% endif %} - <div></div> - </div> - <div class="row text-center"> - {% if gallery %} - <button type="button" class="button-red" onClick="unsetGallery({{ ts }})">Remove from Gallery</button> - {% else %} - <button type="button" class="button-green" onClick="setGallery({{ ts }})">Add to Gallery</button> - {% endif %} - </div> - </div> - </div> - <script type="text/javascript"> - function setGallery(ts) { - const req = new XMLHttpRequest(); - req.open("PUT", `/gallery/${ts}`) - req.onreadystatechange = function() { location.reload() } - req.send() - } - function unsetGallery(ts) { - const req = new XMLHttpRequest(); - req.open("DELETE", `/gallery/${ts}`) - req.onreadystatechange = function() { location.reload() } - req.send() - } - </script> - </body> -</html> diff --git a/ppboatwatch/gallery.py b/ppboatwatch/gallery.py @@ -1,114 +0,0 @@ -import aiohttp_jinja2 -import jinja2 -import os -import shutil -import sys - -from aiohttp import web -from datetime import datetime -from itertools import groupby - -from .archive import Archive - - -class Curator: - def __init__(self, db_file): - self.archive = Archive(db_file) - self.app = web.Application() - self.app.add_routes( - [ - web.get("/", self.redirect_latest), - web.get("/gallery/{ts}", self.display_match), - web.put("/gallery/{ts}", self.set_gallery), - web.delete("/gallery/{ts}", self.unset_gallery), - web.static("/data", "./data"), - ] - ) - aiohttp_jinja2.setup(self.app, loader=jinja2.FileSystemLoader("./ppboatwatch/")) - - def run(self): - web.run_app(self.app) - - async def redirect_latest(self, request): - ts, _, _, _, _, _, _, _, _ = self.archive.list_matches()[0] - raise web.HTTPFound(f"/gallery/{ts}") - - async def display_match(self, request): - ts = int(request.match_info["ts"]) - (ts, filename, label, score, x0, y0, x1, y1, gallery, previous_ts, next_ts) = ( - self.archive.get_match_page(ts) - ) - dt = datetime.fromtimestamp(int(ts)).strftime("%Y-%m-%d %I:%M:%S") - context = { - "date_time": dt, - "prev_ts": previous_ts, - "ts": ts, - "next_ts": next_ts, - "filename": filename, - "label": label, - "score": score, - "x0": x0, - "y0": y0, - "x1": x1, - "y1": y1, - "gallery": True if gallery else False, - } - return aiohttp_jinja2.render_template("gallery.html", request, context) - - async def set_gallery(self, request): - ts = request.match_info["ts"] - try: - self.archive.set_gallery(int(ts)) - return web.Response(status=200) - except Exception as ex: - logging.error(f"Failed to set_gallery: {ex}") - return web.Response(status=500) - - async def unset_gallery(self, request): - ts = request.match_info["ts"] - try: - self.archive.unset_gallery(int(ts)) - return web.Response(status=200) - except Exception as ex: - logging.error(f"Failed to set_gallery: {ex}") - return web.Response(status=500) - - -# Entry point for gallery curation web UI. -def curate(): - curator = Curator("archive.db") - curator.run() - - -# Entry point for static gallery generation; produces _layouts and -# assets/img folders for jekyll gh-pages site, based on the `gallery` -# column in an Archive. -def generate(): - if not os.path.exists("gallery"): - os.makedirs("gallery") - if not os.path.exists("_posts"): - os.makedirs("_posts") - - rows = Archive("archive.db").list_gallery_matches() - all_matches = list(groupby(rows, key=lambda cols: (cols[0], cols[1]))) - for i, ((ts, f), matches) in enumerate(all_matches): - # Copy frame file to gallery. - shutil.copyfile(f, f"gallery/{ts}.jpg") - # Make a new post. - ymd = datetime.fromtimestamp(int(ts)).strftime("%Y-%m-%d") - with open(f"_posts/{ymd}-{ts}.md", "w") as post_file: - dt = datetime.fromtimestamp(int(ts)).strftime("%Y-%m-%d %I:%M:%S") - # TODO: Could add lag/lead to LIST_GALLERY_MATCHES_SQL query instead. - next_ts = all_matches[i - 1][0][0] if i > 0 else None - prev_ts = all_matches[i + 1][0][0] if i < len(all_matches) - 1 else None - post_file.writelines( - [ - "---\n", - "layout: default\n", - f"ts: {ts}\n", - f"prev_ts: {prev_ts}\n" if prev_ts else "", - f"next_ts: {next_ts}\n" if next_ts else "", - f'date_time: "{dt}"\n', - "---\n", - ] - ) diff --git a/pyproject.toml b/pyproject.toml @@ -53,9 +53,7 @@ dependencies = [ ] [project.scripts] -curate-gallery = "ppboatwatch.gallery:curate" find-objects = "ppboatwatch.find_objects:main" -gen-gallery = "ppboatwatch.gallery:generate" ppbbww = "ppboatwatch.ppbbww:main" sample-streams = "ppboatwatch.sample_streams:main"