videopython 0.55.2__tar.gz → 0.55.3__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 (92) hide show
  1. {videopython-0.55.2 → videopython-0.55.3}/PKG-INFO +1 -1
  2. {videopython-0.55.2 → videopython-0.55.3}/pyproject.toml +1 -1
  3. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/_ollama.py +58 -3
  4. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/effects.py +5 -1
  5. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/understanding/audio.py +10 -1
  6. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/understanding/image.py +5 -0
  7. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/understanding/objects.py +64 -7
  8. {videopython-0.55.2 → videopython-0.55.3}/.gitignore +0 -0
  9. {videopython-0.55.2 → videopython-0.55.3}/LICENSE +0 -0
  10. {videopython-0.55.2 → videopython-0.55.3}/README.md +0 -0
  11. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/__init__.py +0 -0
  12. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/__init__.py +0 -0
  13. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/_device.py +0 -0
  14. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/_optional.py +0 -0
  15. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/_predictor.py +0 -0
  16. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/_revisions.py +0 -0
  17. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/auto_edit/__init__.py +0 -0
  18. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/auto_edit/backend.py +0 -0
  19. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/auto_edit/catalog.py +0 -0
  20. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/auto_edit/editor.py +0 -0
  21. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/auto_edit/local.py +0 -0
  22. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/auto_edit/models.py +0 -0
  23. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/auto_edit/resolve.py +0 -0
  24. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/dubbing/__init__.py +0 -0
  25. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/dubbing/_tts_backend.py +0 -0
  26. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/dubbing/audio_ops.py +0 -0
  27. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/dubbing/config.py +0 -0
  28. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/dubbing/dubber.py +0 -0
  29. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/dubbing/models.py +0 -0
  30. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/dubbing/pipeline.py +0 -0
  31. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/dubbing/quality.py +0 -0
  32. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/dubbing/remux.py +0 -0
  33. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/dubbing/separation.py +0 -0
  34. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/dubbing/timing.py +0 -0
  35. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/dubbing/translation.py +0 -0
  36. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/dubbing/voice_sample.py +0 -0
  37. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/errors.py +0 -0
  38. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/generation/__init__.py +0 -0
  39. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/generation/audio.py +0 -0
  40. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/generation/image.py +0 -0
  41. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/generation/video.py +0 -0
  42. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/keyframe.py +0 -0
  43. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/ops.py +0 -0
  44. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/transforms.py +0 -0
  45. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/understanding/__init__.py +0 -0
  46. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/understanding/_detector.py +0 -0
  47. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/understanding/classification.py +0 -0
  48. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/understanding/faces.py +0 -0
  49. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/understanding/temporal.py +0 -0
  50. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/video_analysis/__init__.py +0 -0
  51. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/video_analysis/analyzer.py +0 -0
  52. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/video_analysis/detectors.py +0 -0
  53. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/video_analysis/models.py +0 -0
  54. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/video_analysis/sampling.py +0 -0
  55. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/ai/video_analysis/source_metadata.py +0 -0
  56. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/audio/__init__.py +0 -0
  57. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/audio/analysis.py +0 -0
  58. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/audio/audio.py +0 -0
  59. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/__init__.py +0 -0
  60. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/_dimensions.py +0 -0
  61. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/_ffmpeg.py +0 -0
  62. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/_video_io.py +0 -0
  63. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/description.py +0 -0
  64. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/draw_detections.py +0 -0
  65. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/exceptions.py +0 -0
  66. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/fonts/Anton-OFL.txt +0 -0
  67. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/fonts/Anton-Regular.ttf +0 -0
  68. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/fonts/BebasNeue-OFL.txt +0 -0
  69. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/fonts/BebasNeue-Regular.ttf +0 -0
  70. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/fonts/DejaVuSans.ttf +0 -0
  71. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/fonts/LICENSE_DEJAVU +0 -0
  72. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/fonts/Lato-Bold.ttf +0 -0
  73. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/fonts/Lato-OFL.txt +0 -0
  74. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/fonts/Poppins-Bold.ttf +0 -0
  75. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/fonts/Poppins-OFL.txt +0 -0
  76. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/fonts/__init__.py +0 -0
  77. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/transcription.py +0 -0
  78. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/base/video.py +0 -0
  79. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/editing/__init__.py +0 -0
  80. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/editing/_ass.py +0 -0
  81. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/editing/_easing.py +0 -0
  82. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/editing/_schema.py +0 -0
  83. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/editing/audio_ops.py +0 -0
  84. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/editing/effects.py +0 -0
  85. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/editing/operation.py +0 -0
  86. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/editing/streaming.py +0 -0
  87. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/editing/transcription_overlay.py +0 -0
  88. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/editing/transforms.py +0 -0
  89. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/editing/video_edit.py +0 -0
  90. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/mcp/__init__.py +0 -0
  91. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/mcp/server.py +0 -0
  92. {videopython-0.55.2 → videopython-0.55.3}/src/videopython/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: videopython
3
- Version: 0.55.2
3
+ Version: 0.55.3
4
4
  Summary: Minimal video generation and processing library.
5
5
  Project-URL: Homepage, https://videopython.com
6
6
  Project-URL: Repository, https://github.com/bartwojtowicz/videopython/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "videopython"
3
- version = "0.55.2"
3
+ version = "0.55.3"
4
4
  description = "Minimal video generation and processing library."
5
5
  authors = [
6
6
  { name = "Bartosz Wójtowicz", email = "bartoszwojtowicz@outlook.com" },
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ import re
6
7
  from typing import Any
7
8
 
8
9
  import numpy as np
@@ -16,6 +17,45 @@ class OllamaError(AiError, RuntimeError):
16
17
  """Ollama returned unusable output (non-JSON or an unexpected shape)."""
17
18
 
18
19
 
20
+ # Ollama's own default context window is 4096 tokens, and an oversized request
21
+ # *fails* (``exceed_context_size_error``) instead of being truncated -- so a call
22
+ # carrying images has to size the window before sending. A vision model spends
23
+ # roughly this many tokens per image. Deliberately generous: underestimating
24
+ # fails the request outright, overestimating only costs KV-cache memory.
25
+ _TOKENS_PER_IMAGE = 1024
26
+ # Headroom for the system prompt, the user text, and the generated answer.
27
+ _TEXT_TOKEN_ALLOWANCE = 2048
28
+ # Never ask for less than Ollama's own default.
29
+ _MIN_NUM_CTX = 4096
30
+ _CONTEXT_OVERFLOW_RE = re.compile(r"request \((\d+) tokens\) exceeds the available context size")
31
+
32
+
33
+ def _round_num_ctx(token_count: int) -> int:
34
+ return max(_MIN_NUM_CTX, 1 << (token_count - 1).bit_length())
35
+
36
+
37
+ def _num_ctx_for_images(image_count: int) -> int:
38
+ """Estimate the initial context window for ``image_count`` images.
39
+
40
+ Rounded up to a power of two so a run over scenes with differing frame counts
41
+ reuses a handful of window sizes instead of a new one per call -- ``num_ctx``
42
+ is a runner-level setting, so varying it every call risks reloading the model
43
+ between scenes.
44
+ """
45
+ needed = image_count * _TOKENS_PER_IMAGE + _TEXT_TOKEN_ALLOWANCE
46
+ return _round_num_ctx(needed)
47
+
48
+
49
+ def _num_ctx_after_overflow(error: str, current: int) -> int | None:
50
+ """Return a larger context after Ollama reports a context overflow."""
51
+ match = _CONTEXT_OVERFLOW_RE.search(error)
52
+ if match:
53
+ return _round_num_ctx(int(match.group(1)) + _TEXT_TOKEN_ALLOWANCE)
54
+ if "exceed_context_size_error" in error:
55
+ return current * 2
56
+ return None
57
+
58
+
19
59
  class OllamaStructuredClient:
20
60
  """Generate schema-constrained JSON from text + optional images via Ollama.
21
61
 
@@ -30,6 +70,10 @@ class OllamaStructuredClient:
30
70
  budget thinking, stops on ``length``, and returns empty content. None of these
31
71
  callers want the chain-of-thought, so thinking is disabled on models that
32
72
  support it.
73
+
74
+ Image requests start with a context estimate based on the image count. If a
75
+ model uses more visual tokens, the request retries once with the token count
76
+ reported by Ollama. An explicit ``num_ctx`` in ``options`` always wins.
33
77
  """
34
78
 
35
79
  def __init__(self, model: str, *, host: str | None = None, options: dict[str, Any] | None = None) -> None:
@@ -69,12 +113,23 @@ class OllamaStructuredClient:
69
113
  if images:
70
114
  user["images"] = [encode_png_b64(image) for image in images]
71
115
  messages = [{"role": "system", "content": system}, user]
116
+ options = self.options
117
+ image_count = len(images) if images else 0
118
+ auto_num_ctx = image_count > 0 and "num_ctx" not in options
119
+ if auto_num_ctx:
120
+ options = {**options, "num_ctx": _num_ctx_for_images(image_count)}
72
121
  kwargs: dict[str, Any] = {}
73
122
  if self._supports_thinking():
74
123
  kwargs["think"] = False
75
- response = self._get_client().chat(
76
- model=self.model, messages=messages, format=schema, options=self.options, **kwargs
77
- )
124
+ client = self._get_client()
125
+ try:
126
+ response = client.chat(model=self.model, messages=messages, format=schema, options=options, **kwargs)
127
+ except Exception as exc:
128
+ retry_num_ctx = _num_ctx_after_overflow(str(exc), options["num_ctx"]) if auto_num_ctx else None
129
+ if retry_num_ctx is None:
130
+ raise
131
+ options = {**options, "num_ctx": retry_num_ctx}
132
+ response = client.chat(model=self.model, messages=messages, format=schema, options=options, **kwargs)
78
133
  content = response.message.content
79
134
  try:
80
135
  data = json.loads(content)
@@ -43,7 +43,11 @@ class ObjectDetectionOverlay(Effect):
43
43
  confidence_threshold: float = Field(0.5, ge=0, le=1, description="Minimum detection confidence to draw a box, 0-1.")
44
44
  class_filter: list[str] | None = Field(
45
45
  None,
46
- description='Only draw these COCO class names, e.g. ["person", "car", "dog"]. Null draws all classes.',
46
+ description=(
47
+ 'Only draw these COCO class names, e.g. ["person", "car", "motorcycle"]. '
48
+ "Null draws all classes. Standard COCO names and D-FINE's alternate spellings "
49
+ "(motorbike, aeroplane, sofa, pottedplant, diningtable, tvmonitor) are accepted."
50
+ ),
47
51
  )
48
52
  show_confidence: bool = Field(True, description="Append the detection confidence as a percentage to each label.")
49
53
  box_color: tuple[int, int, int] | None = Field(
@@ -333,7 +333,16 @@ class AudioToText(ManagedPredictor):
333
333
  )
334
334
 
335
335
  all_words = self._assign_speakers_to_words(all_words, diarization_result)
336
- return Transcription(words=all_words, language=transcription.language)
336
+
337
+ # Rebuilding from words regroups by speaker and drops the per-segment
338
+ # confidence the supplied transcription carried, exactly as it does on the
339
+ # combined path -- so re-attach it the same way. Without this, splitting
340
+ # transcription and diarization into two calls silently loses confidence
341
+ # that running them as one keeps.
342
+ source_segments = transcription.segments
343
+ rebuilt = Transcription(words=all_words, language=transcription.language)
344
+ _attach_confidence_by_overlap(rebuilt.segments, source_segments)
345
+ return rebuilt
337
346
 
338
347
  def _run_vad(self, audio_mono: Audio) -> list[tuple[float, float]]:
339
348
  """Return voiced spans in seconds using Silero VAD.
@@ -41,6 +41,11 @@ class SceneVLM(ManagedPredictor):
41
41
  The model must be vision-capable and support Ollama's structured-output
42
42
  ``format``; ``ollama pull <model>`` first. ``options`` are extra Ollama
43
43
  generation options merged over ``temperature=0``.
44
+
45
+ A scene's frames are sent as one multi-image request, so the context window
46
+ is sized to the frame count automatically -- Ollama's 4096-token default
47
+ fits only one or two frames and *fails* anything larger. Pass an explicit
48
+ ``num_ctx`` in ``options`` to override that sizing.
44
49
  """
45
50
 
46
51
  def __init__(
@@ -10,8 +10,12 @@ two stay one mental model. Consumed by
10
10
  per-frame object analysis.
11
11
 
12
12
  D-FINE (Apache-2.0) replaced the AGPL-licensed Ultralytics YOLO weights. Its COCO
13
- labels use VOC-style spellings (``motorbike``, ``aeroplane``, ``sofa``, ``pottedplant``,
14
- ``diningtable``, ``tvmonitor``) -- ``class_filter`` must use the model's exact names.
13
+ labels use VOC-style spellings for six classes (``motorbike``, ``aeroplane``,
14
+ ``sofa``, ``pottedplant``, ``diningtable``, ``tvmonitor``) where the standard COCO
15
+ names are ``motorcycle``, ``airplane``, ``couch``, ``potted plant``,
16
+ ``dining table`` and ``tv``. ``class_filter`` accepts either spelling: names are
17
+ normalized through :data:`CLASS_ALIASES` before matching, and any name the model
18
+ does not emit is logged once the class list is known.
15
19
  """
16
20
 
17
21
  from __future__ import annotations
@@ -19,6 +23,9 @@ from __future__ import annotations
19
23
  import logging
20
24
  from typing import TYPE_CHECKING, Any
21
25
 
26
+ if TYPE_CHECKING:
27
+ from collections.abc import Iterable
28
+
22
29
  from videopython.ai._revisions import pinned
23
30
  from videopython.ai.understanding._detector import Backend, DetectorBase
24
31
  from videopython.base.description import BoundingBox, DetectedObject
@@ -28,7 +35,31 @@ if TYPE_CHECKING:
28
35
 
29
36
  logger = logging.getLogger(__name__)
30
37
 
31
- __all__ = ["ObjectDetector", "MODEL_SIZES"]
38
+ __all__ = ["ObjectDetector", "MODEL_SIZES", "CLASS_ALIASES", "normalize_class_names"]
39
+
40
+ # D-FINE emits VOC-style spellings for six COCO classes. Callers -- and LLMs asked
41
+ # to name a class -- reach for the standard COCO spelling, which would match
42
+ # nothing and draw nothing, with no error to explain the silence. Accept both.
43
+ CLASS_ALIASES: dict[str, str] = {
44
+ "motorcycle": "motorbike",
45
+ "airplane": "aeroplane",
46
+ "couch": "sofa",
47
+ "potted plant": "pottedplant",
48
+ "dining table": "diningtable",
49
+ "tv": "tvmonitor",
50
+ }
51
+
52
+
53
+ def normalize_class_names(names: "Iterable[str]") -> tuple[str, ...]:
54
+ """Map COCO-standard class names onto the VOC-style spellings D-FINE emits.
55
+
56
+ Case and surrounding whitespace are normalized first, so ``"Potted Plant"``
57
+ and ``"potted plant"`` both reach ``pottedplant``. A name with no alias is
58
+ passed through unchanged (lowercased) -- this translates spellings, it does
59
+ not validate them; see :meth:`ObjectDetector.unknown_filter_classes`.
60
+ """
61
+ return tuple(CLASS_ALIASES.get(key, key) for key in (" ".join(n.lower().split()) for n in names))
62
+
32
63
 
33
64
  # D-FINE COCO checkpoints (Apache-2.0), in ascending size/quality. ``model_size``
34
65
  # on ``ObjectDetectionOverlay`` maps onto these; pinned in ``_revisions.py``.
@@ -46,7 +77,9 @@ class ObjectDetector(DetectorBase[DetectedObject]):
46
77
  The D-FINE weights (default ``ustc-community/dfine-nano-coco``) download from
47
78
  HuggingFace on first real use; class names come from the model config.
48
79
  Detection is gated by ``confidence_threshold`` and optionally restricted to
49
- ``class_filter`` (COCO class names; YOLO-style spellings are normalized).
80
+ ``class_filter``, which accepts either D-FINE's VOC-style spellings or the
81
+ standard COCO ones (``motorcycle``, ``airplane``, ``couch``, ``potted plant``,
82
+ ``dining table``, ``tv``) -- see :func:`normalize_class_names`.
50
83
  """
51
84
 
52
85
  DEFAULT_CONFIDENCE_THRESHOLD = 0.5
@@ -68,14 +101,18 @@ class ObjectDetector(DetectorBase[DetectedObject]):
68
101
  ``ustc-community/dfine-nano-coco``, ``...-small-coco``,
69
102
  ``...-medium-coco``, ``...-large-coco``). Downloaded on first use.
70
103
  confidence_threshold: Minimum detection confidence in ``[0, 1]``.
71
- class_filter: If non-empty, only these COCO class names are kept
72
- (D-FINE's VOC-style spelling, e.g. ``motorbike``/``tvmonitor``).
104
+ class_filter: If non-empty, only these COCO class names are kept.
105
+ Either spelling works -- ``motorcycle`` and ``motorbike`` both
106
+ match -- and names are normalized via
107
+ :func:`normalize_class_names`. A name the model never emits is
108
+ logged as a warning once the model loads, since it would
109
+ otherwise just silently match nothing.
73
110
  backend: Detection device - ``"cpu"``, ``"gpu"``, or ``"auto"``.
74
111
  """
75
112
  super().__init__(backend=backend)
76
113
  self.model_name = model_name
77
114
  self.confidence_threshold = confidence_threshold
78
- self.class_filter = tuple(class_filter)
115
+ self.class_filter = normalize_class_names(class_filter)
79
116
  self._model: Any = None
80
117
  self._processor: Any = None
81
118
  self._class_names: dict[int, str] = {}
@@ -93,6 +130,26 @@ class ObjectDetector(DetectorBase[DetectedObject]):
93
130
  model = model.to("cuda")
94
131
  self._model = model
95
132
  self._class_names = {int(k): v for k, v in model.config.id2label.items()}
133
+ self._warn_unknown_filter_classes()
134
+
135
+ def unknown_filter_classes(self) -> tuple[str, ...]:
136
+ """``class_filter`` names this model never emits (empty until it loads)."""
137
+ if not self._class_names:
138
+ return ()
139
+ known = set(self._class_names.values())
140
+ return tuple(name for name in self.class_filter if name not in known)
141
+
142
+ def _warn_unknown_filter_classes(self) -> None:
143
+ """Log filter names that cannot match, which would otherwise draw nothing."""
144
+ unknown = self.unknown_filter_classes()
145
+ if unknown:
146
+ logger.warning(
147
+ "class_filter names not emitted by %s: %s. Nothing will be detected for them; "
148
+ "the model's classes are %s.",
149
+ self.model_name,
150
+ ", ".join(unknown),
151
+ ", ".join(sorted(set(self._class_names.values()))),
152
+ )
96
153
 
97
154
  def _infer(self, images: list[np.ndarray]) -> list[list[DetectedObject]]:
98
155
  import torch
File without changes
File without changes
File without changes