# `Monty.Frameworks.Utils.EdgeDetection.StructureTensor`
[🔗](https://github.com/d-led/ex.tbp.monty/blob/v0.1.0/packages/ex_tbp_monty_core/lib/monty/frameworks/utils/edge_detection/structure_tensor.ex#L1)

A structure tensor at one point: the 2×2 symmetric matrix `[[xx, xy], [xy, yy]]`.

Mirrors `tbp/monty/frameworks/utils/edge_detection.py::StructureTensor`.

Its four numbers are what an edge looks like in a patch of gradients: how
strongly the brightness changes along x, the same along y, and how much the
two move together. The eigenvalues of the matrix are the strengths along the
dominant and the perpendicular direction; an edge is a matrix with one large
eigenvalue and one small one, and a corner is a matrix with two.

# `t`

```elixir
@type t() :: %Monty.Frameworks.Utils.EdgeDetection.StructureTensor{
  xx: float(),
  xy: float(),
  yy: float()
}
```

# `coherence`

```elixir
@spec coherence(t()) :: float()
```

How *oriented* the tensor is, in `[0, 1]`: 1 is a single clean edge, 0 is a
patch with no direction at all.

`(lambda_max - lambda_min) / (lambda_max + lambda_min)`. Two eigenvalues a
hair apart are a round patch, not an edge, and Monty calls that zero rather
than letting rounding turn noise into confidence.

# `edge_angle`

```elixir
@spec edge_angle(t()) :: float()
```

The edge direction in `[0, π]`: perpendicular to the gradient.

# `edge_strength`

```elixir
@spec edge_strength(t()) :: float()
```

How strong the strongest edge in the tensor is: the root of the larger
eigenvalue, floored at zero because a numerically tiny negative eigenvalue is
noise, not a negative edge.

# `eigenvalues`

```elixir
@spec eigenvalues(t()) :: {float(), float()}
```

The smallest and the largest eigenvalue, `{lambda_min, lambda_max}`.

Monty asks numpy for both; the closed form for a symmetric 2×2 is the same
answer and says what it is: their sum is the trace and their product the
determinant, so they are `mean ± sqrt(mean² - determinant)` with
`mean = (xx + yy) / 2`.

# `gradient_theta`

```elixir
@spec gradient_theta(t()) :: float()
```

The gradient direction in radians, normal to the dominant edge.

In `(-π/2, π/2]`: an orientation, not a heading, because the tensor cannot
tell one side of an edge from the other.

# `new`

```elixir
@spec new(float(), float(), float()) :: t()
```

The two, from the three numbers.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
