Source code for pyfli.sp_analysis.simulator.pattern_gen
# sp_analysis/simulator/pattern_gen.py
"""
Generate Hadamard and DCT sensing patterns for simulated single-pixel acquisition.
This module belongs to :mod:`pyfli.sp_analysis.simulator` and is part of PyFLI single-
pixel camera basis generation, acquisition simulation, and reconstruction solvers.
Public API includes classes :class:`BasisPatterns`.
"""
from typing import Any
import numpy as np
from scipy.fftpack import dct
from scipy.linalg import hadamard
[docs]
class BasisPatterns:
"""
Generate sensing patterns for single-pixel acquisition experiments. It supports
Hadamard ordering, differential pattern pairs, DCT patterns, and reshaping vector
patterns to image grids.
Parameters
----------
resolution : tuple[int, ...]
Spatial resolution of generated patterns or reconstructed images.
sampling_ratio : float
Fraction of basis patterns retained for acquisition simulation.
"""
def __init__(
self, resolution: tuple[int, ...] = (128, 128), sampling_ratio: float = 1.0
) -> None:
self.sampling_ratio = sampling_ratio
self.res_h, self.res_w = resolution
self.n_total = self.res_h * self.res_w
self.n_patterns = int(self.n_total * self.sampling_ratio)
def _get_hadamard_matrix(self) -> Any:
"""Generates a standard Walsh-Hadamard matrix (-1, 1)."""
return hadamard(self.n_total)
def _get_zigzag_indices(self) -> Any:
"""Computes indices for 2D Zigzag ordering."""
indices = np.indices((self.res_h, self.res_w))
sum_indices = indices[0] + indices[1]
return np.argsort(sum_indices.flatten(order="C"))
[docs]
def generate_hadamard(
self, order_strategy: str = "zigzag", differential: bool = True
) -> Any:
"""
Generates Hadamard patterns.
Binary mosaic-like patterns with only horizontal and vertical features.
"""
H = self._get_hadamard_matrix()
if order_strategy == "zigzag":
sort_idx = self._get_zigzag_indices()
else:
sort_idx = np.arange(self.n_total)
H_ordered = H[sort_idx[: self.n_patterns], :]
if differential:
# Split into positive and negative components for DMD (0/1)
P_pos = (H_ordered + 1) / 2
P_neg = (1 - H_ordered) / 2
return np.vstack((P_pos, P_neg))
return (H_ordered + 1) / 2
[docs]
def generate_fourier_dct(self, order_strategy: str = "zigzag") -> Any:
"""
Generates Grayscale DCT basis patterns (periodic fringes).
Optimized version: uses identity transformation to extract 2D basis.
Captures H, V, and Oblique features.
"""
# Create an identity matrix representing each pixel location
identity = np.eye(self.n_total)
# Applying 2D DCT to each identity vector retrieves the basis functions
# This is significantly faster than nested loops
basis = dct(
dct(identity.reshape(-1, self.res_h, self.res_w), axis=1, norm="ortho"),
axis=2,
norm="ortho",
).reshape(self.n_total, self.n_total)
if order_strategy == "zigzag":
sort_idx = self._get_zigzag_indices()
basis = basis[sort_idx]
# Normalize to [0, 1] range for DMD hardware constraints
# We add epsilon to prevent division by zero for the DC component
b_min = basis.min(axis=1, keepdims=True)
b_max = basis.max(axis=1, keepdims=True)
basis_dmd = (basis - b_min) / (b_max - b_min + 1e-10)
return basis_dmd[: self.n_patterns, :]
[docs]
@staticmethod
def reshape_pattern(pattern_vector: np.ndarray, res: Any) -> Any:
"""Utility to convert flattened 1D vector to 2D image."""
return pattern_vector.reshape(res)