Case Study 1 — Nineteen Thousand Carves, Thirty-Four Hundred That Mattered

A wildlife photographer formatted the wrong CF card after a two-week expedition, then shot over part of it before noticing. There was no backup of a once-a-year migration he could not reshoot. Carving brought back almost everything — but it was the file format, not hope, that decided which frames came home whole, and how a flat pile of nameless files became a sorted archive again.

Background

A wildlife photographer returned from a two-week expedition with a single 256 GB CompactFlash card holding roughly 3,400 frames — each shot as a paired CR2 RAW and a full-resolution JPEG, the way he always worked. The trip's centerpiece was a migratory bird event that happens for about ten days a year in one place; the images were, in the literal sense, irreplaceable.

Home, jet-lagged, he meant to import the card that night. Instead he slotted it into the camera the next morning, thought it was a spare, and chose Format card from the menu. He then shot about 80 frames of his own backyard before the empty card icon and the missing trip registered. He stopped, ejected the card, put it in its case, and brought it in. His intake notes were exact: SanDisk Extreme Pro 256 GB CF, exFAT, physically perfect, reads cleanly, no errors; condition received powered down, in case, in person.

This was unambiguously a logical job — an accidental format, not a failed card — but it was past the easy line. The in-camera format had written a fresh, empty exFAT root directory and allocation bitmap, and the 80 new backyard frames had been written onto the card, landing on clusters that previously held trip data. Chapter 6's metadata recovery pulled back the frames whose residual directory entries survived; this case is what carving added on top of that, and what it could not.

Recovery vs. Forensics. This is a pure recovery job: the only question is whether usable bytes come back, and the EXIF date inside each frame is a convenience for sorting, not evidence anyone will defend in court. Had these same frames mattered to a case — a contested location, a disputed date — the EXIF would have to be hashed, offset-logged, and corroborated, because a carved file has no file-system timestamp to cross-check. Same carve; here it restores an archive, there it would be a fact to prove.

The recovery

Step one, before anything else, was to image the card through a write-blocking reader — a memory card gets the same treatment as a hard drive:

sudo dcfldd if=/dev/sdc of=cf256.dd bs=4M hash=sha256 \
     hashlog=cf256.sha256 conv=noerror,sync status=on
sha256sum cf256.dd
#   c41e9b27a0f8d3...  cf256.dd   (recorded; the card goes back in its case)

Every later pass ran against cf256.dd. Because the metadata-driven recovery of Chapter 6 had already harvested the frames the residual directory still described, the carving stage targeted only what the directory could no longer reach — so the next move was to isolate the unallocated space and carve that, not the whole card:

blkls cf256.dd > cf256_unalloc.dd        # only the unallocated blocks
photorec /log /d /cases/birds/recup_dir /cmd cf256_unalloc.dd \
         partition_none,fileopt,cr2,enable,jpg,enable,mov,enable,everything,disable,search

Two format facts shaped the entire result. First, CR2 is a TIFF-based format with no reliable footer, so PhotoRec carved it by structure-and-maximum-size, walking the file's internal offsets rather than searching for an end marker. Second — and this turned out to be the day's good luck — a CR2 embeds a full-resolution JPEG preview inside itself. That nesting meant that even where a large RAW had fragmented and would not carve whole, its embedded preview JPEG often carved out cleanly as a perfectly usable image.

PhotoRec finished with a flood that is shocking only until you remember what lives in unallocated space:

recup_dir summary (unallocated space, photo formats only)
   jpg : 18,902   recovered   (includes CR2-embedded previews + thumbnails)
   cr2 :  3,107   recovered
   mov :     11   recovered
   ----
   ~19,000 carved files from ~80 GB of unallocated space

Nineteen thousand carves from one card. The number includes every camera-generated thumbnail, every embedded preview separated from its parent, the 80 backyard frames, duplicates, and — buried among them — the few thousand frames that mattered. The client did not want 19,000 anonymous f-number.jpg files; he wanted his birds, in order. So the work moved from carving to triage, using the only context a carved file carries: its own internal metadata.

# Triage carved images by internal EXIF (date + camera model) and resolution.
# Goal: drop thumbnails/previews-of-previews, keep full frames, sort by capture time.
import glob, os
from PIL import Image
from PIL.ExifTags import TAGS

def info(path):
    try:
        img = Image.open(path)
        meta = {TAGS.get(t): v for t, v in (img._getexif() or {}).items()}
        return (img.size[0] * img.size[1],            # pixel area
                meta.get("DateTimeOriginal"),         # "2026:05:18 06:11:42"
                meta.get("Model"),                    # the body that shot it
                path)
    except Exception:
        return None                                   # not a valid image -> false positive

frames = [r for r in (info(f)
          for f in glob.glob("/cases/birds/recup_dir/recup_dir.*/*.jpg"))
          if r and r[0] >= 6_000_000                  # keep >= ~6 MP (drop thumbs/previews)
          and r[2] == "Canon EOS R5"]                 # keep only this photographer's body
frames.sort(key=lambda r: r[1] or "0000")            # order by capture time
print(f"{len(frames)} full-resolution frames from the trip camera, in order")

Filtering to the photographer's own camera body and at least six megapixels collapsed 19,000 carves to a few thousand real frames; sorting by DateTimeOriginal reassembled the trip day by day, the migration morning's burst falling neatly together. The EXIF survived because it is internal to the file — the directory that once held a "date modified" was overwritten by the format, but the camera's own record of the shutter press rode along inside each frame and carved out intact.

The final tally split along the lines the formats dictated, not the lines anyone hoped for:

   ~3,300 / 3,400  frames recovered usable (RAW or embedded full-res preview)
        ~70         frames lost: overwritten by the 80 backyard shots,
                    or RAWs fragmented with their previews also damaged
         9 / 11     carved MOV clips played; 2 long clips would not (moov tail)

Limitation. The ~70 lost frames are theme #5 made concrete. The 80 backyard photos physically overwrote some trip data — deleted is not destroyed, but overwritten is — and a handful of large RAWs had fragmented across the card with no surviving map to reorder them. No tool and no fee brings those back. The honest delivery said so plainly: about 3,300 frames recovered and sorted by date, roughly 70 gone, the cause of each loss named.

The analysis

  1. Image first, even a memory card, even when it is "just" recovery. Carving is iterative — you re-run with different formats enabled and different size caps — and every pass here ran against cf256.dd. The irreplaceable card was read once and shelved. The discipline that protects a courtroom exhibit is the same one that protects a migration you cannot reshoot.

  2. Carve the unallocated space, not the whole card. blkls isolated the deleted content so the carve was not buried under re-recoveries of the live backyard frames. It finished faster and the output was about what was lost, not what was already safe.

  3. The format decides the promise. CR2's TIFF lineage (no footer → max-size carving) and its embedded full-res JPEG preview are why fragmented RAWs still often yielded a usable image. Knowing the format's internals turned "the RAW is corrupt" into "but its preview carved clean" for dozens of frames.

  4. Triage is the real recovery work. Carving produced 19,000 files in minutes; turning them back into an archive took the EXIF-and-resolution filter and a sort by capture time. A carved file loses the name and folder the photographer chose but keeps the context it holds about itself — and that internal context is what makes a flat pile usable again.

  5. A named partial beats a vague "most of it." The client got ~3,300 frames sorted by date, an explicit count of the ~70 losses, and a hashed delivery manifest he could trust. Honesty about the overwrite and the fragmented clips was not bad news to soften; it was the professional result.

Tool Tip. The single biggest time-saver here was PhotoRec's "File Opt" menu: enabling only cr2, jpg, and mov and disabling everything else. Left wide open, the run would have added tens of thousands of carved .html, .txt, and cache fragments to wade through. Carve for the formats you actually need.

Discussion questions

  1. The camera offered both a "Quick format" and a "Low-level format." How would this recovery have differed if the photographer had chosen the low-level option, and why?
  2. CR2 embeds a full-resolution JPEG preview. Explain how that nesting helped recovery here — and how the same nesting (a JPEG inside a file) hurts a naive carver in the chapter's "nested footer" bug.
  3. ⭐ The triage script keeps only frames shot by the Canon EOS R5 and at least 6 MP. Explain precisely what each filter removes from the 19,000 carves, what kind of real frame each filter risks discarding by mistake, and how you would adjust the thresholds to be safe rather than tidy.
  4. Roughly 70 frames were unrecoverable. Draft the two or three sentences you would say to the photographer to communicate the loss honestly, without false hope and without needless alarm.
  5. If these images had instead been shot to a phone's internal storage with TRIM enabled, rather than to a CF card, how might the outcome have changed, and which chapter would you consult to set expectations before quoting?