API Reference
==============

This page provides detailed API documentation for all public classes and functions in qlty.

In-Memory Classes
------------------

NCYXQuilt
~~~~~~~~~~

.. autoclass:: qlty.qlty2D.NCYXQuilt
   :members:
   :undoc-members:
   :show-inheritance:

**Example:**

.. code-block:: python

    from qlty import NCYXQuilt

    quilt = NCYXQuilt(
        Y=128, X=128,
        window=(32, 32),
        step=(16, 16),
        border=(5, 5),
        border_weight=0.1
    )

    data = torch.randn(10, 3, 128, 128)
    patches = quilt.unstitch(data)
    reconstructed, weights = quilt.stitch(patches)

NCZYXQuilt
~~~~~~~~~~

.. autoclass:: qlty.qlty3D.NCZYXQuilt
   :members:
   :undoc-members:
   :show-inheritance:

**Example:**

.. code-block:: python

    from qlty import NCZYXQuilt

    quilt = NCZYXQuilt(
        Z=64, Y=64, X=64,
        window=(32, 32, 32),
        step=(16, 16, 16),
        border=(4, 4, 4),
        border_weight=0.1
    )

    volume = torch.randn(5, 1, 64, 64, 64)
    patches = quilt.unstitch(volume)
    reconstructed, weights = quilt.stitch(patches)

Disk-Cached Classes
--------------------

LargeNCYXQuilt
~~~~~~~~~~~~~~

.. autoclass:: qlty.qlty2DLarge.LargeNCYXQuilt
   :members:
   :undoc-members:
   :show-inheritance:

**Example:**

.. code-block:: python

    from qlty import LargeNCYXQuilt
    import tempfile
    import os

    temp_dir = tempfile.mkdtemp()
    filename = os.path.join(temp_dir, "dataset")

    quilt = LargeNCYXQuilt(
        filename=filename,
        N=100,
        Y=512, X=512,
        window=(128, 128),
        step=(64, 64),
        border=(10, 10),
        border_weight=0.1
    )

    data = torch.randn(100, 3, 512, 512)
    for i in range(quilt.N_chunks):
        idx, patch = quilt.unstitch_next(data)
        processed = model(patch.unsqueeze(0))
        quilt.stitch(processed, idx)

    result = quilt.return_mean()

LargeNCZYXQuilt
~~~~~~~~~~~~~~~

.. autoclass:: qlty.qlty3DLarge.LargeNCZYXQuilt
   :members:
   :undoc-members:
   :show-inheritance:

Utility Functions
------------------

weed_sparse_classification_training_pairs_2D
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. autofunction:: qlty.cleanup.weed_sparse_classification_training_pairs_2D

**Example:**

.. code-block:: python

    from qlty import NCYXQuilt, weed_sparse_classification_training_pairs_2D

    quilt = NCYXQuilt(Y=128, X=128, window=(32, 32), step=(16, 16), border=(5, 5))

    input_patches = torch.randn(100, 3, 32, 32)
    label_patches = torch.ones(100, 32, 32) * (-1)  # Missing labels
    label_patches[0:50] = 1.0  # Some valid

    border_tensor = quilt.border_tensor()
    valid_in, valid_out, mask = weed_sparse_classification_training_pairs_2D(
        input_patches, label_patches, missing_label=-1, border_tensor=border_tensor
    )

weed_sparse_classification_training_pairs_3D
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. autofunction:: qlty.cleanup.weed_sparse_classification_training_pairs_3D

Patch Pair Extraction Functions
---------------------------------

extract_patch_pairs
~~~~~~~~~~~~~~~~~~~

.. autofunction:: qlty.patch_pairs_2d.extract_patch_pairs

**Example:**

.. code-block:: python

    from qlty import extract_patch_pairs
    import torch

    tensor = torch.randn(5, 3, 128, 128)  # 5 images, 3 channels, 128x128
    window = (32, 32)  # 32x32 patches
    num_patches = 10  # 10 patch pairs per image
    delta_range = (8.0, 16.0)  # Euclidean distance between 8 and 16 pixels

    patches1, patches2, deltas = extract_patch_pairs(
        tensor, window, num_patches, delta_range, random_seed=42
    )

    # patches1: (50, 3, 32, 32) - patches at original locations
    # patches2: (50, 3, 32, 32) - patches at displaced locations
    # deltas: (50, 2) - displacement vectors (dx, dy)

extract_overlapping_pixels
~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. autofunction:: qlty.patch_pairs_2d.extract_overlapping_pixels

**Example:**

.. code-block:: python

    from qlty import extract_patch_pairs, extract_overlapping_pixels
    import torch

    # Extract patch pairs
    patches1, patches2, deltas, rotations = extract_patch_pairs(
    patches1, patches2, deltas, rotations = extract_patch_pairs(
        tensor, window=(32, 32), num_patches=10, delta_range=(8.0, 16.0)
    )

    # Extract overlapping pixels
    overlapping1, overlapping2 = extract_overlapping_pixels(
        patches1, patches2, deltas
    )

    # overlapping1: (K, 3) - overlapping pixels from patches1
    # overlapping2: (K, 3) - overlapping pixels from patches2
    # K is the total number of overlapping pixels
    # Corresponding pixels are at the same index in both tensors

extract_patch_pairs_3d
~~~~~~~~~~~~~~~~~~~~~~

.. autofunction:: qlty.patch_pairs_3d.extract_patch_pairs_3d

**Example:**

.. code-block:: python

    from qlty import extract_patch_pairs_3d
    import torch

    tensor = torch.randn(5, 1, 64, 64, 64)  # 5 volumes, 1 channel, 64x64x64
    window = (16, 16, 16)  # 16x16x16 patches
    num_patches = 10  # 10 patch pairs per volume
    delta_range = (8.0, 12.0)  # Euclidean distance between 8 and 12 voxels

    patches1, patches2, deltas = extract_patch_pairs_3d(
        tensor, window, num_patches, delta_range, random_seed=42
    )

    # patches1: (50, 1, 16, 16, 16) - patches at original locations
    # patches2: (50, 1, 16, 16, 16) - patches at displaced locations
    # deltas: (50, 3) - displacement vectors (dx, dy, dz)

extract_overlapping_pixels_3d
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. autofunction:: qlty.patch_pairs_3d.extract_overlapping_pixels_3d

**Example:**

.. code-block:: python

    from qlty import extract_patch_pairs_3d, extract_overlapping_pixels_3d
    import torch

    # Extract patch pairs
    patches1, patches2, deltas = extract_patch_pairs_3d(
        tensor, window=(16, 16, 16), num_patches=10, delta_range=(8.0, 12.0)
    )

    # Extract overlapping pixels
    overlapping1, overlapping2 = extract_overlapping_pixels_3d(
        patches1, patches2, deltas
    )

    # overlapping1: (K, 1) - overlapping pixels from patches1
    # overlapping2: (K, 1) - overlapping pixels from patches2
    # K is the total number of overlapping pixels
    # Corresponding pixels are at the same index in both tensors

Pre-Tokenization for Patch Processing (2D)
--------------------------------------------

tokenize_patch
~~~~~~~~~~~~~~

.. autofunction:: qlty.pretokenizer_2d.sequences.tokenize_patch

**Example:**

.. code-block:: python

    from qlty import tokenize_patch
    import torch

    # Tokenize a single patch into overlapping subpatches
    patch = torch.randn(3, 64, 64)  # 3 channels, 64x64 patch
    tokens, coords = tokenize_patch(patch, patch_size=16, stride=8)

    # tokens: (T, 768) - flattened token vectors (T tokens, each 3*16*16=768 dims)
    # coords: (T, 2) - absolute (y, x) coordinates of each token
    print(f"Created {tokens.shape[0]} tokens from patch")

build_sequence_pair
~~~~~~~~~~~~~~~~~~~

.. autofunction:: qlty.pretokenizer_2d.sequences.build_sequence_pair

**Example:**

.. code-block:: python

    from qlty import build_sequence_pair, extract_patch_pairs
    import torch

    # Extract patch pairs using qlty's extract_patch_pairs
    images = torch.randn(10, 3, 128, 128)
    patches1, patches2, deltas, rotations = extract_patch_pairs(
        images, window=(64, 64), num_patches=5, delta_range=(10.0, 20.0)
    )

    # Build sequence pairs with overlap information
    # Process a single pair
    result = build_sequence_pair(
        patches1[0],      # (3, 64, 64)
        patches2[0],      # (3, 64, 64)
        dx=deltas[0, 0].item(),
        dy=deltas[0, 1].item(),
        rot_k90=rotations[0].item(),
        patch_size=16,
        stride=8
    )

    # result contains:
    # - tokens1, tokens2: Token sequences from each patch
    # - coords1, coords2: Absolute coordinates for each token
    # - overlap_mask1, overlap_mask2: Which tokens have overlaps
    # - overlap_indices1_to_2, overlap_indices2_to_1: Token mappings
    # - overlap_fractions: Fraction of overlap for each token
    # - overlap_pairs: List of (i, j) pairs of overlapping tokens

    # Process a batch efficiently
    batch_result = build_sequence_pair(
        patches1,         # (50, 3, 64, 64)
        patches2,         # (50, 3, 64, 64)
        dx=deltas[:, 0],  # (50,)
        dy=deltas[:, 1],  # (50,)
        rot_k90=rotations, # (50,)
        patch_size=16,
        stride=8
    )

    # Batch result has same keys but tensors are padded to max length
    # - tokens1, tokens2: (50, T_max, D)
    # - sequence_lengths: (50,) - actual lengths
    # - overlap_pair_counts: (50,) - number of overlaps per pair

Parameter Details
-----------------

Window and Step Sizes
~~~~~~~~~~~~~~~~~~~~~~

- **window**: Size of each patch in pixels
  - 2D: `(Y_size, X_size)`
  - 3D: `(Z_size, Y_size, X_size)`

- **step**: Distance the window moves between patches
  - 2D: `(Y_step, X_step)`
  - 3D: `(Z_step, Y_step, X_step)`
  - Common: step = window/2 for 50% overlap

Border Parameters
~~~~~~~~~~~~~~~~~

- **border**: Size of border region to downweight
  - Can be `int` (same for all dimensions) or `tuple` (per dimension)
  - `None` or `0` means no border
  - Typically 10-20% of window size

- **border_weight**: Weight for border pixels (0.0 to 1.0)
  - 0.0: Completely exclude borders
  - 0.1: Recommended default
  - 1.0: Full weight (not recommended)

Return Types
------------

All methods return PyTorch tensors (in-memory classes) or NumPy arrays (Large classes):

- **unstitch()**: Returns `torch.Tensor` of shape `(M, C, ...)`
- **stitch()**: Returns `Tuple[torch.Tensor, torch.Tensor]` (result, weights)
- **border_tensor()**: Returns `torch.Tensor` (in-memory) or `np.ndarray` (Large)
- **get_times()**: Returns `Tuple[int, ...]` with number of patches per dimension
