attr-eomt 1.1.4__tar.gz → 1.2.0__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.
Files changed (75) hide show
  1. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/PKG-INFO +1 -1
  2. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/preprocess.py +32 -0
  3. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/tta.py +9 -5
  4. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/pyproject.toml +1 -1
  5. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tests/test_smoke.py +14 -0
  6. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tests/test_tta.py +9 -0
  7. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/.github/workflows/pages.yml +0 -0
  8. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/.github/workflows/publish.yml +0 -0
  9. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/.github/workflows/release.yml +0 -0
  10. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/.github/workflows/tests.yml +0 -0
  11. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/.gitignore +0 -0
  12. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/LICENSE +0 -0
  13. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/README.md +0 -0
  14. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/configs/coco.yaml +0 -0
  15. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/docs/assets/00-hero-banner.png +0 -0
  16. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/docs/assets/00-hero-banner.svg +0 -0
  17. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/docs/examples/billie_eilish.jpg +0 -0
  18. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/docs/examples/rosalia.jpg +0 -0
  19. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/docs/examples/sample.jpg +0 -0
  20. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/docs/examples/taylor_swift.jpg +0 -0
  21. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/__init__.py +0 -0
  22. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/api.py +0 -0
  23. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/aux_cls.py +0 -0
  24. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/box_loss.py +0 -0
  25. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/compress.py +0 -0
  26. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/config.py +0 -0
  27. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/data/__init__.py +0 -0
  28. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/data/coco.py +0 -0
  29. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/data/config.py +0 -0
  30. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/data/download.py +0 -0
  31. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/data/transforms.py +0 -0
  32. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/device.py +0 -0
  33. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/ema.py +0 -0
  34. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/engine/__init__.py +0 -0
  35. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/engine/distributed.py +0 -0
  36. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/engine/match.py +0 -0
  37. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/engine/predict.py +0 -0
  38. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/engine/track.py +0 -0
  39. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/engine/train.py +0 -0
  40. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/engine/validate.py +0 -0
  41. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/loss.py +0 -0
  42. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/model.py +0 -0
  43. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/plotting.py +0 -0
  44. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/postprocess.py +0 -0
  45. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/reid.py +0 -0
  46. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/serialization.py +0 -0
  47. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/eomt/visualize.py +0 -0
  48. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/sample_data/README.md +0 -0
  49. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/sample_data/annotations/instances_train.json +0 -0
  50. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/sample_data/annotations/instances_val.json +0 -0
  51. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/sample_data/data.yaml +0 -0
  52. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/sample_data/images/train/.gitkeep +0 -0
  53. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/sample_data/images/val/.gitkeep +0 -0
  54. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/scripts/info.py +0 -0
  55. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/scripts/match.py +0 -0
  56. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/scripts/predict.py +0 -0
  57. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/scripts/push_to_hub.py +0 -0
  58. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/scripts/track.py +0 -0
  59. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/scripts/train.py +0 -0
  60. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/scripts/train_detect_coco.sh +0 -0
  61. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/scripts/val.py +0 -0
  62. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/site/index.html +0 -0
  63. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tests/test_api.py +0 -0
  64. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tests/test_architecture.py +0 -0
  65. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tests/test_augment.py +0 -0
  66. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tests/test_compress.py +0 -0
  67. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tests/test_ddp.py +0 -0
  68. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tests/test_parity.py +0 -0
  69. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tests/test_recipe.py +0 -0
  70. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tests/test_regression_attr.py +0 -0
  71. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tests/test_reid.py +0 -0
  72. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tests/test_sweep.py +0 -0
  73. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tools/attr_tagger/README.md +0 -0
  74. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/tools/attr_tagger/tag.py +0 -0
  75. {attr_eomt-1.1.4 → attr_eomt-1.2.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: attr-eomt
3
- Version: 1.1.4
3
+ Version: 1.2.0
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
@@ -65,3 +65,35 @@ def preprocess_numpy(
65
65
  canvas[:nh, :nw] = arr
66
66
  chw = np.ascontiguousarray(canvas.transpose(2, 0, 1))
67
67
  return chw, make_preprocess_meta(True, (nh, nw), input_size)
68
+
69
+
70
+ def preprocess_torch(
71
+ img_rgb_hwc: np.ndarray,
72
+ input_size: int,
73
+ *,
74
+ letterbox: bool = True,
75
+ mean=None,
76
+ std=None,
77
+ device="cpu",
78
+ ):
79
+ """:func:`preprocess_numpy` run with torch on ``device`` (e.g. the model's GPU).
80
+
81
+ The resize is an antialiased bilinear one rounded to uint8, as PIL's, so the result matches
82
+ :func:`preprocess_numpy` within one grey level. Returns ``(chw_float32_tensor, meta)``.
83
+ """
84
+ import torch
85
+ import torch.nn.functional as F # noqa: N812
86
+
87
+ h, w = img_rgb_hwc.shape[:2]
88
+ if letterbox:
89
+ scale = input_size / max(h, w)
90
+ nh, nw = max(1, round(h * scale)), max(1, round(w * scale))
91
+ else:
92
+ nh, nw = input_size, input_size
93
+ x = torch.from_numpy(np.ascontiguousarray(img_rgb_hwc)).to(device).permute(2, 0, 1)[None].float()
94
+ x = F.interpolate(x, size=(nh, nw), mode="bilinear", antialias=True, align_corners=False).round_()
95
+ mean = torch.as_tensor(IMAGENET_MEAN if mean is None else mean, dtype=torch.float32, device=device).view(3, 1, 1)
96
+ std = torch.as_tensor(IMAGENET_STD if std is None else std, dtype=torch.float32, device=device).view(3, 1, 1)
97
+ chw = x.new_zeros((3, input_size, input_size)) # 0 == mean post-normalize
98
+ chw[:, :nh, :nw] = (x[0] / 255.0 - mean) / std
99
+ return chw, make_preprocess_meta(letterbox, (nh, nw), input_size)
@@ -38,6 +38,7 @@ repair / replace rules). Attribute probabilities and embeddings are score-weight
38
38
  from __future__ import annotations
39
39
 
40
40
  import contextlib
41
+ import functools
41
42
  import itertools
42
43
  import math
43
44
  from dataclasses import dataclass
@@ -47,7 +48,7 @@ import torch
47
48
  import torch.nn.functional as F # noqa: N812
48
49
 
49
50
  from .postprocess import _masks_to_original, aux_result, aux_value_result, boxes_from_masks, query_scores
50
- from .preprocess import preprocess_numpy
51
+ from .preprocess import preprocess_numpy, preprocess_torch
51
52
 
52
53
  _AUGMENT_KEYS = {"flip", "scales"}
53
54
  _TILES_KEYS = {"size", "overlap", "full"}
@@ -288,12 +289,16 @@ def predict_views(
288
289
  amp_dtype: torch.dtype | None = None,
289
290
  aux_scopes: dict | None = None,
290
291
  embed: bool = False,
292
+ device_preprocess: bool = False,
291
293
  ) -> dict:
292
294
  """Instances of the RGB ``image`` ``(H, W, 3)`` uint8, merged over the views of ``cfg``.
293
295
 
294
296
  Returns the :func:`~eomt.postprocess.postprocess_instance` dict (``boxes`` / ``scores`` / ``classes`` / ``masks``
295
297
  and, with attribute heads, ``aux``), plus ``embed`` ``(N, hidden)`` with ``embed=True``. There is no ``query_idx``:
296
298
  a merged instance comes from several queries.
299
+
300
+ With ``device_preprocess=True`` the views are preprocessed on the model's device
301
+ (:func:`~eomt.preprocess.preprocess_torch`) instead of with PIL on the CPU; the inputs match within one grey level.
297
302
  """
298
303
  if getattr(model, "family", "instance") != "instance":
299
304
  raise NotImplementedError("TTA / tiled inference supports the instance family only.")
@@ -311,15 +316,14 @@ def predict_views(
311
316
  view_cells = [_grid_cells(v.rect, g, hg, wg) for v in views]
312
317
 
313
318
  flip_perms = {s.name: s.flip_perm() for s in getattr(model, "aux_specs", []) if s.flip_perm() is not None}
319
+ prep = functools.partial(preprocess_torch, device=dev) if device_preprocess else preprocess_numpy
314
320
  found: list[dict] = []
315
321
  for size in dict.fromkeys(v.size for v in views):
316
322
  idx = [i for i, v in enumerate(views) if v.size == size]
317
323
  for start in range(0, len(idx), cfg.batch):
318
324
  chunk = idx[start : start + cfg.batch]
319
- pre = [
320
- preprocess_numpy(_crop(image, views[i]), size, letterbox=letterbox, mean=mean, std=std) for i in chunk
321
- ]
322
- x = torch.from_numpy(np.stack([chw for chw, _ in pre])).to(dev)
325
+ pre = [prep(_crop(image, views[i]), size, letterbox=letterbox, mean=mean, std=std) for i in chunk]
326
+ x = torch.stack([torch.as_tensor(chw) for chw, _ in pre]).to(dev)
323
327
  ctx = torch.autocast(dev.type, dtype=amp_dtype) if amp_dtype is not None else contextlib.nullcontext()
324
328
  with ctx:
325
329
  out = model(x)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "attr-eomt"
7
- version = "1.1.4"
7
+ version = "1.2.0"
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,6 +514,20 @@ 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("letterbox", [True, False])
518
+ def test_preprocess_torch_matches_preprocess_numpy_within_one_grey_level(letterbox):
519
+ import numpy as np
520
+
521
+ from eomt.preprocess import IMAGENET_STD, preprocess_numpy, preprocess_torch
522
+
523
+ img = np.random.default_rng(0).integers(0, 255, (97, 151, 3), dtype=np.uint8)
524
+ chw, meta = preprocess_torch(img, 64, letterbox=letterbox)
525
+ expected, expected_meta = preprocess_numpy(img, 64, letterbox=letterbox)
526
+ assert chw.shape == (3, 64, 64) and chw.dtype == torch.float32 and meta == expected_meta
527
+ diff = np.abs(chw.numpy() - expected) * IMAGENET_STD.reshape(3, 1, 1) * 255 # in grey levels
528
+ assert diff.max() <= 1.01 and diff.mean() < 0.25
529
+
530
+
517
531
  def test_letterbox_inverse_crops_content_not_padding():
518
532
  from eomt.postprocess import _masks_to_original
519
533
  from eomt.preprocess import make_preprocess_meta
@@ -172,6 +172,15 @@ def test_flip_tta_merges_the_two_views_and_scores_their_agreement():
172
172
  assert _iou(mean["masks"][0], 14, 35, 56, 91) > 0.95
173
173
 
174
174
 
175
+ def test_device_preprocessed_views_find_the_same_instances():
176
+ img = _image(280, 420, 70, 42, 315, 98)
177
+ cfg = TTAConfig(flip=True, scales=(1.0, 1.5), tiles=True, tile_size=140)
178
+ cpu = predict_views(_Blobs(), img, cfg, imgsz=IMGSZ, conf_thres=0.3)
179
+ dev = predict_views(_Blobs(), img, cfg, imgsz=IMGSZ, conf_thres=0.3, device_preprocess=True)
180
+ assert dev["num_detections"] == cpu["num_detections"] == 1
181
+ assert torch.allclose(dev["scores"], cpu["scores"]) and _same(dev["masks"], cpu["masks"])
182
+
183
+
175
184
  def test_predict_image_with_tta_keeps_the_result_contract():
176
185
  from eomt.engine.predict import predict_image
177
186
 
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