Increase ruff coverage (#23644)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / Jetson Jetpack 6 (push) Waiting to run

* Pin ruff

* Add python upgrade fixes

This enables python upgrade checks in ruff to look for deprecated types and patterns. This namely fixes:
- usage of deprecated `Typing` which is now built in
- some specific exceptions which are caught and have new aliases

Some specific UP checks were also ignored as they are stylistic / unimportant and likely to cause bugs

* Remove async blocking calls

Use asyncio.to_thread on two remaining blocking calls to fix hanging event thread loop. Enable this specific rule to block it in the future.

* Use proper logging mechanism

* Correctly format logs

* Raise with context

When raising an exception include the from context to improve debugging

* Cleanup
This commit is contained in:
Nicolas Mowen
2026-07-06 12:28:02 -05:00
committed by GitHub
parent 455b8687e8
commit 4ee12e6237
169 changed files with 1053 additions and 1150 deletions
@@ -10,7 +10,7 @@ import random
import re
import string
from pathlib import Path
from typing import Any, List, Tuple
from typing import Any
import cv2
import numpy as np
@@ -86,7 +86,7 @@ class LicensePlateProcessingMixin:
self.similarity_threshold = 0.8
self.cluster_threshold = 0.85
def _detect(self, image: np.ndarray, debug_frame_id: int) -> List[np.ndarray]:
def _detect(self, image: np.ndarray, debug_frame_id: int) -> list[np.ndarray]:
"""
Detect possible areas of text in the input image by first resizing and normalizing it,
running a detection model, and filtering out low-probability regions.
@@ -132,8 +132,8 @@ class LicensePlateProcessingMixin:
return self._filter_polygon(boxes, (h, w)) # type: ignore[return-value,arg-type]
def _classify(
self, images: List[np.ndarray]
) -> Tuple[List[np.ndarray], List[Tuple[str, float]]] | None:
self, images: list[np.ndarray]
) -> tuple[list[np.ndarray], list[tuple[str, float]]] | None:
"""
Classify the orientation or category of each detected license plate.
@@ -163,8 +163,8 @@ class LicensePlateProcessingMixin:
return self._process_classification_output(images, outputs)
def _recognize(
self, camera: str, images: List[np.ndarray]
) -> Tuple[List[str], List[List[float]]]:
self, camera: str, images: list[np.ndarray]
) -> tuple[list[str], list[list[float]]]:
"""
Recognize the characters on the detected license plates using the recognition model.
@@ -205,7 +205,7 @@ class LicensePlateProcessingMixin:
def _process_license_plate(
self, camera: str, id: str, image: np.ndarray, debug_frame_id: int
) -> Tuple[List[str], List[List[float]], List[int]]:
) -> tuple[list[str], list[list[float]], list[int]]:
"""
Complete pipeline for detecting, classifying, and recognizing license plates in the input image.
Combines multi-line plates into a single plate string, grouping boxes by vertical alignment and ordering top to bottom,
@@ -469,11 +469,11 @@ class LicensePlateProcessingMixin:
def _merge_nearby_boxes(
self,
boxes: List[np.ndarray],
boxes: list[np.ndarray],
plate_width: float,
gap_fraction: float = 0.1,
min_overlap_fraction: float = -0.2,
) -> List[np.ndarray]:
) -> list[np.ndarray]:
"""
Merge bounding boxes that are likely part of the same license plate based on proximity,
with a dynamic max_gap based on the provided width of the entire license plate.
@@ -555,7 +555,7 @@ class LicensePlateProcessingMixin:
def _boxes_from_bitmap(
self, output: np.ndarray, mask: np.ndarray, dest_width: int, dest_height: int
) -> Tuple[np.ndarray, List[float]]:
) -> tuple[np.ndarray, list[float]]:
"""
Process the binary mask to extract bounding boxes and associated confidence scores.
@@ -620,7 +620,7 @@ class LicensePlateProcessingMixin:
return np.array(boxes, dtype="int32"), scores
@staticmethod
def _get_min_boxes(contour: np.ndarray) -> Tuple[List[Tuple[float, float]], float]:
def _get_min_boxes(contour: np.ndarray) -> tuple[list[tuple[float, float]], float]:
"""
Calculate the minimum bounding box (rotated rectangle) for a given contour.
@@ -659,7 +659,7 @@ class LicensePlateProcessingMixin:
return cv2.mean(bitmap[y1 : y2 + 1, x1 : x2 + 1], mask)[0]
@staticmethod
def _expand_box(points: List[Tuple[float, float]]) -> np.ndarray:
def _expand_box(points: list[tuple[float, float]]) -> np.ndarray:
"""
Expand a polygonal shape slightly by a factor determined by the area-to-perimeter ratio.
@@ -677,7 +677,7 @@ class LicensePlateProcessingMixin:
return expanded
def _filter_polygon(
self, points: List[np.ndarray], shape: Tuple[int, int]
self, points: list[np.ndarray], shape: tuple[int, int]
) -> np.ndarray:
"""
Filter a set of polygons to include only valid ones that fit within an image shape
@@ -839,8 +839,8 @@ class LicensePlateProcessingMixin:
return padded_image
def _process_classification_output(
self, images: List[np.ndarray], outputs: List[np.ndarray]
) -> Tuple[List[np.ndarray], List[Tuple[str, float]]]:
self, images: list[np.ndarray], outputs: list[np.ndarray]
) -> tuple[list[np.ndarray], list[tuple[str, float]]]:
"""
Process the classification model output by matching labels with confidence scores.
@@ -1095,8 +1095,8 @@ class LicensePlateProcessingMixin:
return None # No detection above the threshold
def _get_cluster_rep(
self, plates: List[dict]
) -> Tuple[str, float, List[float], int]:
self, plates: list[dict]
) -> tuple[str, float, list[float], int]:
"""
Cluster plate variants and select the representative from the best cluster.
"""
@@ -1704,7 +1704,7 @@ class CTCDecoder:
"""
self.characters = []
if character_dict_path and os.path.exists(character_dict_path):
with open(character_dict_path, "r", encoding="utf-8") as f:
with open(character_dict_path, encoding="utf-8") as f:
self.characters = (
["blank"] + [line.strip() for line in f if line.strip()] + [" "]
)
@@ -1812,8 +1812,8 @@ class CTCDecoder:
self.char_map = {i: char for i, char in enumerate(self.characters)}
def __call__(
self, outputs: List[np.ndarray]
) -> Tuple[List[str], List[List[float]]]:
self, outputs: list[np.ndarray]
) -> tuple[list[str], list[list[float]]]:
"""
Decode a batch of model outputs into character sequences and their confidence scores.
@@ -4,7 +4,7 @@ import logging
import os
import threading
import time
from typing import Any, Optional
from typing import Any
from peewee import DoesNotExist
@@ -142,7 +142,7 @@ class AudioTranscriptionPostProcessor(PostProcessorApi):
except Exception as e:
logger.error(f"Error in audio transcription post-processing: {e}")
def __transcribe_audio(self, audio_data: bytes) -> Optional[str]:
def __transcribe_audio(self, audio_data: bytes) -> str | None:
"""Transcribe WAV audio data using faster-whisper."""
if not self.recognizer:
logger.debug("Recognizer not initialized")
@@ -168,8 +168,9 @@ class AudioTranscriptionPostProcessor(PostProcessorApi):
return None
logger.debug(
"Detected language '%s' with probability %f"
% (info.language, info.language_probability)
"Detected language '%s' with probability %f",
info.language,
info.language_probability,
)
return text
@@ -102,10 +102,8 @@ class LicensePlatePostProcessor(LicensePlateProcessingMixin, PostProcessorApi):
Recordings.start_time,
)
.where(
(
(frame_time >= Recordings.start_time)
& (frame_time <= Recordings.end_time)
)
(frame_time >= Recordings.start_time)
& (frame_time <= Recordings.end_time)
)
.where(Recordings.camera == camera_name)
.order_by(Recordings.start_time.desc())
@@ -55,7 +55,7 @@ class SemanticTriggerProcessor(PostProcessorApi):
# load stats from disk
try:
with open(os.path.join(CONFIG_DIR, ".search_stats.json"), "r") as f:
with open(os.path.join(CONFIG_DIR, ".search_stats.json")) as f:
data = json.loads(f.read())
self.thumb_stats.from_dict(data["thumb_stats"])
self.desc_stats.from_dict(data["desc_stats"])
+2 -1
View File
@@ -4,9 +4,10 @@ import logging
import threading
from abc import ABC, abstractmethod
from collections import deque
from collections.abc import Callable
from concurrent.futures import Future
from queue import Empty, Full, Queue
from typing import Any, Callable
from typing import Any
import numpy as np
@@ -4,7 +4,7 @@ import logging
import os
import queue
import threading
from typing import Any, Optional
from typing import Any
import numpy as np
@@ -75,9 +75,7 @@ class AudioTranscriptionRealTimeProcessor(RealTimeProcessorApi):
f"Failed to initialize live streaming audio transcription: {e}"
)
def __process_audio_stream(
self, audio_data: np.ndarray
) -> Optional[tuple[str, bool]]:
def __process_audio_stream(self, audio_data: np.ndarray) -> tuple[str, bool] | None:
if (
self.model_runner.model is None
and self.config.audio_transcription.model_size == "small"
+2 -2
View File
@@ -7,7 +7,7 @@ import logging
import os
import shutil
from pathlib import Path
from typing import Any, Optional
from typing import Any
import cv2
import numpy as np
@@ -219,7 +219,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
logger.debug("Not processing due to hitting max rec attempts.")
return
face: Optional[dict[str, Any]] = None
face: dict[str, Any] | None = None
if self.requires_face_detection:
logger.debug("Running manual face detection.")
@@ -1053,7 +1053,7 @@ if __name__ == "__main__":
SAMPLING_RATE = 16000
duration = len(load_audio(audio_path)) / SAMPLING_RATE
logger.info("Audio duration is: %2.2f seconds" % duration)
logger.info("Audio duration is: %2.2f seconds", duration)
asr, online = asr_factory(args, logfile=logfile)
if args.vac: