I don't have a single photo of my great-grandparents. So I built an altar for the people nobody remembers. A developer built Living Portraits, a digital ofrenda that restores unidentified archival photographs using four open-source models — GFPGAN v1.4 for face repair, DDColor for colorization, and Depth Anything V2 for depth — running sequentially on a single small DigitalOcean server. After an early face-animation version made a 1913 portrait of Srinivasa Ramanujan grin, the developer removed all facial motion, re-posing and generated speech, restoring images only through light, color and depth and labeling every download "AI-restored." Each portrait carries its archive source, license, a face-similarity score, and a "Light a candle" counter that increments once per visitor. This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine https://dev.to/challenges/hacktoberfest-week4-2026-10-26 There is an idea from Día de los Muertos that I keep coming back to. A person dies three times. Once when their body stops. Once when they are buried. And a final time, when the last person who remembers them is gone. I went looking for a photograph of my great-grandparents to put on an ofrenda. There isn't one. Nobody in my family has a single picture of them. I don't know their faces at all. So I went to the public archives instead, and found page after page of people in the same situation as my great-grandparents. Portraits from India, a hundred years old and more, labelled with a single line: Unidentified woman. Milk-woman, studio photograph. Indian Christian, India, ca. 1910. Someone loved each of these people. Someone paid for that photograph, dressed them in their best clothes, told them to hold still. And now nobody knows their names. So I built them an altar. A woman photographed for a lantern slide around 1910. Her name was not recorded. Above, the photo as it survives. Below, the same photo after Living Portraits. Living Portraits is a digital ofrenda for old photographs. You place a photo on the altar, and it comes back to you in one short sequence I call the Return : Under every portrait is everything we know about them, which is often almost nothing. A name if there is one. Her name was not recorded if there isn't. A link to the archive the photo came from and its licence, so you can check it is real. And a number showing how closely the restored face still matches the original. If the final death comes when no one remembers you, the smallest thing we can do is remember. Under each portrait there is one button: Light a candle. A fourth candle catches on the altar's shelf, and the server keeps count, one candle per visitor. It isn't much. But a woman whose name nobody wrote down, a hundred years ago, now has a number next to her that only goes up. You can also place your own family's photos on the altar, and save their Return as a short video to send to the family group. My first version animated the faces. One open-source model, a few lines of code, and Srinivasa Ramanujan's portrait from 1913 turned its head and broke into a wide, toothy grin. It looked impressive for about two seconds. Then it felt scary and awkward. A man who never smiled like that for any camera was smiling at me because a model decided he should. These are real people. They can't agree to be made to smile for us, and they can't be asked. So I deleted it. In Living Portraits the face is never moved, never re-posed and never given words. The photo comes back to life through light, colour and depth, the way a real photograph does when you carry it to a window. Every download is labelled AI-restored . That decision changed the whole project, and I think it made it better. Try it live: http://139.59.7.49 http://139.59.7.49 light a candle for someone while you're there Watch the 68-second demo: demo video https://github.com/Tanhaker/living-portraits/blob/main/docs/media/demo.mp4 . A photo is placed on the altar, processed live on DigitalOcean, returns, and gets its first candle. The narration was generated with ElevenLabs. Here is a photo being placed on the altar and processed live on the DigitalOcean server: Four open-source models run one after another on a single small server. The browser does the rest. Mend. GFPGAN v1.4 repairs blur, scratches and damage on the face. I blend its output with the original at 60% instead of 100%, so the person keeps their own face rather than a polished stranger's. Colour. DDColor predicts colour for black-and-white and sepia prints. It only predicts the colour; every bit of light and detail comes from the original photograph. Depth. Depth Anything V2 estimates how near or far each part of the photo is. That depth map is what lets the portrait shift when you move, and what makes the candlelight fall on the face before the wall behind it. Check. A face-recognition model ArcFace, through facexlib compares the face before and after restoration, and that score is printed under every portrait. The server, a small FastAPI app, sends back three images: the photo as it was, the photo restored, and the depth map. The Return itself is a short WebGL shader running in your browser. The candlelight sweep, the colour spreading out from the face located by the face detector , the depth and the flicker are all drawn live, so the original photo is never baked into a video. Face-restoration models are trained to make faces look good , and "good" drifts toward young, smooth and symmetrical. For a stranger's photo, that's fine. For someone who can't tell you what they really looked like, it's a quiet kind of erasure. So I measure it. The ArcFace check compares the face before and after for every photo. Across the eleven archive portraits I tested, ten scored 0.88 or higher, and the median was 0.94. On this scale, around 0.5 already means "probably the same person". The one low score, 0.74, was a side profile, which face models struggle with in general. Most 19th-century portraits are albumen prints: brown ink on cream paper, not black on white. My first black-and-white detector looked at saturation, decided these prints were already in colour, and skipped them. Half of my archive photos came back untouched. The fix was to stop asking "is there colour?" and ask "does the colour depend on anything except brightness?" In a toned print, dark means brown and light means cream, and that's all. So I fit colour as a function of brightness and look at what's left over: python def is black and white img bgr : small = cv2.resize img bgr, 256, 256 lab = cv2.cvtColor small, cv2.COLOR BGR2LAB .reshape -1, 3 .astype np.float32 L, a, b = lab :, 0 / 255, lab :, 1 , lab :, 2 X = np.stack np.ones like L , L, L L, L 3 , 1 colour as a curve of brightness leftover = sum np.std c - X @ np.linalg.lstsq X, c, rcond=None 0 for c in a, b return leftover < 4.0 sepia and grey prints leave almost nothing behind Real colour photos leave a lot behind. Sepia and grey prints leave almost nothing. It now sorts every archive photo I have correctly. DDColor's first results were vivid in a way old photographs never are: magenta suits, orange hands. I scale the predicted colour back by a quarter. The portraits look less like an advert and more like a hand-tinted print, which is closer to the truth of the era. The whole altar lives on one DigitalOcean CPU-Optimized Droplet , and DigitalOcean is a big part of why one person could build and ship this in a weekend. It sits close to the people it's for. The portraits are from India, and so are many of the families I hope will use it, so the Droplet runs in DigitalOcean's Bangalore region. Dedicated cores, steady timing. Restoring a photo is heavy, continuous number-crunching. On a CPU-Optimized Droplet with 4 dedicated vCPUs, a photo takes about 12 seconds, every time, with no noisy neighbours slowing it down. One machine runs everything. The four models, the FastAPI server, the candle counts and the web page itself all live on the same Droplet as a single systemd service. There is no separate GPU service, no third-party AI API, and nothing else to keep in sync. From a blank Droplet to live with one script. setup.sh in the repo installs the models on a fresh Ubuntu Droplet, and the service file keeps the app running across restarts. Every update during the build was a copy and a service restart. It doesn't need a GPU. I planned to rent a GPU Droplet. Then I removed the face animation, the heaviest model, and found everything else fits comfortably on CPU: about 12 seconds per photo on the Droplet, against about 5 seconds on my laptop's 4 GB RTX 3050. The cost is small and predictable. The Droplet costs $0.125 an hour, which works out to roughly $0.0004 of compute per photo, and the whole build ran on DigitalOcean's Hacktoberfest credit. That matters for who can use this. A family, a small local archive or a school history project can run their own altar for a few dollars a day, or switch it off when no one is visiting. Commercial apps already "bring old photos to life". To use them, you upload your family's photographs to a company's servers, usually on a subscription, and accept whatever the model decides to do to the faces of people who can't object. Every decision that shaped this project was only possible because the models were open: And the photographs never leave a server the family controls. Nothing is sent to an AI company, and nothing can be taken away by a subscription ending or an app shutting down. A memory deserves at least that. Her first candle was lit while I was recording the demo. The script uploaded a portrait from the 1880s, a young woman in her best sari with every ornament she owned, captioned Her name was not recorded. The Return played, the colour came back into her face, and then the button was pressed and the count changed from nothing to 1 candle lit . It's a small number. But it's one more than she had yesterday. Her portrait is in the gallery on the live site. If you visit, light one for her. Best Use of DigitalOcean. The entire app, from the four models to the API, the candle counts and the page, runs on one DigitalOcean CPU-Optimized Droplet, deployed with the setup script and systemd service in the repo. It shows that open-source image models don't need a GPU to be useful to a family. Best Use of ElevenLabs. The demo's narration was generated with ElevenLabs, and the repo includes the script and the tools that cut the screen recording to each narrated paragraph. Bring an old family photo to the ofrenda. Living Portraits mends its creases, returns its colour and gives it depth, using only open-source models on a server you control. The face is never moved, re-posed or given words. The photo comes back to life through light, colour and depth instead. Built for the DEV Hacktoberfest Open-Source AI Challenge, Week 4: Ghost in the Machine . When a photo is placed on the altar, the page plays one short sequence on a WebGL canvas: The page can record the Return as a video to… Built with help from an AI coding assistant. Every portrait shown is public domain or CC0, from Wikimedia Commons, with sources listed in the repository README.