attr-eomt 1.2.0__tar.gz → 1.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/PKG-INFO +1 -1
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/preprocess.py +52 -7
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/pyproject.toml +1 -1
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tests/test_smoke.py +13 -8
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tests/test_tta.py +1 -1
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/.github/workflows/pages.yml +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/.github/workflows/publish.yml +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/.github/workflows/release.yml +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/.github/workflows/tests.yml +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/.gitignore +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/LICENSE +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/README.md +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/configs/coco.yaml +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/docs/assets/00-hero-banner.png +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/docs/assets/00-hero-banner.svg +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/docs/examples/billie_eilish.jpg +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/docs/examples/rosalia.jpg +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/docs/examples/sample.jpg +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/docs/examples/taylor_swift.jpg +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/__init__.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/api.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/aux_cls.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/box_loss.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/compress.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/config.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/data/__init__.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/data/coco.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/data/config.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/data/download.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/data/transforms.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/device.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/ema.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/engine/__init__.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/engine/distributed.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/engine/match.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/engine/predict.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/engine/track.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/engine/train.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/engine/validate.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/loss.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/model.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/plotting.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/postprocess.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/reid.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/serialization.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/tta.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/eomt/visualize.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/sample_data/README.md +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/sample_data/annotations/instances_train.json +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/sample_data/annotations/instances_val.json +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/sample_data/data.yaml +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/sample_data/images/train/.gitkeep +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/sample_data/images/val/.gitkeep +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/scripts/info.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/scripts/match.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/scripts/predict.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/scripts/push_to_hub.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/scripts/track.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/scripts/train.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/scripts/train_detect_coco.sh +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/scripts/val.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/site/index.html +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tests/test_api.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tests/test_architecture.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tests/test_augment.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tests/test_compress.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tests/test_ddp.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tests/test_parity.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tests/test_recipe.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tests/test_regression_attr.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tests/test_reid.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tests/test_sweep.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tools/attr_tagger/README.md +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/tools/attr_tagger/tag.py +0 -0
- {attr_eomt-1.2.0 → attr_eomt-1.2.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: attr-eomt
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: Standalone EoMT (Encoder-only Mask Transformer) for instance segmentation, with DINOv2 init, COCO training/validation and inference.
|
|
5
5
|
Project-URL: Homepage, https://github.com/imagra93/attr-eomt
|
|
6
6
|
Project-URL: Documentation, https://imagra93.github.io/attr-eomt
|
|
@@ -13,10 +13,14 @@ needs to map masks back to the original image (see :func:`make_preprocess_meta`)
|
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
+
import functools
|
|
17
|
+
|
|
16
18
|
import numpy as np
|
|
17
19
|
|
|
18
20
|
IMAGENET_MEAN = np.array([0.485, 0.456, 0.406], dtype=np.float32)
|
|
19
21
|
IMAGENET_STD = np.array([0.229, 0.224, 0.225], dtype=np.float32)
|
|
22
|
+
#: Fixed-point precision of PIL's 8-bit resampling (Pillow's Resample.c).
|
|
23
|
+
_PIL_PRECISION_BITS = 32 - 8 - 2
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
def make_preprocess_meta(letterbox: bool, content_hw: tuple[int, int], input_size: int) -> dict:
|
|
@@ -67,6 +71,47 @@ def preprocess_numpy(
|
|
|
67
71
|
return chw, make_preprocess_meta(True, (nh, nw), input_size)
|
|
68
72
|
|
|
69
73
|
|
|
74
|
+
@functools.lru_cache(maxsize=64)
|
|
75
|
+
def _pil_bilinear_taps(in_size: int, out_size: int) -> tuple[np.ndarray, np.ndarray]:
|
|
76
|
+
"""PIL's fixed-point bilinear coefficients for resizing ``in_size`` px to ``out_size``.
|
|
77
|
+
|
|
78
|
+
Mirrors ``precompute_coeffs`` and ``normalize_coeffs_8bpc`` of Pillow's Resample.c. Returns the input index and
|
|
79
|
+
the integer weight of every tap, both ``(out_size, taps)``; padding taps have weight 0.
|
|
80
|
+
"""
|
|
81
|
+
scale = in_size / out_size
|
|
82
|
+
filterscale = max(scale, 1.0)
|
|
83
|
+
support = filterscale # the bilinear filter's support is 1
|
|
84
|
+
center = (np.arange(out_size) + 0.5) * scale
|
|
85
|
+
xmin = np.maximum((center - support + 0.5).astype(np.int64), 0) # C's (int) truncation
|
|
86
|
+
xmax = np.minimum((center + support + 0.5).astype(np.int64), in_size)
|
|
87
|
+
x = xmin[:, None] + np.arange(int(np.ceil(support)) * 2 + 1)
|
|
88
|
+
w = np.maximum(1.0 - np.abs((x - center[:, None] + 0.5) * (1.0 / filterscale)), 0.0) * (x < xmax[:, None])
|
|
89
|
+
total = np.cumsum(w, axis=1)[:, -1:] # summed in order, as PIL does
|
|
90
|
+
w = np.divide(w, total, out=np.zeros_like(w), where=total != 0)
|
|
91
|
+
return np.minimum(x, in_size - 1), np.floor(0.5 + w * (1 << _PIL_PRECISION_BITS)).astype(np.int32)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _pil_resize(x, nh: int, nw: int):
|
|
95
|
+
"""Resize a ``(C, H, W)`` int32 image as PIL's ``Image.resize(..., BILINEAR)`` does an 8-bit one, bit for bit:
|
|
96
|
+
a horizontal then a vertical pass, each rounded to uint8."""
|
|
97
|
+
import torch
|
|
98
|
+
|
|
99
|
+
half = 1 << (_PIL_PRECISION_BITS - 1)
|
|
100
|
+
if nw != x.shape[2]:
|
|
101
|
+
idx, k = (torch.from_numpy(a).to(x.device) for a in _pil_bilinear_taps(x.shape[2], nw))
|
|
102
|
+
acc = torch.full((x.shape[0], x.shape[1], nw), half, dtype=torch.int32, device=x.device)
|
|
103
|
+
for t in range(k.shape[1]):
|
|
104
|
+
acc += x[:, :, idx[:, t]] * k[:, t]
|
|
105
|
+
x = (acc >> _PIL_PRECISION_BITS).clamp_(0, 255)
|
|
106
|
+
if nh != x.shape[1]:
|
|
107
|
+
idx, k = (torch.from_numpy(a).to(x.device) for a in _pil_bilinear_taps(x.shape[1], nh))
|
|
108
|
+
acc = torch.full((x.shape[0], nh, x.shape[2]), half, dtype=torch.int32, device=x.device)
|
|
109
|
+
for t in range(k.shape[1]):
|
|
110
|
+
acc += x[:, idx[:, t], :] * k[:, t, None]
|
|
111
|
+
x = (acc >> _PIL_PRECISION_BITS).clamp_(0, 255)
|
|
112
|
+
return x
|
|
113
|
+
|
|
114
|
+
|
|
70
115
|
def preprocess_torch(
|
|
71
116
|
img_rgb_hwc: np.ndarray,
|
|
72
117
|
input_size: int,
|
|
@@ -76,13 +121,12 @@ def preprocess_torch(
|
|
|
76
121
|
std=None,
|
|
77
122
|
device="cpu",
|
|
78
123
|
):
|
|
79
|
-
""":func:`preprocess_numpy` run with torch on ``device`` (e.g. the model's GPU).
|
|
124
|
+
""":func:`preprocess_numpy` run with torch on ``device`` (e.g. the model's GPU), with the same result.
|
|
80
125
|
|
|
81
|
-
The resize
|
|
82
|
-
|
|
126
|
+
The resize reproduces PIL's fixed-point bilinear one, so the output equals :func:`preprocess_numpy`'s.
|
|
127
|
+
Returns ``(chw_float32_tensor, meta)``.
|
|
83
128
|
"""
|
|
84
129
|
import torch
|
|
85
|
-
import torch.nn.functional as F # noqa: N812
|
|
86
130
|
|
|
87
131
|
h, w = img_rgb_hwc.shape[:2]
|
|
88
132
|
if letterbox:
|
|
@@ -90,10 +134,11 @@ def preprocess_torch(
|
|
|
90
134
|
nh, nw = max(1, round(h * scale)), max(1, round(w * scale))
|
|
91
135
|
else:
|
|
92
136
|
nh, nw = input_size, input_size
|
|
93
|
-
x = torch.from_numpy(np.ascontiguousarray(img_rgb_hwc)).to(device).permute(2, 0, 1)
|
|
94
|
-
x =
|
|
137
|
+
x = torch.from_numpy(np.ascontiguousarray(img_rgb_hwc)).to(device).permute(2, 0, 1).to(torch.int32)
|
|
138
|
+
x = _pil_resize(x, nh, nw).float()
|
|
95
139
|
mean = torch.as_tensor(IMAGENET_MEAN if mean is None else mean, dtype=torch.float32, device=device).view(3, 1, 1)
|
|
96
140
|
std = torch.as_tensor(IMAGENET_STD if std is None else std, dtype=torch.float32, device=device).view(3, 1, 1)
|
|
141
|
+
levels = torch.full((3, 1, 1), 255.0, device=device) # not a scalar, which torch would divide by as x * (1 / 255)
|
|
97
142
|
chw = x.new_zeros((3, input_size, input_size)) # 0 == mean post-normalize
|
|
98
|
-
chw[:, :nh, :nw] = (x
|
|
143
|
+
chw[:, :nh, :nw] = (x / levels - mean) / std
|
|
99
144
|
return chw, make_preprocess_meta(letterbox, (nh, nw), input_size)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "attr-eomt"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.1"
|
|
8
8
|
description = "Standalone EoMT (Encoder-only Mask Transformer) for instance segmentation, with DINOv2 init, COCO training/validation and inference."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -514,18 +514,23 @@ def test_preprocess_letterbox_meta_and_padding():
|
|
|
514
514
|
assert not meta2["letterbox"] and meta2["content_hw"] == (20, 20)
|
|
515
515
|
|
|
516
516
|
|
|
517
|
+
@pytest.mark.parametrize("device", ["cpu", pytest.param("cuda", marks=pytest.mark.skipif(
|
|
518
|
+
not torch.cuda.is_available(), reason="no GPU"))])
|
|
517
519
|
@pytest.mark.parametrize("letterbox", [True, False])
|
|
518
|
-
|
|
520
|
+
@pytest.mark.parametrize("shape, size", [((97, 151), 64), ((720, 1280), 644), ((300, 200), 644), ((5, 900), 140)])
|
|
521
|
+
def test_preprocess_torch_equals_preprocess_numpy(shape, size, letterbox, device):
|
|
522
|
+
"""Downscaled, upscaled and extreme aspect ratios: the same input as PIL's, value for value."""
|
|
519
523
|
import numpy as np
|
|
520
524
|
|
|
521
|
-
from eomt.preprocess import
|
|
525
|
+
from eomt.preprocess import preprocess_numpy, preprocess_torch
|
|
522
526
|
|
|
523
|
-
img = np.random.default_rng(0).integers(0,
|
|
524
|
-
chw, meta = preprocess_torch(img,
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
assert
|
|
527
|
+
img = np.random.default_rng(0).integers(0, 256, (*shape, 3), dtype=np.uint8)
|
|
528
|
+
chw, meta = preprocess_torch(img, size, letterbox=letterbox, mean=(0.5, 0.4, 0.3), std=(0.2, 0.3, 0.25),
|
|
529
|
+
device=device)
|
|
530
|
+
expected, expected_meta = preprocess_numpy(img, size, letterbox=letterbox, mean=(0.5, 0.4, 0.3),
|
|
531
|
+
std=(0.2, 0.3, 0.25))
|
|
532
|
+
assert chw.shape == (3, size, size) and chw.dtype == torch.float32 and meta == expected_meta
|
|
533
|
+
assert np.array_equal(chw.cpu().numpy(), expected)
|
|
529
534
|
|
|
530
535
|
|
|
531
536
|
def test_letterbox_inverse_crops_content_not_padding():
|
|
@@ -178,7 +178,7 @@ def test_device_preprocessed_views_find_the_same_instances():
|
|
|
178
178
|
cpu = predict_views(_Blobs(), img, cfg, imgsz=IMGSZ, conf_thres=0.3)
|
|
179
179
|
dev = predict_views(_Blobs(), img, cfg, imgsz=IMGSZ, conf_thres=0.3, device_preprocess=True)
|
|
180
180
|
assert dev["num_detections"] == cpu["num_detections"] == 1
|
|
181
|
-
assert torch.
|
|
181
|
+
assert torch.equal(dev["scores"], cpu["scores"]) and torch.equal(dev["masks"], cpu["masks"])
|
|
182
182
|
|
|
183
183
|
|
|
184
184
|
def test_predict_image_with_tta_keeps_the_result_contract():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|