🗂️ Data¶
The target container every task shares, the datasets that read COCO, YOLO-format and DOTA annotations into it, and the augmentation transforms that move images and targets together. Generated from the docstrings in src/lucid_yolo/data/.
Targets¶
Targets
dataclass
¶
Geometry for a single image, kept aligned across the transform pipeline.
The boxes/labels/polygons triple shares one instance axis of
length N: labels has one entry per box and polygons, when present,
has one ring per box. rboxes lives on an independent axis of length M
and carries no labels in this container (rotated-target labelling lands with
the OBB head in Phase 8).
Attributes:
| Name | Type | Description |
|---|---|---|
boxes |
Tensor
|
|
labels |
Tensor
|
|
polygons |
list[Tensor]
|
List of |
rboxes |
Tensor
|
|
difficult |
Tensor
|
|
keypoints |
Tensor
|
|
keypoint_vis |
Tensor
|
|
Examples:
>>> import torch
>>> t = Targets(
... boxes=torch.tensor([[0.0, 0.0, 4.0, 4.0], [1.0, 1.0, 2.0, 2.0]]),
... labels=torch.tensor([3, 7]),
... )
>>> len(t.labels)
2
>>> t.filter(torch.tensor([True, False])).labels.tolist()
[3]
__post_init__
¶
Validate shapes and dtypes of every modality; raise on any mismatch.
clone
¶
filter
¶
Select a subset of targets, keeping every modality aligned.
keep selects along the shared instance axis (boxes, labels, keypoints and,
when present, polygons). Because rotated boxes live on an independent axis,
they need their own mask: when rboxes is non-empty rkeep is
required, and when rboxes is empty rkeep must be omitted. This
keeps the two axes explicit rather than silently coupling their lengths.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
keep
|
Tensor
|
|
required |
rkeep
|
Tensor | None
|
|
None
|
Returns:
| Type | Description |
|---|---|
Targets
|
A new :class: |
Targets
|
and rotated boxes; the returned tensors are independent copies. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a mask has the wrong length, or |
TypeError
|
If a mask is not boolean. |
Examples:
>>> import torch
>>> t = Targets(
... boxes=torch.tensor([[0.0, 0.0, 1.0, 1.0], [2.0, 2.0, 3.0, 3.0]]),
... labels=torch.tensor([1, 2]),
... polygons=[torch.zeros((3, 2)), torch.ones((4, 2))],
... )
>>> kept = t.filter(torch.tensor([False, True]))
>>> kept.labels.tolist(), len(kept.polygons)
([2], 1)
concat
classmethod
¶
Concatenate several images' targets into one (mosaic/mix assembly).
Boxes, labels and rotated boxes are concatenated along their axes. Polygon and keypoint presence must be consistent: either every contributing instance carries that modality or none does — a mix is rejected rather than silently dropped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
list[Targets]
|
Targets to merge, in order. An empty list yields
:meth: |
required |
Returns:
| Type | Description |
|---|---|
Targets
|
A single :class: |
Targets
|
rotated boxes. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If polygon/keypoint presence is mixed across |
Examples:
empty
classmethod
¶
Return a valid, fully-empty target set (zero instances, no rotated boxes or keypoints).
Returns:
| Name | Type | Description |
|---|---|---|
A |
Targets
|
class: |
Targets
|
and |
|
Targets
|
meth: |
Examples:
TiledTargets
dataclass
¶
One window's targets, with the R18 bookkeeping the crop produced.
Attributes:
| Name | Type | Description |
|---|---|---|
targets |
Targets
|
The window-local targets, |
difficult |
Tensor
|
|
visible_fraction |
Tensor
|
|
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> empty = TiledTargets(Targets.empty(), torch.zeros(0, dtype=torch.bool), torch.zeros(0))
>>> empty.difficult.shape
torch.Size([0])
__post_init__
¶
Validate that every per-instance field shares one axis with the targets.
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If any field's length disagrees with the instance axis. |
Datasets¶
CocoDetectionDataset
¶
Bases: Dataset[tuple[Tensor, Targets]]
COCO-format detection/segmentation dataset yielding (image, Targets).
The instances JSON is parsed once at construction: category ids are remapped
to contiguous int64 labels (the mapping is exposed as
:attr:category_id_to_label / :attr:label_to_category_id) and annotations
are grouped by image id. Each __getitem__ decodes its image to a CHW
float32 tensor in [0, 1] and returns the targets built at construction:
one xyxy box per kept annotation, and its first segmentation ring as a
(P, 2) polygon. Crowd (iscrowd=1) and non-polygon (RLE / empty /
degenerate) annotations are skipped, so a retained image carries one ring per
box or an empty target set.
Where each box comes from is stated once, in the module docstring: an image
whose instances all keep a ring gets ring-envelope boxes, so box and mask
describe one object; an image with any ringless instance carries no polygons
and gets COCO's bbox, clamped to the image bounds. Either way an instance
whose box ends up zero-width or zero-height is dropped, matching what the
train pipeline's instance_keep_mask does to one.
An optional single-image transforms (any
:class:~lucid_yolo.data.transforms.GeometricTransform, e.g.
:class:~lucid_yolo.data.letterbox.Letterbox for validation) is applied to each
sample before it is returned. Multi-image assemblies (mosaic, mixup,
copy-paste) are composed at the datamodule level, not here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
images_dir
|
Path
|
Directory holding the image files named by the annotations. |
required |
annotation_file
|
Path
|
Path to the COCO |
required |
transforms
|
GeometricTransform | None
|
Optional per-image geometric transform applied to every
sample. Defaults to |
None
|
oriented
|
bool
|
Read each annotation's four-point ring as a rotated box (WP-088;
see the module docstring). |
False
|
keypoints
|
bool
|
Parse each retained annotation's COCO |
False
|
cache_images
|
bool
|
Decode every image once at construction and retain the RGB
tensors in memory (:meth: |
False
|
Attributes:
| Name | Type | Description |
|---|---|---|
category_id_to_label |
Mapping from COCO category id to contiguous label. |
|
label_to_category_id |
The inverse mapping, label to COCO category id. |
|
keypoint_flip_pairs |
The |
Examples:
>>> CocoDetectionDataset # doctest: +SKIP
>>> # ds = CocoDetectionDataset(images_dir, annotation_file)
>>> # image, targets = ds[0] # image: (3, H, W) float32 in [0, 1]
image_ids
property
¶
The COCO image id of each loader position, in __getitem__ order.
Published because a consumer that pairs this dataset's positions with
anything read off the same annotation file — the whole-image tile merge
(:mod:lucid_yolo.eval.tile_merge) pairing a tile to its window, the COCO
scorer naming the image an accumulated detection belongs to — otherwise has
to infer the identity from the annotations that landed at a position. Two
tiles carrying byte-identical annotation sets are indistinguishable that
way and are not indistinguishable by id, so the pairing can be asserted
rather than argued.
Returns:
| Type | Description |
|---|---|
int
|
One id per position, ascending: records are sorted by image id at |
...
|
construction, which is the order :meth: |
Examples:
>>> import json, tempfile
>>> from pathlib import Path
>>> payload = {
... "categories": [{"id": 1}],
... "images": [
... {"id": 7, "file_name": "b.png", "height": 4, "width": 4},
... {"id": 3, "file_name": "a.png", "height": 4, "width": 4},
... ],
... "annotations": [],
... }
>>> with tempfile.TemporaryDirectory() as tmp:
... path = Path(tmp) / "instances.json"
... _ = path.write_text(json.dumps(payload), encoding="utf-8")
... CocoDetectionDataset(Path(tmp), path).image_ids
(3, 7)
file_names
property
¶
The image file name of each loader position, in __getitem__ order.
The companion of :attr:image_ids for a consumer that arrives with a file
rather than an id — a figure drawn over 000007.jpg needs that image's
labels, and the file name is the one key both the picture on disk and the
annotation record share (WP-189).
Returns:
| Type | Description |
|---|---|
tuple[str, ...]
|
One name per position, as the JSON spells it, aligned with :attr: |
Examples:
>>> import json, tempfile
>>> from pathlib import Path
>>> payload = {
... "categories": [{"id": 1}],
... "images": [
... {"id": 7, "file_name": "b.png", "height": 4, "width": 4},
... {"id": 3, "file_name": "a.png", "height": 4, "width": 4},
... ],
... "annotations": [],
... }
>>> with tempfile.TemporaryDirectory() as tmp:
... path = Path(tmp) / "instances.json"
... _ = path.write_text(json.dumps(payload), encoding="utf-8")
... CocoDetectionDataset(Path(tmp), path).file_names
('a.png', 'b.png')
cache_images
¶
Decode every distinct image file once and keep it for every later draw.
cache_images=True at construction calls this; a reader built without the flag
can call it later to the same effect. A second call is a no-op, and the cache is
installed only once every file has decoded, so a failure part-way leaves the
reader uncached rather than half-filled.
Records are deduplicated by file_name exactly as the annotation file spells it,
with no path normalisation: two records naming a.png share one decoded tensor,
while a.png and ./a.png are two spellings and are cached once each. The cost
of an alias is memory only — each spelling still decodes the same file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
progress
|
bool
|
Report the cache's expected size before the first decode — a
|
False
|
Examples:
>>> import json, tempfile
>>> from pathlib import Path
>>> import torch
>>> from torchvision.io import write_png
>>> payload = {
... "categories": [{"id": 1}],
... "images": [{"id": 1, "file_name": "a.png", "height": 4, "width": 6}],
... "annotations": [],
... }
>>> with tempfile.TemporaryDirectory() as tmp:
... root = Path(tmp)
... _ = (root / "instances.json").write_text(json.dumps(payload), encoding="utf-8")
... _ = write_png(torch.zeros(3, 4, 6, dtype=torch.uint8), str(root / "a.png"))
... dataset = CocoDetectionDataset(root, root / "instances.json")
... dataset.cache_images()
... (root / "a.png").unlink()
... tuple(dataset[0][0].shape)
(3, 4, 6)
targets_at
¶
Return position index's targets without decoding its image.
:meth:__getitem__ decodes the picture and applies transforms; a consumer
that wants the labels alone — to draw them over an image it reads itself, or to
count instances — gets the precomputed, untransformed
:class:~lucid_yolo.data.targets.Targets here at no decode (WP-189).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Zero-based image index, as :meth: |
required |
Returns:
| Type | Description |
|---|---|
Targets
|
The targets built at construction, in original-image pixels. |
Examples:
>>> import json, tempfile
>>> from pathlib import Path
>>> payload = {
... "categories": [{"id": 1}],
... "images": [{"id": 3, "file_name": "a.png", "height": 8, "width": 8}],
... "annotations": [{"id": 1, "image_id": 3, "category_id": 1, "bbox": [1, 2, 3, 4], "iscrowd": 0}],
... }
>>> with tempfile.TemporaryDirectory() as tmp:
... path = Path(tmp) / "instances.json"
... _ = path.write_text(json.dumps(payload), encoding="utf-8")
... CocoDetectionDataset(Path(tmp), path).targets_at(0).boxes.tolist()
[[1.0, 2.0, 4.0, 6.0]]
__getitem__
¶
Return the (image, Targets) pair for image index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Zero-based image index (COCO image ids are re-indexed to a dense
|
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
A CHW float32 image in |
Targets
|
( |
tuple[Tensor, Targets]
|
after the optional |
YoloDetectionDataset
¶
Bases: Dataset[tuple[Tensor, Targets]]
YOLO-format dataset yielding the same (image, Targets) pairs the COCO reader does.
Images are the sorted :data:IMAGE_SUFFIXES files of images_dir; each one's label file
is the same stem under labels_dir. Labels are parsed per sample rather than at
construction — one image's text is a handful of rows, so the copy-on-write pressure that
made the COCO reader precompute (WP-073) does not arise, and reading them here keeps the
image's own decoded size as the denormalizing scale. A malformed row therefore raises at
the __getitem__ that reads it, not at construction; lucid-data check is the
pre-flight that parses every row of the tree up front, and the module docstring states
why the two readers differ here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
images_dir
|
Path
|
Directory holding the split's images. |
required |
labels_dir
|
Path
|
Directory holding one |
required |
names
|
Sequence[str]
|
Class names in index order; the length bounds every row's class index. |
required |
transforms
|
GeometricTransform | None
|
Optional per-image geometric transform applied to every sample. |
None
|
oriented
|
bool
|
Read nine-field rows as quadrilaterals and fit rotated boxes, instead of
five-field |
False
|
allow_missing_labels
|
bool
|
Read an image with no label file as a background image instead
of raising. |
False
|
cache_images
|
bool
|
Decode all images once at construction and retain their RGB
tensors (:meth: |
False
|
Attributes:
| Name | Type | Description |
|---|---|---|
names |
The class names, in index order. |
|
keypoint_flip_pairs |
list[tuple[int, int]] | None
|
Always |
Examples:
>>> YoloDetectionDataset # doctest: +SKIP
>>> # ds = YoloDetectionDataset.from_root(root, "train")
>>> # image, targets = ds[0] # image: (3, H, W) float32 in [0, 1]
cache_images
¶
Decode every listed image once and keep it for every later draw.
cache_images=True at construction calls this; a reader built without the flag
can call it later to the same effect. A second call is a no-op, and the cache is
installed only once every file has decoded, so a failure part-way leaves the
reader uncached rather than half-filled.
Entries are keyed by each listed path as the directory listing spells it, with no symlink resolution, so two directory entries aliasing one file — a symlink beside its target — are cached once each. The cost of an alias is memory only.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
progress
|
bool
|
Draw a progress bar over the decode and, once it finishes, report
the image count and the bytes the cache retains as a |
False
|
Examples:
>>> import tempfile
>>> from pathlib import Path
>>> import torch
>>> from torchvision.io import write_png
>>> with tempfile.TemporaryDirectory() as tmp:
... images, labels = Path(tmp) / "images", Path(tmp) / "labels"
... images.mkdir()
... labels.mkdir()
... _ = write_png(torch.zeros(3, 4, 6, dtype=torch.uint8), str(images / "a.png"))
... dataset = YoloDetectionDataset(images, labels, ["car"], allow_missing_labels=True)
... dataset.cache_images()
... (images / "a.png").unlink()
... tuple(dataset[0][0].shape)
(3, 4, 6)
from_root
classmethod
¶
from_root(data_root: Path, split: str, transforms: GeometricTransform | None = None, *, oriented: bool = False, allow_missing_labels: bool = False, cache_images: bool = False) -> YoloDetectionDataset
Build a split's dataset from a dataset root alone.
The root's data.yaml is authoritative: it supplies the class names, and its own
split entry supplies the image directory when it resolves. Where the two directories
come from is :func:resolve_split_dirs, shared with the pre-run check so a validated
root is the tree this reader then opens.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_root
|
Path
|
Directory holding |
required |
split
|
str
|
Split name, e.g. |
required |
transforms
|
GeometricTransform | None
|
Optional per-image geometric transform. |
None
|
oriented
|
bool
|
Read the oriented nine-field rows (see the class docstring). |
False
|
allow_missing_labels
|
bool
|
Read an image with no label file as a background image. |
False
|
cache_images
|
bool
|
Decode and retain every split image as an RGB tensor, |
False
|
Returns:
| Type | Description |
|---|---|
YoloDetectionDataset
|
The dataset for that split. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the |
OSError
|
If |
Examples:
__getitem__
¶
Return the (image, Targets) pair for image index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Zero-based index into the sorted image listing. |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
A CHW float32 image in |
Targets
|
optional |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the image has no label file and |
ValueError
|
If any label row is malformed; the message names the file and line. |
YoloDataConfig
dataclass
¶
A dataset's own data.yaml: its class names and its split image directories.
Split entries are kept as written and resolved on demand by :meth:images_dir, so a
test: line pointing at a directory that was never downloaded costs nothing until
someone asks for that split.
Attributes:
| Name | Type | Description |
|---|---|---|
root |
Path
|
Directory holding the |
names |
tuple[str, ...]
|
Class names in index order — the index into this tuple is the class id. |
splits |
Mapping[str, str]
|
Raw split entries as written in the file, keyed by split name. |
Examples:
>>> import tempfile
>>> from pathlib import Path
>>> text = "names:\n- car\n- truck\nnc: 2\ntrain: ../train/images\n"
>>> with tempfile.TemporaryDirectory() as tmp:
... root = Path(tmp)
... _ = (root / DATA_YAML_NAME).write_text(text, encoding="utf-8")
... (root / "train" / "images").mkdir(parents=True)
... config = YoloDataConfig.read(root / DATA_YAML_NAME)
... (config.names, config.images_dir("train").name)
(('car', 'truck'), 'images')
read
classmethod
¶
Parse a data.yaml into its class names and split entries.
Keys the format does not define (the export's roboflow: metadata block, for
instance) are ignored. nc, when present, must agree with the length of names:
a file disagreeing with itself is one whose class space cannot be trusted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_yaml
|
Path
|
Path to the dataset's |
required |
Returns:
| Type | Description |
|---|---|
YoloDataConfig
|
The parsed configuration, rooted at the file's own directory. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the document is not a mapping, |
OSError
|
If the file cannot be read. |
Examples:
images_dir
¶
Resolve one split's image directory from the entry the file wrote.
The entry is tried literally against the file's own directory first, then with its
leading .. components dropped — the published export writes ../train/images
for a tree that sits at <root>/train/images, and the literal reading of that
escapes the root.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
split
|
str
|
Split name as keyed in the file, e.g. |
required |
Returns:
| Type | Description |
|---|---|
Path
|
The first candidate that is a directory. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If the file names no such split. |
FileNotFoundError
|
If no candidate exists; the message lists every path tried. |
Examples:
>>> import tempfile
>>> from pathlib import Path
>>> with tempfile.TemporaryDirectory() as tmp:
... root = Path(tmp)
... (root / "valid" / "images").mkdir(parents=True)
... config = YoloDataConfig(root=root, names=("car",), splits={"val": "../valid/images"})
... config.images_dir("val") == root / "valid" / "images"
True
Transforms¶
Compose
¶
Apply a sequence of :class:GeometricTransform in order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transforms
|
Sequence[GeometricTransform]
|
The transforms to apply left-to-right. An empty sequence is the identity. |
required |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> identity = Compose([])
>>> image = torch.zeros(3, 8, 8)
>>> out_image, out_targets = identity(image, Targets.empty())
>>> out_image.shape
torch.Size([3, 8, 8])
__call__
¶
Run each transform in turn, threading the image/targets pair through.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
Tensor
|
CHW image tensor. |
required |
targets
|
Targets
|
The targets to carry alongside |
required |
Returns:
| Type | Description |
|---|---|
tuple[Tensor, Targets]
|
The image and targets after all transforms have been applied. |
Letterbox
¶
Aspect-preserving resize-and-pad transform with an exact inverse (A10).
The image is scaled by r = min(target_h / H, target_w / W) (a single
ratio for both axes) and padded to the target canvas with pad_value, in
one upstream warp from the source canvas to the letterboxed one. Boxes,
polygons, rotated-box centres and keypoints are warped through the same
scale-plus-translation affine; rotated-box w/h scale by r and
theta is unchanged, and keypoint visibilities carry over untouched (a
letterbox never crops, so no annotated point leaves the canvas).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_size
|
int | tuple[int, int]
|
Target canvas as a single |
required |
pad_value
|
float
|
Constant fill for the padded border, in the image's value
range. Defaults to |
_DEFAULT_PAD_VALUE
|
allow_upscale
|
bool
|
Whether the content may be enlarged beyond native size
when the target is larger than the source. Defaults to |
True
|
Examples:
>>> from lucid_yolo.data.transforms import GeometricTransform
>>> isinstance(Letterbox(640), GeometricTransform)
True
__call__
¶
Letterbox image and warp targets through the matching affine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
Tensor
|
CHW image tensor (float, in the same value range as
|
required |
targets
|
Targets
|
Geometry to warp alongside the image. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Tensor, Targets]
|
The padded |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> lb = Letterbox(4)
>>> image = torch.zeros(3, 2, 4)
>>> t = Targets(boxes=torch.tensor([[0.0, 0.0, 4.0, 2.0]]), labels=torch.tensor([0]))
>>> out_image, out_targets = lb(image, t)
>>> out_image.shape
torch.Size([3, 4, 4])
>>> out_targets.boxes
tensor([[0., 1., 4., 3.]])
inverse_map
¶
inverse_map(points: Tensor, orig_size: tuple[int, int], letterboxed_size: tuple[int, int]) -> Tensor
Map letterboxed points back to original-image coordinates exactly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
Tensor
|
|
required |
orig_size
|
tuple[int, int]
|
Original image |
required |
letterboxed_size
|
tuple[int, int]
|
Letterboxed canvas |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
|
Tensor
|
dtype. |
Examples:
forward_affine
¶
Return the forward affine and output size mapping a source into this canvas.
Exposes the pure scale-and-translation letterbox geometry for a source of
(orig_h, orig_w) without resampling any image, so a caller can compose
it into another warp or state it to one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
orig_h
|
int
|
Source image height in pixels. |
required |
orig_w
|
int
|
Source image width in pixels. |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
A |
int
|
affine mapping source pixels into the letterboxed canvas, and the |
int
|
target canvas height and width. |
Examples:
warp_targets
¶
Warp targets through the letterbox affine for a source size, no image.
The single target-side implementation: :meth:__call__ routes through it after
resampling the image, and a fused warp calls it directly to letterbox targets
another transform has already produced at the source canvas. Boxes, polygons and
keypoints are mapped point-wise and rotated-box centres are warped with extents
scaled and angle fixed — no image is touched either way.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
targets
|
Targets
|
Geometry at the source canvas to map into the letterboxed canvas. |
required |
orig_h
|
int
|
Source image height in pixels. |
required |
orig_w
|
int
|
Source image width in pixels. |
required |
Returns:
| Type | Description |
|---|---|
Targets
|
A new :class: |
Targets
|
coordinates. |
Examples:
RandomAffine
¶
Random affine warp applied jointly to an image and its targets (WP-010).
Each call samples a rotation in [-degrees, degrees], per-axis shears in
[-shear, shear], a uniform scale in [1 - scale, 1 + scale] (floored just
above zero) and a translation of [-translate, translate] times the canvas
size on each axis — R1 Table S3's ranges, which is what this class still owns —
and states them to one fuse-augmentations segment, which resamples the image
once (bilinear, grey 114/255 fill). Every target modality rides the same
composed matrix. Boxes/polygons are clipped to the canvas and filtered by
min_box_size and min_visibility.
With letterbox set, upstream's aspect-preserving fit joins the same segment,
so the source canvas reaches the letterboxed one in a single resample rather than
two (WP-070's saving, WP-156's delegation). Targets are clipped and filtered at
the source canvas and only then mapped through the letterbox affine.
Rotated boxes take the module docstring's corner-warp-and-re-fit path (WP-058)
and are clipped and filtered by the same two thresholds, with boxes
recomputed as the envelope of the rotated geometry. That path filters, so it
requires rboxes 1:1 with boxes and no polygons (WP-056); anything else
raises :class:ValueError.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
degrees
|
float
|
Maximum absolute rotation in degrees. Defaults to |
0.0
|
translate
|
float
|
Maximum absolute translation as a fraction of the canvas size
(applied per axis). Defaults to |
0.1
|
scale
|
float
|
Half-width of the uniform scale range |
0.5
|
shear
|
float
|
Maximum absolute shear in degrees, sampled independently per axis.
Defaults to |
0.0
|
generator
|
Generator | None
|
Optional :class: |
None
|
min_box_size
|
float
|
Minimum clipped side length in pixels for an instance to be
kept. Defaults to |
2.0
|
min_visibility
|
float
|
Minimum kept-area fraction (clipped-extent area divided by
pre-clip-extent area) for an instance to be kept. Defaults to |
0.1
|
letterbox
|
int | tuple[int, int] | None
|
Output canvas the warp lands on, as a single |
None
|
allow_upscale
|
bool
|
Whether |
True
|
Attributes:
| Name | Type | Description |
|---|---|---|
last_params |
AffineParams | None
|
The :class: |
last_matrix |
Tensor | None
|
The |
letterbox |
The :class: |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> from lucid_yolo.data.transforms import GeometricTransform
>>> isinstance(RandomAffine(), GeometricTransform)
True
>>> aff = RandomAffine(degrees=0.0, translate=0.0, scale=0.0, shear=0.0)
>>> image = torch.zeros(3, 8, 8)
>>> t = Targets(boxes=torch.tensor([[1.0, 1.0, 5.0, 5.0]]), labels=torch.tensor([0]))
>>> out_image, out_targets = aff(image, t)
>>> out_image.shape
torch.Size([3, 8, 8])
>>> out_targets.boxes
tensor([[1., 1., 5., 5.]])
The letterboxed form lands on its own canvas in one resample:
>>> fused = RandomAffine(degrees=0.0, translate=0.0, scale=0.0, letterbox=16)
>>> out_image, _ = fused(torch.rand(3, 20, 40), Targets.empty())
>>> out_image.shape
torch.Size([3, 16, 16])
__call__
¶
Warp image and targets through one freshly-sampled affine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
Tensor
|
CHW image tensor (float, in the same value range as the grey
fill, i.e. |
required |
targets
|
Targets
|
Geometry to warp alongside the image. Non-empty |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
The warped |
Targets
|
warped, clipped and filtered targets. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> gen = torch.Generator().manual_seed(0)
>>> aff = RandomAffine(degrees=10.0, translate=0.1, scale=0.2, generator=gen)
>>> out_image, _ = aff(torch.rand(3, 16, 16), Targets.empty())
>>> out_image.shape
torch.Size([3, 16, 16])
>>> aff.last_matrix.shape
torch.Size([3, 3])
sample
¶
Draw one :class:AffineParams from the configured ranges, consuming the RNG.
The sampling half of the seam: this is the only method that touches
:attr:generator, so a caller that wants a stated transform rather than a
drawn one skips it and builds :class:AffineParams directly (WP-147).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
height
|
int
|
Canvas height in pixels; scales the vertical translation range. |
required |
width
|
int
|
Canvas width in pixels; scales the horizontal translation range. |
required |
Returns:
| Type | Description |
|---|---|
AffineParams
|
The sampled parameters. Nothing is stashed on the instance -- the |
AffineParams
|
|
Examples:
apply
¶
Warp image and targets through params, drawing nothing.
The application half of the seam. Given the same params this is a pure
function of its inputs, which is what lets an expectation be frozen against
stated parameters rather than against a seed (WP-147).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
Tensor
|
CHW image tensor (float, in the same value range as the grey
fill, i.e. |
required |
targets
|
Targets
|
Geometry to warp alongside the image. Non-empty |
required |
params
|
AffineParams
|
The affine to apply. |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
The warped image — the input canvas, or |
Targets
|
configured — and the warped, clipped and filtered targets. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
RuntimeError
|
If the transforms did not fuse into one matrix-bearing
geometric segment — see :meth: |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> quarter = AffineParams(
... angle=0.0, shear_x=0.0, shear_y=0.0, scale=1.0, translate_x=2.0, translate_y=0.0
... )
>>> box = Targets(boxes=torch.tensor([[1.0, 1.0, 5.0, 5.0]]), labels=torch.tensor([0]))
>>> _, out = RandomAffine().apply(torch.zeros(3, 8, 8), box, quarter)
>>> out.boxes.tolist()
[[3.0, 1.0, 7.0, 5.0]]
HSVJitter
¶
Photometric HSV jitter applied to the image alone (WP-013).
Each call samples three independent gains — hue from [-hsv_h, hsv_h],
saturation from [-hsv_s, hsv_s] and value from [-hsv_v, hsv_v] — converts
the image to HSV, applies the hue gain additively modulo 1, the saturation and
value gains multiplicatively as channel * (1 + gain) clamped to [0, 1],
and converts back to RGB. Targets are geometry only and pass through unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hsv_h
|
float
|
Half-width of the additive hue-gain range, as a fraction of the hue
circle. Defaults to |
0.015
|
hsv_s
|
float
|
Half-width of the multiplicative saturation-gain range. Defaults to
|
0.7
|
hsv_v
|
float
|
Half-width of the multiplicative value-gain range. Defaults to |
0.4
|
generator
|
Generator | None
|
Optional :class: |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
last_gains |
tuple[float, float, float] | None
|
The |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> from lucid_yolo.data.transforms import GeometricTransform
>>> isinstance(HSVJitter(), GeometricTransform)
True
>>> jitter = HSVJitter(hsv_h=0.0, hsv_s=0.0, hsv_v=0.0)
>>> image = torch.rand(3, 8, 8)
>>> out_image, out_targets = jitter(image, Targets.empty())
>>> torch.allclose(out_image, image, atol=1e-5)
True
__call__
¶
Jitter image in HSV space; return it with targets unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
Tensor
|
CHW RGB image tensor with values in |
required |
targets
|
Targets
|
Geometry carried alongside the image; returned untouched. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Tensor, Targets]
|
The colour-jittered |
Examples:
sample
¶
Draw one :class:HSVParams from the configured ranges, consuming the RNG.
The sampling half of the seam: this is the only method that touches
:attr:generator, so a caller wanting stated gains rather than drawn ones
builds :class:HSVParams directly (WP-147).
Returns:
| Type | Description |
|---|---|
HSVParams
|
The three sampled gains. Nothing is stashed on the instance -- |
HSVParams
|
|
Examples:
apply
¶
Apply params to image in HSV space, drawing nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
Tensor
|
CHW RGB image tensor with values in |
required |
targets
|
Targets
|
Geometry carried alongside the image; returned untouched. |
required |
params
|
HSVParams
|
The gains to apply. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Tensor, Targets]
|
The colour-jittered |
Examples:
HorizontalFlip
¶
Left-right image flip with matching target mirroring (WP-013).
With probability p (drawn per call) the image columns are reversed and every
target modality is mirrored about the vertical axis at x = (W - 1) / 2 — the
axis the column reversal itself reflects about, since a coordinate names a sample
point and the outermost samples sit at 0 and W - 1 (WP-154b). Boxes swap and
reflect their x-extent (x1' = (W - 1) - x2, x2' = (W - 1) - x1), polygon and
keypoint points reflect (x' = (W - 1) - x), and rotated boxes reflect their centre
(cx' = (W - 1) - cx) with the long edge reflected and re-canonicalized (WP-058).
When a dataset keypoint pair map is supplied, it then swaps left/right keypoint
identities (WP-120) — coordinates and visibility flags together, so a mirrored "left
elbow" arrives in the right slot carrying its own flag. With probability 1 - p the
image and targets pass through unchanged.
The rotated-box mirror is exact — an isometry maps the rectangle to a rectangle, and
-theta names the mirrored long edge up to the half turn a rectangle is invariant
under — so nothing is dropped and, unlike
:class:~lucid_yolo.data.affine.RandomAffine, this transform imposes no instance-axis
requirement on the rotated modality.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
p
|
float
|
Probability of flipping. Defaults to |
0.5
|
generator
|
Generator | None
|
Optional :class: |
None
|
keypoint_flip_pairs
|
list[tuple[int, int]] | None
|
Dataset-supplied |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
last_flipped |
bool | None
|
Whether the most recent call flipped, or |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> from lucid_yolo.data.transforms import GeometricTransform
>>> isinstance(HorizontalFlip(), GeometricTransform)
True
>>> flip = HorizontalFlip(p=1.0)
>>> image = torch.arange(4.0).reshape(1, 1, 4).expand(3, 1, 4).contiguous()
>>> boxes = torch.tensor([[0.0, 0.0, 1.0, 2.0]])
>>> _, out = flip(image, Targets(boxes=boxes, labels=torch.tensor([0])))
>>> out.boxes.tolist()
[[2.0, 0.0, 3.0, 2.0]]
>>> pose = Targets(
... boxes=torch.zeros((1, 4)), labels=torch.tensor([0]),
... keypoints=torch.tensor([[[1.0, 2.0], [3.0, 4.0]]]),
... keypoint_vis=torch.tensor([[2, 1]]),
... )
>>> _, mirrored = HorizontalFlip(p=1.0, keypoint_flip_pairs=[(0, 1)])(
... torch.zeros((3, 2, 4)), pose
... )
>>> mirrored.keypoints.tolist(), mirrored.keypoint_vis.tolist()
([[[0.0, 4.0], [2.0, 2.0]]], [[1, 2]])
__call__
¶
Flip image and mirror targets with probability p.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
Tensor
|
CHW image tensor. |
required |
targets
|
Targets
|
Geometry to mirror when the image is flipped. |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Either the original |
Targets
|
flipped image with every target modality mirrored. |
Examples:
sample
¶
Draw whether this call flips, consuming one uniform against p.
The sampling half of the seam: this is the only method that touches
:attr:generator, so a caller wanting a stated flip rather than a drawn one
builds :class:FlipParams directly (WP-147).
Returns:
| Type | Description |
|---|---|
FlipParams
|
The drawn decision. Nothing is stashed on the instance -- |
FlipParams
|
|
Examples:
apply
¶
Mirror image and targets when params says so, drawing nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
Tensor
|
CHW image tensor. |
required |
targets
|
Targets
|
Geometry to mirror when the image is flipped. |
required |
params
|
FlipParams
|
Whether to mirror. |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Either the original |
Targets
|
with every target modality mirrored. |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> boxes = torch.tensor([[0.0, 0.0, 1.0, 2.0]])
>>> t = Targets(boxes=boxes, labels=torch.tensor([0]))
>>> _, out = HorizontalFlip().apply(torch.zeros(3, 2, 4), t, FlipParams(flipped=True))
>>> out.boxes.tolist()
[[2.0, 0.0, 3.0, 2.0]]
MosaicAssembly
¶
Assemble four images and their targets into one 2S x 2S mosaic (WP-011).
Each call samples a centre (cx, cy) uniformly from [0.5S, 1.5S] on each
axis, anchors the four images to that centre (image 0's bottom-right corner,
image 1's bottom-left, image 2's top-right, image 3's top-left all meet at the
centre), crops each to its quadrant, and grey-fills any uncovered canvas. Each
image's boxes/polygons are shifted by its placement offset, clipped to the
canvas, filtered by min_box_size and min_visibility, then merged via
:meth:~lucid_yolo.data.targets.Targets.concat.
This is an assembly, not a :class:~lucid_yolo.data.transforms.GeometricTransform:
it consumes four (image, targets) pairs rather than one, so its call
signature deliberately differs from that protocol. The images may differ in
size but must share a channel count, dtype and device.
Rotated boxes are shifted with their image, clipped to the canvas and filtered
by the same two thresholds (WP-058, module docstring), with boxes recomputed
as the envelope of the rotated geometry. Because that path filters, an input
whose rboxes do not share the instance axis with its boxes, or that
carries polygons alongside them, raises :class:ValueError.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_size
|
int
|
The base size |
required |
generator
|
Generator | None
|
Optional :class: |
None
|
pad_value
|
float
|
Fill value for uncovered canvas. Defaults to |
_DEFAULT_PAD_VALUE
|
min_box_size
|
float
|
Minimum clipped side length in pixels for an instance to be
kept. Defaults to |
2.0
|
min_visibility
|
float
|
Minimum kept-area fraction (clipped-extent area divided by
pre-clip-extent area) for an instance to be kept. Defaults to |
0.1
|
Attributes:
| Name | Type | Description |
|---|---|---|
last_center |
tuple[int, int] | None
|
The |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> gen = torch.Generator().manual_seed(0)
>>> mosaic = MosaicAssembly(target_size=16, generator=gen)
>>> items = [(torch.zeros(3, 16, 16), Targets.empty()) for _ in range(4)]
>>> out_image, out_targets = mosaic(items)
>>> out_image.shape
torch.Size([3, 32, 32])
>>> out_targets.boxes.shape
torch.Size([0, 4])
__call__
¶
Assemble items into one mosaic image and its merged targets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
list[tuple[Tensor, Targets]]
|
Exactly four |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
The |
Targets
|
and merged targets. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> gen = torch.Generator().manual_seed(0)
>>> mosaic = MosaicAssembly(target_size=8, generator=gen)
>>> box = Targets(boxes=torch.tensor([[1.0, 1.0, 5.0, 5.0]]), labels=torch.tensor([0]))
>>> items = [(torch.rand(3, 8, 8), box.clone()) for _ in range(4)]
>>> out_image, out_targets = mosaic(items)
>>> out_image.shape
torch.Size([3, 16, 16])
>>> mosaic.last_center is not None
True
sample
¶
Draw the stitch centre, consuming two uniforms from the RNG.
The sampling half of the seam: this is the only method that touches
:attr:generator, so a caller wanting a stated centre rather than a drawn
one builds :class:MosaicParams directly (WP-147).
Returns:
| Type | Description |
|---|---|
MosaicParams
|
The sampled centre, each axis an integer drawn uniformly from |
MosaicParams
|
|
MosaicParams
|
is set by :meth: |
Examples:
apply
¶
Stitch items about params' centre, drawing nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
list[tuple[Tensor, Targets]]
|
Exactly four |
required |
params
|
MosaicParams
|
The stitch centre to anchor the four images to. |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
The |
Targets
|
and merged targets. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> box = Targets(boxes=torch.tensor([[1.0, 1.0, 5.0, 5.0]]), labels=torch.tensor([0]))
>>> items = [(torch.rand(3, 8, 8), box.clone()) for _ in range(4)]
>>> out_image, _ = MosaicAssembly(target_size=8).apply(items, MosaicParams(center_x=8, center_y=8))
>>> out_image.shape
torch.Size([3, 16, 16])
Mixup
¶
Convex pixel blend of two same-size images with concatenated targets (WP-012).
Each call draws a trigger uniform: with probability p the two images are
blended lam * a + (1 - lam) * b with lam from Beta(alpha, alpha) and
both target sets are concatenated unchanged (every box from both images is
kept at full weight — detection-lineage mixup applies no lam label
smoothing). With probability 1 - p the first pair passes through
untouched.
This is an assembly, not a :class:~lucid_yolo.data.transforms.GeometricTransform:
it consumes two (image, targets) pairs rather than one. The two images must
share the same shape; concatenating the targets additionally requires
consistent polygon presence (a :meth:~lucid_yolo.data.targets.Targets.concat
rule).
Rotated boxes are carried through unchanged (WP-058): the blend moves no
geometry, so both inputs' rboxes are concatenated exactly as their boxes and
labels are, still canonical, with nothing clipped or dropped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
p
|
float
|
Probability of blending on a given call. |
required |
alpha
|
float
|
Shared |
32.0
|
generator
|
Generator | None
|
Optional :class: |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
last_lam |
float | None
|
The blend factor used on the most recent call, or |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> gen = torch.Generator().manual_seed(0)
>>> mixup = Mixup(p=1.0, generator=gen)
>>> a = (torch.ones(3, 8, 8), Targets(boxes=torch.zeros((1, 4)), labels=torch.tensor([0])))
>>> b = (torch.zeros(3, 8, 8), Targets(boxes=torch.ones((1, 4)), labels=torch.tensor([1])))
>>> out_image, out_targets = mixup([a, b])
>>> out_targets.labels.tolist()
[0, 1]
>>> bool(torch.allclose(out_image, torch.full_like(out_image, mixup.last_lam)))
True
__call__
¶
Blend items with probability p; otherwise return the first pair.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
list[tuple[Tensor, Targets]]
|
Exactly two |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Either the blended |
Targets
|
concatenated targets — or, when the call does not trigger, the first |
tuple[Tensor, Targets]
|
input pair untouched. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
sample
¶
Draw the trigger and, when it fires, the blend factor.
The sampling half of the seam: this is the only method that touches
:attr:generator, so a caller wanting a stated blend rather than a drawn one
builds :class:MixupParams directly (WP-147). The trigger uniform is always
consumed; the two gamma draws behind lam are consumed only when it fires,
which is the draw order __call__ has always had.
Returns:
| Type | Description |
|---|---|
MixupParams
|
The sampled decision: a blend factor, or |
Examples:
apply
¶
Blend items at params' factor, or pass the first pair through.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
list[tuple[Tensor, Targets]]
|
Exactly two |
required |
params
|
MixupParams
|
The blend factor, or |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Either the blended |
Targets
|
untouched. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the two images differ in shape, checked only when
|
Examples:
CopyPaste
¶
Paste polygon instances from one image onto another (WP-012).
The two (image, targets) inputs are the destination a and the
source b. For each polygon-carrying instance of b a paste draw is
taken; with probability p (and up to max_paste pastes) the instance's
polygon is rasterised to a pixel mask, those source pixels overwrite the
destination, and the instance's box/label/polygon are appended to a's
targets. Instances of b without a polygon are never pasted — the mask is
mandatory.
Because pasted instances always carry a polygon, the destination a must
itself carry polygons (one ring per box) or be empty; otherwise the merged
target set would mix polygon-present and polygon-absent instances, which
:class:~lucid_yolo.data.targets.Targets rejects. This matches the segmentation
lineage the augmentation comes from.
This is an assembly, not a :class:~lucid_yolo.data.transforms.GeometricTransform:
it consumes two pairs rather than one. Both images must share the same shape.
Rotated boxes are not supported, for the reason the module docstring gives:
the paste unit is a polygon mask and the oriented path carries no polygons
(WP-056), so any input with a non-empty rboxes raises
:class:NotImplementedError rather than have its rotated modality dropped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
p
|
float
|
Per-candidate-instance paste probability. |
required |
max_paste
|
int | None
|
Optional cap on the number of instances pasted in one call.
|
None
|
generator
|
Generator | None
|
Optional :class: |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
last_pasted |
int | None
|
The number of instances pasted on the most recent call, or
|
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> ring = torch.tensor([[1.0, 1.0], [4.0, 1.0], [4.0, 4.0], [1.0, 4.0]])
>>> src = Targets(boxes=torch.tensor([[1.0, 1.0, 4.0, 4.0]]), labels=torch.tensor([5]), polygons=[ring])
>>> a = (torch.zeros(3, 6, 6), Targets.empty())
>>> b = (torch.ones(3, 6, 6), src)
>>> copy_paste = CopyPaste(p=1.0)
>>> out_image, out_targets = copy_paste([a, b])
>>> out_targets.labels.tolist(), copy_paste.last_pasted
([5], 1)
>>> bool(out_image[0, 2, 2] == 1.0)
True
__call__
¶
Paste sampled source instances onto the destination image and targets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
list[tuple[Tensor, Targets]]
|
Exactly two |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
The destination image with the pasted pixels and its targets with the |
Targets
|
pasted instances appended. When nothing is pasted, a copy of the |
tuple[Tensor, Targets]
|
destination image and a clone of its targets are returned. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
NotImplementedError
|
If either input carries a non-empty |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> ring = torch.tensor([[1.0, 1.0], [3.0, 1.0], [3.0, 3.0], [1.0, 3.0]])
>>> src = Targets(boxes=torch.tensor([[1.0, 1.0, 3.0, 3.0]]), labels=torch.tensor([2]), polygons=[ring])
>>> copy_paste = CopyPaste(p=0.0)
>>> a = (torch.zeros(3, 5, 5), Targets.empty())
>>> out_image, out_targets = copy_paste([a, (torch.ones(3, 5, 5), src)])
>>> torch.equal(out_image, a[0]) and out_targets.boxes.shape[0] == 0
True
sample
¶
Draw each of candidate_count candidates against p, up to max_paste.
The sampling half of the seam: this is the only method that touches
:attr:generator, so a caller wanting a stated selection rather than a drawn
one builds :class:CopyPasteParams directly (WP-147). The draw count depends
on the candidate count and the cap alone -- never on pixels -- which is what
lets the loop split from the paste it used to interleave with.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
candidate_count
|
int
|
Number of polygon-carrying source instances, i.e.
|
required |
Returns:
| Type | Description |
|---|---|
CopyPasteParams
|
The selected indices, in paste order. Nothing is stashed on the instance |
CopyPasteParams
|
-- |
Examples:
apply
¶
Paste the instances params names onto the destination, drawing nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
list[tuple[Tensor, Targets]]
|
Exactly two |
required |
params
|
CopyPasteParams
|
The source instance indices to paste, in paste order. |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
The destination image with the pasted pixels and its targets with the |
Targets
|
pasted instances appended. When |
tuple[Tensor, Targets]
|
destination image and a clone of its targets are returned. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the two images differ in shape. |
NotImplementedError
|
If either input carries a non-empty |
Examples:
>>> import torch
>>> from lucid_yolo.data.targets import Targets
>>> ring = torch.tensor([[1.0, 1.0], [4.0, 1.0], [4.0, 4.0], [1.0, 4.0]])
>>> src = Targets(boxes=torch.tensor([[1.0, 1.0, 4.0, 4.0]]), labels=torch.tensor([5]), polygons=[ring])
>>> items = [(torch.zeros(3, 6, 6), Targets.empty()), (torch.ones(3, 6, 6), src)]
>>> _, out = CopyPaste(p=1.0).apply(items, CopyPasteParams(selected=(0,)))
>>> out.labels.tolist()
[5]