This is my submission for the Hacktoberfest Open-Source AI challenge, Side Hustle week.
Three thousand photos, one weekend
After a wedding, a photographer has around 3,000 frames and needs to throw away two-thirds of them before editing can begin. That means squinting at near-identical bursts, hunting for blinks, and zooming in to check focus, one photo at a time. It takes hours. It is also the one step of the job that clients never see and never want to pay for.
AI cullers exist, but they ask for the one thing a photographer can't hand over: the whole shoot, uploaded to a server they don't control. Wedding, family and school photos come with privacy promises. For many photographers, that rules those tools out.
That gap is the idea behind Cull.
What Cull does
Cull sorts a shoot into keepers and rejects, and every step runs on the photographer's own machine. It is plain HTML, CSS and JavaScript, with no backend and no build step.
Step 1: a fast first pass, in the browser. Each photo is scored on sharpness and exposure, and a perceptual hash groups bursts so only the best frame of each one survives. The sharpness score is the variance of a Laplacian filter over a downscaled copy, which is cheap and works well for catching blur:
const l = 4*Y[i] - Y[i-1] - Y[i+1] - Y[i-w] - Y[i+w]; // Laplacian at each pixel
s += l; s2 += l*l; n++;
const sharpness = s2/n - (s/n)**2; // variance = how sharp
Step 2: Gemma makes the hard calls. The photos that land close to the keep threshold are exactly the ones maths can't judge: is that a blink, is the face soft or the background? After upload, Cull sends each borderline photo to Gemma 3 running in the photographer's own Ollama and asks whether the eyes are open, the subject is sharp and the composition is worth keeping. Gemma answers KEEP or REJECT, and that verdict overrides the score.
fetch(`${ollamaUrl}/api/generate`, {
method: 'POST',
body: JSON.stringify({ model: 'gemma3:4b', stream: false, prompt, images: [jpegBase64] })
});
Step 3: the AI is visible, and the human stays in charge. The workspace has a Gemma panel that shows the model, the score, Gemma's reasoning and how many photos the AI has judged. Photos Gemma decided carry an AI badge. K and X keep or reject, and any photo can be compared with the best frame of its burst.
Step 4: real output. Export a ZIP of the kept originals, or a CSV of every decision with its reason.
Around that are the practical pieces: adjustable thresholds and weights, saved shoots, a Models page that lists what your Ollama has installed, and a local-only account so settings persist. Nothing leaves the machine except requests to localhost.
Why open innovation matters for what I built
Privacy is the product. With a hosted vision API, my pitch would be "trust us with your clients' faces". With open weights it becomes "there is nothing to trust: open the network tab and look." For wedding, family and school photographers, that is the reason to buy.
The economics only work with a local model. Hosted vision APIs charge per image, and a single wedding is thousands of images. Local inference costs nothing per photo, so I can charge a flat price and never meter a customer's usage.
I can swap and tune the model. The model is one setting in the app. When a better open vision model appears, I change one string. The critique prompt is editable too, so a portrait photographer and a wildlife photographer can ask Gemma different questions. A closed API would leave me with whatever the provider decides to ship, at whatever price they decide to charge.
Where the open approach fell short. A 4B-parameter model is weaker than the largest closed models, and each Gemma call takes a few seconds on a laptop. That is why Gemma only sees the borderline photos and the cheap maths handles the rest. I haven't benchmarked Cull against a professional culler, so I won't quote an accuracy number. RAW files aren't read directly yet, so photographers export JPG previews first.
Try it
https://github.com/tanisha-naruka/Cull-project.git
https://drive.google.com/file/d/1J6mHriBak1qwHotPanyfiztWmIZcqEB3/view?usp=sharing
ollama pull gemma3:4b
OLLAMA_ORIGINS=* ollama serve # Windows PowerShell: $env:OLLAMA_ORIGINS="*"; ollama serve
Open index.html (or run npm start), create a local account, upload a folder of JPGs and watch Gemma review the borderline ones. If Ollama isn't running, Cull still works using the maths-only first pass.
The side hustle
Cull is free for local use. The paid version I'd sell is a one-off licence for XMP sidecars, so every keep and reject lands directly in Lightroom. I haven't built that part yet. Because there are no servers to run, almost all of the price is margin.
Prize categories
Best Use of Gemma: Gemma 3 is the judge in the pipeline, running locally through Ollama.
Top comments (0)