Metadata-Version: 2.4
Name: whircam
Version: 0.1.0
Summary: Windows Hello IR camera module — cv2.VideoCapture-like access to hidden IR cameras
Project-URL: Homepage, https://github.com/jacksonfcs/Windows-Hello-IR-Camera-Python
Project-URL: Repository, https://github.com/jacksonfcs/Windows-Hello-IR-Camera-Python.git
Author-email: jacksonfcs <jackson@jacksonfcs.com>
License: MIT
License-File: LICENSE
Keywords: MediaFrameSourceGroup,camera,depth-camera,infrared,ir-camera,laptop-camera,pywinrt,windows-camera,windows-hello,winrt
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Win32 (MS Windows)
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: Microsoft :: Windows :: Windows 10
Classifier: Operating System :: Microsoft :: Windows :: Windows 11
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Video :: Capture
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Requires-Python: >=3.10
Requires-Dist: winrt-runtime>=3.2
Requires-Dist: winrt-windows-devices-enumeration>=3.2
Requires-Dist: winrt-windows-devices>=3.2
Requires-Dist: winrt-windows-foundation-collections>=3.2
Requires-Dist: winrt-windows-foundation>=3.2
Requires-Dist: winrt-windows-graphics-directx-direct3d11>=3.2
Requires-Dist: winrt-windows-graphics-directx>=3.2
Requires-Dist: winrt-windows-graphics-imaging>=3.2
Requires-Dist: winrt-windows-graphics>=3.2
Requires-Dist: winrt-windows-media-capture-frames>=3.2
Requires-Dist: winrt-windows-media-capture>=3.2
Requires-Dist: winrt-windows-media-mediaproperties>=3.2
Requires-Dist: winrt-windows-media>=3.2
Requires-Dist: winrt-windows-storage-streams>=3.2
Provides-Extra: dev
Requires-Dist: pytest>=9.1.1; extra == 'dev'
Provides-Extra: full
Requires-Dist: numpy>=1.24; extra == 'full'
Requires-Dist: opencv-python>=4.6; extra == 'full'
Description-Content-Type: text/markdown

# WHIRCAM — Windows Hello IR Camera for Python

[![PyPI version](https://img.shields.io/pypi/v/whircam)](https://pypi.org/project/whircam/)
[![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)
[![License](https://img.shields.io/badge/license-MIT-green)](https://opensource.org/licenses/MIT)
[![Windows](https://img.shields.io/badge/platform-Windows-blue)](https://github.com/jacksonfcs/Windows-Hello-IR-Camera-Python)

Access Windows Hello IR cameras from Python like `cv2.VideoCapture`. These
cameras are hidden from legacy APIs (DirectShow, OpenCV, Media Foundation) by
the driver's `SkipCameraEnumeration` and `SensorCameraMode` flags. This module
talks to them through the `MediaFrameSourceGroup` API instead.

**This is a raw camera access library, not an authentication tool.** It
reads pixel data and depth maps from the IR camera sensor. It does **not**
perform face recognition, Windows Hello sign-in, or any biometric processing.

Try it in one shot (requires an IR camera and `opencv-python`):
```powershell
uvx --from whircam[full] whircam --imshow
```

Or with pip:
```powershell
pip install whircam[full] && whircam --imshow
```

See your IR camera live in Python:
```python
import whircam
with whircam.IRCam() as cap:
    while cap.imshow() != 27:   # ESC to exit
        pass
whircam.IRCam.destroy_all_windows()
```

Full API reference:
```python
groups = whircam.find_groups()
cap    = whircam.IRCam(format_output="gray")  # or "bgr" (default)

ret, frame = cap.read()           # (bool, 640×360 BGR or grayscale ndarray)
ret, depth = cap.read_depth()     # (bool, 640×360 uint16 depth in mm)
ret, frame, depth = cap.read_both()

cap.release()
# or  with whircam.IRCam() as cap: ...
```

## Requirements

- **Windows 10 1809+** (build 17763) — the `MediaFrameSourceGroup` API
  is not available on earlier versions.
- A Windows Hello IR camera (most laptops with Windows Hello have one).
- **[winrt-*](https://github.com/pywinrt/pywinrt)** — Per-namespace Python bindings for
  the WinRT APIs this module uses (14 namespace packages — Foundation,
  Foundation.Collections, Media, MediaProperties, Media.Capture,
  Media.Capture.Frames, Devices, Devices.Enumeration, Storage.Streams,
  Graphics, Imaging, DirectX, Direct3D11, plus the runtime).
  Required for all functionality.  Automatically installed via ``pip`` or
  ``uv`` (see below).
- **Optional:** ``numpy`` + ``opencv-python`` — needed only at runtime
  (frame decoding, pixel conversion, and ``imshow()``).  Install via
  ``whircam[full]``.

## Install

```powershell
pip install whircam                # core only (find_groups, GroupInfo)
pip install whircam[full]          # + numpy + opencv-python (read, imshow)
```

With uv:

```powershell
uv add whircam                     # core only
uv add whircam[full]               # + numpy + opencv-python
```

Try it once without installing:

```powershell
uvx --from whircam[full] whircam --imshow
```

The core ``winrt-*`` dependencies are pulled in automatically. If you copy
just ``whircam.py`` into your project manually, install the 14 namespace
packages separately:

```powershell
uv add winrt-runtime winrt-Windows.Foundation winrt-Windows.Foundation.Collections winrt-Windows.Media winrt-Windows.Media.MediaProperties winrt-Windows.Media.Capture winrt-Windows.Media.Capture.Frames winrt-Windows.Devices winrt-Windows.Devices.Enumeration winrt-Windows.Storage.Streams winrt-Windows.Graphics winrt-Windows.Graphics.Imaging winrt-Windows.Graphics.DirectX winrt-Windows.Graphics.DirectX.Direct3D11
```

NumPy and OpenCV (the ``[full]`` extra) are needed only at runtime —
``find_groups()`` works with the winrt-* packages alone.

## Contributing

Issues and pull requests are welcome. I maintain this project in my free
time and review contributions as I can — there are no guarantees on
response or release cadence.

Dev setup (uv):
```
uv sync --group dev
uv run pytest -k "not test_does_not_crash and not test_entry_point_script_help and not test_help_exits_ok"
```

Dev setup (pip):
```powershell
pip install -e ".[dev]"
pytest -k "not test_does_not_crash and not test_entry_point_script_help and not test_help_exits_ok"
```

Both commands above install the winrt-* packages, numpy, and opencv-python
automatically via the project's ``pyproject.toml`` dependencies.

The `-k` filter excludes the hardware-dependent and CLI-entry-point
tests that require a camera or an installed script. Run without the filter
if you have an IR camera and `whircam` installed.

## API

### Discovery

```python
whircam.find_groups()
```

Returns a list of camera groups that contain an IR source:

```python
[
  {"index": 0, "name": "Rts-DMFT-Group",
   "sources": [{"kind": "Color", ...}, {"kind": "Infrared", ...}]},
  {"index": 1, "name": "USB2.0 IR UVC WebCam",
   "sources": [{"kind": "Infrared", ...}]},
]
```

`import whircam` and `whircam.find_groups()` only require the `winrt-*` packages. NumPy and OpenCV
([optional](#install)) are only needed when you open a camera via ``IRCam()``,
and are imported lazily at that point.

### Opening a camera

```python
cap = whircam.IRCam()                                 # auto-selects first IR camera
cap = whircam.IRCam(group=0)                          # by find_groups() index
cap = whircam.IRCam(group="USB2.0 IR UVC WebCam")    # by display name
cap = whircam.IRCam(group=groups[0])                  # by find_groups() dict
cap = whircam.IRCam(format_index=1)                    # select format by index
cap = whircam.IRCam(format_output="gray")             # skip BGR conversion (returns 2-D frames)
```

The camera opens in `SharedReadOnly` mode — it will not interfere with Windows
Hello face authentication.

### Reading frames

| Method | Returns | Behavior |
|---|---|---|
| `cap.read()` | `(bool, ndarray)` | Blocks until a new IR frame is received. Shape: `(h, w, 3)` for BGR, `(h, w)` for grayscale |
| `cap.read_depth()` | `(bool, uint16_ndarray)` | Blocks until a new depth frame is received |
| `cap.read_both()` | `(bool, ndarray, uint16)` | Blocks until a new frame pair is received |
| `cap.imshow(mode=)` | `int` | Read next frame and show in window. `mode=` controls frame filtering: `"illuminated"` (default, IR LED on), `"ambient"`, or `"both"`. Returns `cv2.waitKey` key code (``-1`` if no frame) |
| `IRCam.destroy_all_windows()` | — | Close all OpenCV windows created by `imshow()` — no `import cv2` needed |

The read methods block until a new frame is available.  The internal pump reads
the most recent frame from the camera each cycle — if frames arrive faster
than the consumer processes them, some are silently dropped (analogous to
``cv2.VideoCapture.read`` with a ring-buffer backend).  Depth is in
millimeters (1–65535).  Returns ``(False, None)`` on timeout or error.

### Properties

```python
cap.formats        # [{"index":0, "width":640, "height":360, "fps":30.0, "subtype":"L8"}, ...]
cap.info           # {"group_name":..., "format_index":..., "depth_available":..., "format_applied":...}
cap.fps            # current frame rate
cap.width          # frame width in pixels
cap.height         # frame height in pixels
cap.shape          # (height, width) — OpenCV convention
cap.is_opened      # True while pump is running
```

### Context manager

```python
with whircam.IRCam() as cap:
    ret, frame = cap.read()
```

## CLI

```powershell
# Live IR camera preview (ESC to exit)
whircam --imshow
whircam --imshow 0                  # select by index
whircam --imshow "USB Camera"       # select by name
whircam --imshow --mode both        # show every frame (unfiltered)
whircam --imshow --mode ambient     # show only ambient frames

# List IR cameras
whircam --list

# Check version
whircam --version
```

## Limitations

- **This is a near-IR depth camera, not thermal IR.** Windows Hello cameras
  use 850 nm near-infrared structured light or time-of-flight for face
  authentication. They do **not** detect heat/thermal radiation — the output
  is a grayscale intensity image of the scene under IR illumination (and
  optionally a depth map in mm).

- **~15 fps effective rate**: The Windows Hello camera driver alternates
  illuminated and ambient frames. `try_acquire_latest_frame()` takes ~15–16ms
  per call regardless. You get ~7.5 illuminated + 7.5 ambient frames per
  second regardless of the advertised format.
- **Format switches may fail**: Some camera drivers reject
  `set_format_async()` while the pipeline is held by the Windows
  FrameServer. Format selection is best-effort and falls back to the default.
- **Windows-only**: Relies on APIs unavailable on other platforms.
- **Depends on WinRT APIs that Microsoft may change.** This library reaches
  into `Windows.Media.Capture.Frames`, `Windows.Graphics.Imaging`, and
  related WinRT namespaces — undocumented internal behaviours that Microsoft
  has changed between Windows versions without notice (e.g., frame delivery
  timing in 22H2, `SkipCameraEnumeration` semantics in 24H2). A future
  Windows Update can break this library without warning. No guarantees,
  no SLAs.
- **ARM64 (Surface etc.) — opencv-python has no official ARM64 wheel.**
  The `winrt-*` packages and `numpy` both ship native `win_arm64` wheels, but
  `opencv-python` does not yet ([PR #1143](https://github.com/opencv/opencv-python/pull/1143)).
  If you need native ARM64 Python, use an unofficial wheel from
  [cgohlke/win_arm64-wheels](https://github.com/cgohlke/win_arm64-wheels)
  or run x64 Python under emulation (all major Surface devices support this).
- **Python free-threading (--disable-gil, 3.13+) — untested.** This library
  uses `threading`, `asyncio`, and WinRT COM interop via the winrt-* packages. The
  free-threaded build has not been tested and may not work. The primary
  obstacle is likely the PyWinRT C++/WinRT object model rather than Python
  threading primitives.

## Design Notes

These are intentional trade-offs worth documenting, not bugs.

**Single-file module, not a package.** `whircam.py` is one file you can copy into
any project with no build step. Splitting into `_types.py`, `_frame_store.py`,
etc. would require a directory install and break the zero-dependency drop-in
use case.

**CI runs camera-free tests on every push.** A `.github/workflows/ci.yml`
workflow installs the package and runs `pytest -k "not test_does_not_crash"`
on Windows Server 2022 for Python 3.10–3.13. Hardware-dependent tests
(those that instantiate `IRCam()`) are excluded.

**`test/` has no `__init__.py`.** The `pyproject.toml` sets
`--import-mode=importlib` explicitly. Running `pytest` from the project root
(with the config present) works. Running `pytest test/` from a sibling directory
without the config would fail — but so would any other config-dependent invocation.

**`_PROJECT_URL`.** The `_PROJECT_URL` constant at the top of `whircam.py`
points to the GitHub repo (used in the untested-build warning). Install
instructions now use `pip install whircam` — the git URL is no longer the
primary install path.

**No WinRT mocking in tests.** The `MediaFrameSourceGroup`, `MediaCapture`,
and related types are native C++/WinRT objects with no pure-Python interface
to implement. `fresh_whircam()` (evict + re-import) is the pragmatic substitute
for test isolation.

**`release()` and WinRT call timeouts.** WinRT APIs like
`try_acquire_latest_frame()` have no timeout mechanism — a driver hang can
block the pump thread indefinitely. The daemon thread and force-close via
`loop.stop()` are best-effort. At process exit, WinRT cleans up native
resources automatically.

**Global winrt-* state vs test isolation.** Module-level singletons
(`_MediaFrameSourceGroup`, etc.) are lazy-loaded once. Test isolation uses
`fresh_whircam()` to evict the module from `sys.modules` and re-import.
A class-namespace approach would be cleaner but adds complexity without
practical benefit at this scale.

## Dependencies

| Package | Min version | Required for | Lazy? |
|---|---|---|---|
| `winrt-runtime` + `winrt-Windows.*` (14 namespace packages) | 3.2 | WinRT projection — async ops, MediaCapture, MediaFrameSourceGroup, SoftwareBitmap, Direct3D | no |
| `numpy` | 1.24 | frame buffer conversion | optional — only for `IRCam()` |
| `opencv-python` | 4.6 | pixel format conversion (GRAY→BGR) | optional — only for `IRCam()` |

Version ranges are intentionally broad. The APIs whircam uses (`frombuffer`,
`reshape`, `cvtColor`, `GRAY2BGR`, `RGBA2BGR`) are stable across all listed
minimum versions.

## License

MIT
