Metadata-Version: 2.4
Name: discface
Version: 0.1.0
Summary: From a photo of a CD or DVD to the flat, upright disc image
Author: discface contributors
License: MIT
Project-URL: Homepage, https://github.com/GodOfLores/discface
Project-URL: Source, https://github.com/GodOfLores/discface
Project-URL: Issues, https://github.com/GodOfLores/discface/issues
Keywords: CD,DVD,disc,rectification,perspective,orientation,doritex
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Scientific/Engineering :: Image Processing
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: doritex>=0.1.0
Requires-Dist: numpy>=1.23
Requires-Dist: Pillow>=9.0
Provides-Extra: test
Requires-Dist: pytest; extra == "test"

# discface

Transforms photos of CDs and DVDs into flat, top-down disc images. It removes perspective distortion, cuts out the disc with a transparent outer rim and centre hole, and rotates it so printed text reads upright. The disc boundary is detected geometrically, while text is read with [doritex](https://pypi.org/project/doritex/).

```bash
pip install discface
```

## Example

```python
import discface

disc = discface.extract("blank_dvd.jpg")       # photo of a blank DVD+R on a light table
print(disc.image.size, disc.image.mode)
print(f"turned by {disc.rotation:.1f}°, faint print enhanced: {disc.enhanced}")
disc.save("blank_dvd_disc.png")
```

Output:

```
(2048, 2048) RGBA
turned by -17.7°, faint print enhanced: True
```

<table>
  <tr>
    <td align="center"><img src="https://raw.githubusercontent.com/GodOfLores/discface/main/blank_dvd.jpg" alt="photo of a blank DVD lying on a table" width="420"><br><code>blank_dvd.jpg</code></td>
    <td align="center"><img src="https://raw.githubusercontent.com/GodOfLores/discface/main/blank_dvd.png" alt="the same disc, flat, cut out and upright" width="300"><br><code>blank_dvd_disc.png</code></td>
  </tr>
</table>

Perspective distortion is removed, leaving the disc neatly cut out with a transparent outer rim and centre hole. The image was rotated by −17.7° so that "Verbatim" reads horizontally. Because grey print on silver is often faint, it was also detected via edge-enhanced views (`enhanced: True`).

## Taking the photo

discface is designed for discs placed on a **plain, uniform background** and photographed from roughly above. A sheet of white paper or a light grey tabletop works best, which is how all reference test photos were taken.

- Keep the entire disc in frame with some surrounding margin, ensuring the centre hole is visible.
- Shoot from roughly above. Moderate tilt is corrected automatically, and perspective is rectified precisely.
- Ensure even lighting. Moderate reflections are tolerated, and faint print benefits from enabling `enhance`.
- Capture a single disc per photo, keeping other circular objects out of frame.

In a benchmark where backgrounds around real discs were replaced while keeping the discs intact, rim detection rates were as follows:

| background | rim found |
|---|---|
| light, uniform (all real photos) | 7/7 |
| dark, uniform | 6/7 |
| mid grey, uniform | 6/7 |
| busy (photos, printed paper, noise) | 5/35 |

On dark backgrounds, centre hole detection is less reliable (4/7). Patterned or cluttered backgrounds are not currently supported.

## Command line

```bash
discface photos/ -o discs/
```

```
photos/IMG_4097.JPG -> discs/IMG_4097.png  turned 39.2°  59 words
photos/IMG_4100.JPG -> discs/IMG_4100.png  turned -1.7°  80 words
blank_dvd.jpg -> discs/blank_dvd.png  turned -17.7°  71 words  (faint print, edge-enhanced)
```

Inputs can be individual photos, directories, or a mix of both. Each disc is saved as `<photo name><suffix>.<format>` in the output folder.

| option | meaning |
|---|---|
| `-o DIR` | output folder (default: current folder) |
| `--size N` | output image dimension in px (default 2048) |
| `--margin N` | margin between the rim and the image border in px (default 32) |
| `--no-autorotate` | rectify perspective only, without rotating text upright |
| `--enhance auto\|always\|off` | edge enhancement for faint print, detailed below (default `auto`) |
| `--min-support X` | minimum text agreement score required to rotate a disc (default 40) |
| `--background COLOR` | fill transparent areas with a solid colour, e.g. `white` or `'#202020'` |
| `--format png\|jpg\|webp` | output format; `jpg` defaults to a white background unless `--background` is specified |
| `--suffix TEXT` | string appended to output filenames |
| `--device auto\|cpu` | device used for text recognition (OpenCL GPU or CPU) |
| `--draw` | also save `<name>_words.png` visualizing detected words |
| `--json` | print one JSON line per photo containing detection metadata |
| `-q` | quiet mode, suppressing all output except errors |

Photos without a detectable disc are reported and skipped, returning exit code 1. You can also run the tool via `python -m discface`.

## API

**`discface.extract(image, size=2048, margin=32, autorotate=True, enhance="auto", min_support=40, device="auto", detector=None) -> Disc`**
Runs the three steps below in sequence. `image` can be a file path (with automatic EXIF orientation handling), a `PIL.Image`, or an RGB NumPy array. `device` selects the text recognition backend: `"auto"` uses an OpenCL GPU if present and falls back to CPU, while `"cpu"` forces CPU execution. Raises `discface.DiscNotFound` if no disc is detected.

The individual steps can also be called directly:

```python
geometry = discface.locate("photo.jpg")                       # DiscGeometry: rim, hole, center in the photo
flat = discface.rectify("photo.jpg", geometry, size=2048)      # flat disc, RGBA, not turned
o = discface.orient(flat, enhance="always")                   # Orientation: rotation, support, turned, enhanced, words
upright = flat.rotate(o.rotation)
```

| | |
|---|---|
| `Disc.image` | Rectified disc as a `PIL.Image` (RGBA, `size × size`) with transparency outside the rim and inside the centre hole |
| `Disc.rotation` | Counter-clockwise rotation in degrees applied to align text upright (`0` if unrotated) |
| `Disc.orientation_support` | Cumulative score of agreeing text; below `min_support`, the disc is left unrotated |
| `Disc.enhanced` | `True` if faint text was detected using edge-enhanced views |
| `Disc.words` | Words detected on the disc (`doritex.Word`: `quad`, `angle360`, `score`, …) in `image` coordinates |
| `Disc.rim`, `.hole`, `.center` | Disc geometry in the input photo: fitted ellipses `((cx, cy), (width, height), angle)` and true centre coordinates |
| `Disc.save(path, background=None)` | Saves the image to disk. PNG and WebP preserve transparency, while JPEG defaults to a white background |
| `Disc.to_image(background=None)` | Returns the `PIL.Image`, optionally composited over a solid background colour |
| `discface.load_detector(device="auto")` | Returns a cached doritex detector instance; pass via `detector=` to reuse across calls |

## How upright orientation is decided

Every word detected on the disc votes for its reading direction. Large words carry more weight than fine print, and high-confidence detections outweigh uncertain ones. The dominant direction wins, so a few words printed sideways do not skew the result. Small text running circular along the rim is excluded, as it points in every direction. If too little text agrees (`min_support`), the disc retains its original orientation from the photo.

Faint print, such as grey text on a silver disc or lettering obscured by rainbow reflections, can also be read from two edge-enhanced views. These views emphasize character strokes, boost local contrast, and suppress smooth shading and reflections. The `enhance` option controls this behavior:

- `auto` (default): applies edge enhancement only when standard detection yields insufficient confidence. Discs that read well are processed without extra overhead.
- `always`: processes edge-enhanced views for every disc, adding roughly 1 to 2 seconds per image.
- `off`: never applies edge enhancement.

## Performance

Benchmark times for a 24-megapixel photo after the initial warmup run (which compiles GPU kernels on first execution):

| step | GPU (`device="auto"`) | CPU (`device="cpu"`) |
|---|---|---|
| read the photo | 0.2 s | 0.2 s |
| `locate` | 1.1 s | 1.1 s |
| `rectify` | 0.8 s | 0.8 s |
| `orient` | 0.3 s | 1.9 s |
| `orient`, `enhance="always"` | 3.1 s | 8.6 s |
| **total per photo** | **≈ 2.4 s** | **≈ 4.1 s** |

Both devices produce identical results. Only text recognition runs on the GPU; locating and rectifying the disc always run on the CPU.

## License

MIT
