# Camera Calibration and Capture Bounds in C# (AR51.Net)

Run a **camera calibration** from your app, follow its progress, read the result — and read or set the **tracked area** (capture bounds).

:::info[The calibration itself]
Starting a calibration from code doesn't change how it's done: someone still walks the lit calibration sphere through the capture area. See [Camera calibration](/docs/calibration/camera-calibration/) for good technique.
:::

## Run a calibration

:::caution[Calibration affects every app using the system]
Tracking keeps running on the current calibration while a calibration runs. When it **succeeds**, it **replaces every camera's calibration** for every app connected to the AR 51 system; if it fails or is cancelled, the current calibration stays.
:::

A calibration **ends by itself** once it has collected enough data. Subscribe to the events, then start it:

```csharp
ar51.Calibration.CalibrationProgressChanged += (_, e) =>
    Console.WriteLine($"{e.Status.Progress:P0}, error {e.Status.ReprojectionErrorPixels:F2} px {e.Status.Message}");
ar51.Calibration.CalibrationEnded += (_, e) =>
    Console.WriteLine($"calibration {e.Result.Outcome}, error {e.Result.ReprojectionErrorPixels:F2} px");

await ar51.Calibration.StartCalibrationAsync();
```

- **Progress** runs from 0 to 1. **Reprojection error** is in pixels — lower is better.
- To finish now, call `StopCalibrationAsync`. It stops the running calibration — **whoever started it**, including one an operator started in Mocap Studio — and returns the result. Stopped before it has enough data, it ends as `Cancelled` and the cameras keep their previous values.
- Starting one while another is running throws `AR51Exception` with `Rejected`.

:::caution[Your app keeps the calibration alive]
If your client is stopped or disposed — for example the app shuts down — before the calibration ends, the calibration is **cancelled**.
:::

## The result

<div className="apiTable">

| Outcome | Meaning |
|---|---|
| `Succeeded` | The calibration succeeded; `GetCamerasAsync` returns the new values |
| `Failed` | The calibration failed; the cameras keep their previous values |
| `Cancelled` | Stopped before it had enough data; the cameras keep their previous values |
| `Unknown` | The calibration was started by another app, or none was running |

</div>

The events report **every** calibration, whoever started it (your app, Mocap Studio or another client). The outcome is known only for a calibration your client started.

## Capture bounds

The tracked area — the [capture-area bounding box](/docs/calibration/bounding-box/) — is a box in the AR 51 world (meters): **people outside it are not tracked**.

```csharp
CaptureBounds bounds = await ar51.Calibration.GetCaptureBoundsAsync();
Console.WriteLine($"center {bounds.Center} m, size {bounds.Size} m");
```

:::warning[A wrong area makes people disappear]
`SetCaptureBoundsAsync` applies immediately, and anyone outside the new box stops being tracked. Double-check the values first.
:::

```csharp
await ar51.Calibration.SetCaptureBoundsAsync(bounds with { Size = new Vector3(6, 2.5f, 6) });
```

`CaptureBounds` also gives you `Min`, `Max` and `Contains(point)`.

## Complete sample

Run a calibration and follow its progress. It ends by itself once it has enough data; **Ctrl+C** stops it now (before it has enough data, that ends as `Cancelled`).

{/* SAMPLE:08-calibration — generated from the ar51-net-samples repo; edit there */}
```csharp title="Program.cs"
// Run a camera calibration and follow its progress. It ends by itself once it has enough data; Ctrl+C finishes it now.
using AR51.Net;

await using IAR51Client ar51 = AR51Client.Create();
var ended = new TaskCompletionSource<CalibrationResult>(TaskCreationOptions.RunContinuationsAsynchronously);

ar51.Calibration.CalibrationStarted += (_, e) => Console.WriteLine("Calibration started. Move the lit calibration sphere through the whole area.");
ar51.Calibration.CalibrationProgressChanged += (_, e) =>
    Console.WriteLine($"{e.Status.Progress:P0}, error {e.Status.ReprojectionErrorPixels:F2} px {e.Status.Message}");
ar51.Calibration.CalibrationEnded += (_, e) => ended.TrySetResult(e.Result);

await ar51.StartAsync();
await ar51.Calibration.StartCalibrationAsync();

Console.CancelKeyPress += async (_, e) =>
{
    e.Cancel = true;
    CalibrationResult result = await ar51.Calibration.StopCalibrationAsync();   // "finish now"
    ended.TrySetResult(result);
};

CalibrationResult final = await ended.Task;
Console.WriteLine($"Calibration ended: {final.Outcome}, error {final.ReprojectionErrorPixels:F2} px");
if (final.Outcome == CalibrationOutcome.Succeeded)
    Console.WriteLine("The cameras' new calibration values are in ar51.Cameras.GetCamerasAsync().");
```

[View this sample on Bitbucket](https://bitbucket.org/ar51/ar51-net-samples/src/main/08-calibration/) — clone the [samples repository](https://bitbucket.org/ar51/ar51-net-samples) to run it.
{/* /SAMPLE */}

## Reference

[`ICalibrationClient`](/docs/sdk-api/csharp-api/classes/icalibrationclient/) · [`CalibrationStatus`](/docs/sdk-api/csharp-api/classes/icalibrationclient/#calibrationstatus) · [`CalibrationResult`](/docs/sdk-api/csharp-api/classes/icalibrationclient/#calibrationresult) · [`CaptureBounds`](/docs/sdk-api/csharp-api/classes/capturebounds/)
