diff --git a/_docs/02_tasks/todo/AZ-175_media_table_integration.md b/_docs/02_tasks/done/AZ-175_media_table_integration.md similarity index 100% rename from _docs/02_tasks/todo/AZ-175_media_table_integration.md rename to _docs/02_tasks/done/AZ-175_media_table_integration.md diff --git a/_docs/03_implementation/batch_06_report.md b/_docs/03_implementation/batch_06_report.md new file mode 100644 index 0000000..75a5c0b --- /dev/null +++ b/_docs/03_implementation/batch_06_report.md @@ -0,0 +1,20 @@ +# Batch Report + +**Batch**: 6 (Feature Batch 2) +**Tasks**: AZ-175 +**Date**: 2026-03-31 + +## Task Results + +| Task | Status | Files Modified | Tests | AC Coverage | Issues | +|------|--------|---------------|-------|-------------|--------| +| AZ-175_media_table_integration | Done | 4 files | 3/3 unit | 5/5 ACs covered | None | + +## AC Test Coverage: All covered +## Code Review Verdict: PASS_WITH_WARNINGS +## Auto-Fix Attempts: 0 +## Stuck Agents: None + +## Commit: 40be55a + +## Next Batch: AZ-176 (cleanup_obsolete_path_code) diff --git a/src/inference.pyx b/src/inference.pyx index c528084..de12085 100644 --- a/src/inference.pyx +++ b/src/inference.pyx @@ -1,7 +1,5 @@ import io -import mimetypes import threading -from pathlib import Path import av import cv2 @@ -144,42 +142,6 @@ cdef class Inference: self.ai_availability_status.set_status(AIAvailabilityEnum.ERROR, str(e)) self.is_building_engine = False - cdef bint is_video(self, str filepath): - mime_type, _ = mimetypes.guess_type(filepath) - return (mime_type and mime_type.startswith("video")) - - cpdef run_detect(self, dict config_dict, object annotation_callback, object status_callback=None): - cdef list[str] videos = [] - cdef list[str] images = [] - cdef object media_paths = config_dict.get("paths", []) - cdef AIRecognitionConfig ai_config = AIRecognitionConfig.from_dict(config_dict) - if ai_config is None: - raise Exception('ai recognition config is empty') - - self._annotation_callback = annotation_callback - self._status_callback = status_callback - self.stop_signal = False - self.init_ai() - if self.engine is None: - constants_inf.log( "AI engine not available. Conversion may be in progress. Skipping inference.") - return - - self.detection_counts = {} - for p in media_paths: - media_name = Path(p).stem.replace(" ", "") - self.detection_counts[media_name] = 0 - if self.is_video(p): - videos.append(p) - else: - images.append(p) - if len(images) > 0: - constants_inf.log(f'run inference on {" ".join(images)}...') - self._process_images(ai_config, images) - if len(videos) > 0: - for v in videos: - constants_inf.log(f'run inference on {v}...') - self._process_video(ai_config, v) - cpdef run_detect_image(self, bytes image_bytes, AIRecognitionConfig ai_config, str media_name, object annotation_callback, object status_callback=None): cdef list all_frame_data = [] @@ -302,60 +264,6 @@ cdef class Inference: constants_inf.log(f'Video done: {frame_count} frames read, {batch_count} batches processed') self.send_detection_status() - cdef _process_video(self, AIRecognitionConfig ai_config, str video_name): - cdef int frame_count = 0 - cdef int batch_count = 0 - cdef list batch_frames = [] - cdef list[long] batch_timestamps = [] - cdef int model_h, model_w - cdef str original_media_name - self._previous_annotation = None - - model_h, model_w = self.engine.get_input_shape() - original_media_name = Path(video_name).stem.replace(" ", "") - - v_input = cv2.VideoCapture(video_name) - if not v_input.isOpened(): - constants_inf.logerror(f'Failed to open video: {video_name}') - return - total_frames = int(v_input.get(cv2.CAP_PROP_FRAME_COUNT)) - if total_frames < 1: - total_frames = 1 - fps = v_input.get(cv2.CAP_PROP_FPS) - width = int(v_input.get(cv2.CAP_PROP_FRAME_WIDTH)) - height = int(v_input.get(cv2.CAP_PROP_FRAME_HEIGHT)) - constants_inf.log(f'Video: {total_frames} frames, {fps:.1f} fps, {width}x{height}') - - cdef int effective_batch = min(self.engine.max_batch_size, ai_config.model_batch_size) - if effective_batch < 1: - effective_batch = 1 - - while v_input.isOpened() and not self.stop_signal: - ret, frame = v_input.read() - if not ret or frame is None: - break - - frame_count += 1 - if frame_count % ai_config.frame_period_recognition == 0: - batch_frames.append(frame) - batch_timestamps.append(v_input.get(cv2.CAP_PROP_POS_MSEC)) - - if len(batch_frames) >= effective_batch: - batch_count += 1 - constants_inf.log(f'Video batch {batch_count}: frame {frame_count}/{total_frames} ({frame_count*100//total_frames}%)') - self._process_video_batch(ai_config, batch_frames, batch_timestamps, original_media_name, frame_count, total_frames, model_w) - batch_frames = [] - batch_timestamps = [] - - if batch_frames: - batch_count += 1 - constants_inf.log(f'Video batch {batch_count} (flush): {len(batch_frames)} remaining frames') - self._process_video_batch(ai_config, batch_frames, batch_timestamps, original_media_name, frame_count, total_frames, model_w) - - v_input.release() - constants_inf.log(f'Video done: {frame_count} frames read, {batch_count} batches processed') - self.send_detection_status() - cdef _process_video_batch(self, AIRecognitionConfig ai_config, list batch_frames, list batch_timestamps, str original_media_name, int frame_count, int total_frames, int model_w): @@ -424,18 +332,6 @@ cdef class Inference: self.on_annotation(annotation) self.send_detection_status() - cdef _process_images(self, AIRecognitionConfig ai_config, list[str] image_paths): - cdef list all_frame_data = [] - self._tile_detections = {} - for path in image_paths: - frame = cv2.imread(path) - if frame is None: - constants_inf.logerror(f'Failed to read image {path}') - continue - original_media_name = Path( path).stem.replace(" ", "") - self._append_image_frame_entries(ai_config, all_frame_data, frame, original_media_name) - self._finalize_image_inference(ai_config, all_frame_data) - cdef send_detection_status(self): if self._status_callback is not None: cb = self._status_callback