Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions node/OverlayNode/draw_util/draw_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,10 +673,19 @@ def draw_multi_object_tracking_info(
# トラックID、スコア
score = '%.2f' % score
text = 'TID:%s(%s)' % (str(int(track_id_dict[id])), str(score))

# Position text above bbox if there's space, otherwise inside/below
# Use margin of 5 pixels to avoid text being cut off at image edge
margin = 5
tid_y_pos = y1 - vertical_offset_1
if tid_y_pos < margin:
# Not enough space above, put it inside the box
tid_y_pos = y1 + vertical_offset_1

image = cv2.putText(
image,
text,
(x1, y1 - vertical_offset_1),
(x1, tid_y_pos),
cv2.FONT_HERSHEY_SIMPLEX,
font_scale,
color,
Expand All @@ -685,10 +694,17 @@ def draw_multi_object_tracking_info(

# クラスID
text = 'CID:%s(%s)' % (str(int(class_id)), class_names[int(class_id)])

# Position text above bbox if there's space, otherwise inside/below
cid_y_pos = y1 - vertical_offset_2
if cid_y_pos < margin:
# Not enough space above, put it below TID text
cid_y_pos = y1 + vertical_offset_1 + vertical_offset_2

image = cv2.putText(
image,
text,
(x1, y1 - vertical_offset_2),
(x1, cid_y_pos),
cv2.FONT_HERSHEY_SIMPLEX,
font_scale,
color,
Expand Down
20 changes: 18 additions & 2 deletions node/basenode.py
Original file line number Diff line number Diff line change
Expand Up @@ -959,10 +959,19 @@ def draw_multi_object_tracking_info(

score = "%.2f" % score
text = "TID:%s(%s)" % (str(int(track_id_dict[id])), str(score))

# Position text above bbox if there's space, otherwise inside/below
# Use margin of 5 pixels to avoid text being cut off at image edge
margin = 5
tid_y_pos = y1 - vertical_offset_1
if tid_y_pos < margin:
# Not enough space above, put it inside the box
tid_y_pos = y1 + vertical_offset_1

image = cv2.putText(
image,
text,
(x1, y1 - vertical_offset_1),
(x1, tid_y_pos),
cv2.FONT_HERSHEY_SIMPLEX,
font_scale,
color,
Expand All @@ -971,10 +980,17 @@ def draw_multi_object_tracking_info(

class_name = self.get_class_name(class_id, class_names)
text = "CID:%s(%s)" % (str(int(class_id)), class_name)

# Position text above bbox if there's space, otherwise inside/below
cid_y_pos = y1 - vertical_offset_2
if cid_y_pos < margin:
# Not enough space above, put it below TID text
cid_y_pos = y1 + vertical_offset_1 + vertical_offset_2

image = cv2.putText(
image,
text,
(x1, y1 - vertical_offset_2),
(x1, cid_y_pos),
cv2.FONT_HERSHEY_SIMPLEX,
font_scale,
color,
Expand Down
266 changes: 266 additions & 0 deletions tests/test_mot_label_visibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Test to verify that MOT tracking labels are always visible,
even when bounding boxes are at the top of the image.

This is a simplified unit test that tests the positioning logic directly.
"""
import unittest
import sys
import os

# Add parent directory to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))


class TestMOTLabelPositioningLogic(unittest.TestCase):
"""Test the label positioning logic to ensure visibility"""

def test_label_positioning_at_top_of_image(self):
"""Test that labels are repositioned when bbox is at the top"""
# Simulate the positioning logic from the fix
image_height = 480
font_scale = max(0.3, min(1.0, (image_height / 720.0) * 0.5))
vertical_offset_1 = int(36 * (font_scale / 0.5))
vertical_offset_2 = int(12 * (font_scale / 0.5))
margin = 5

# Test case 1: Bbox at y1 = 0 (very top)
y1 = 0
tid_y_pos = y1 - vertical_offset_1

# Should be negative, so we reposition inside
self.assertLess(tid_y_pos, 0, "Original position should be negative")

# Apply fix logic
if tid_y_pos < margin:
tid_y_pos = y1 + vertical_offset_1

self.assertGreater(tid_y_pos, 0, "Fixed TID position should be positive")
self.assertLess(tid_y_pos, image_height, "Fixed TID position should be within image")

# Test CID position
cid_y_pos = y1 - vertical_offset_2
self.assertLess(cid_y_pos, 0, "Original CID position should be negative")

if cid_y_pos < margin:
cid_y_pos = y1 + vertical_offset_1 + vertical_offset_2

self.assertGreater(cid_y_pos, 0, "Fixed CID position should be positive")
self.assertLess(cid_y_pos, image_height, "Fixed CID position should be within image")
self.assertGreater(cid_y_pos, tid_y_pos, "CID should be below TID when repositioned")

def test_label_positioning_with_small_y1(self):
"""Test label positioning when bbox is close to top (y1 < offset)"""
image_height = 720
font_scale = max(0.3, min(1.0, (image_height / 720.0) * 0.5))
vertical_offset_1 = int(36 * (font_scale / 0.5))
vertical_offset_2 = int(12 * (font_scale / 0.5))
margin = 5

# Small y1 value
y1 = 10
tid_y_pos = y1 - vertical_offset_1

# Should be negative since y1 < vertical_offset_1
self.assertLess(tid_y_pos, 0, "Original position should be negative")

# Apply fix
if tid_y_pos < margin:
tid_y_pos = y1 + vertical_offset_1

self.assertGreater(tid_y_pos, 0, "Fixed position should be positive")
self.assertEqual(tid_y_pos, y1 + vertical_offset_1, "Should be positioned inside bbox")

def test_label_positioning_with_sufficient_space(self):
"""Test that labels stay above bbox when there's sufficient space"""
image_height = 720
font_scale = max(0.3, min(1.0, (image_height / 720.0) * 0.5))
vertical_offset_1 = int(36 * (font_scale / 0.5))
vertical_offset_2 = int(12 * (font_scale / 0.5))
margin = 5

# Bbox with enough space above (y1 = 100)
y1 = 100
tid_y_pos = y1 - vertical_offset_1

# Should be positive (no fix needed)
self.assertGreater(tid_y_pos, 0, "Position should be positive with sufficient space")

# No fix should be applied
if tid_y_pos < margin:
tid_y_pos = y1 + vertical_offset_1

# Should remain above the bbox
self.assertEqual(tid_y_pos, y1 - vertical_offset_1, "Should stay above bbox")

# Same for CID
cid_y_pos = y1 - vertical_offset_2
self.assertGreater(cid_y_pos, 0, "CID position should be positive")

if cid_y_pos < margin:
cid_y_pos = y1 + vertical_offset_1 + vertical_offset_2

self.assertEqual(cid_y_pos, y1 - vertical_offset_2, "CID should stay above bbox")

def test_label_positioning_at_exact_threshold(self):
"""Test label positioning when y1 equals vertical_offset"""
image_height = 720
font_scale = max(0.3, min(1.0, (image_height / 720.0) * 0.5))
vertical_offset_1 = int(36 * (font_scale / 0.5))
margin = 5

# y1 exactly at threshold
y1 = vertical_offset_1
tid_y_pos = y1 - vertical_offset_1

# Should be exactly 0
self.assertEqual(tid_y_pos, 0, "Position should be 0 at threshold")

# Fix should be applied since 0 < margin
if tid_y_pos < margin:
tid_y_pos = y1 + vertical_offset_1

# Position should now be inside the box
self.assertEqual(tid_y_pos, y1 + vertical_offset_1,
"Position should be repositioned inside box when at margin threshold")

def test_multiple_image_sizes(self):
"""Test label positioning logic across different image sizes"""
test_heights = [360, 480, 720, 1080, 1440]
margin = 5

for image_height in test_heights:
font_scale = max(0.3, min(1.0, (image_height / 720.0) * 0.5))
vertical_offset_1 = int(36 * (font_scale / 0.5))
vertical_offset_2 = int(12 * (font_scale / 0.5))

# Test at top (y1 = 0)
y1 = 0
tid_y_pos = y1 - vertical_offset_1
cid_y_pos = y1 - vertical_offset_2

# Apply fix
if tid_y_pos < margin:
tid_y_pos = y1 + vertical_offset_1
if cid_y_pos < margin:
cid_y_pos = y1 + vertical_offset_1 + vertical_offset_2

# Verify fix works for all sizes
self.assertGreater(tid_y_pos, 0,
f"TID should be visible for height {image_height}")
self.assertGreater(cid_y_pos, 0,
f"CID should be visible for height {image_height}")
self.assertLess(tid_y_pos, image_height,
f"TID should be within image for height {image_height}")
self.assertLess(cid_y_pos, image_height,
f"CID should be within image for height {image_height}")

def test_label_ordering_when_repositioned(self):
"""Test that TID and CID maintain proper ordering when repositioned"""
image_height = 720
font_scale = max(0.3, min(1.0, (image_height / 720.0) * 0.5))
vertical_offset_1 = int(36 * (font_scale / 0.5))
vertical_offset_2 = int(12 * (font_scale / 0.5))
margin = 5

# Bbox at top where both labels need repositioning
y1 = 0

# Original positions (both negative)
tid_y_pos = y1 - vertical_offset_1
cid_y_pos = y1 - vertical_offset_2

# Apply fix
if tid_y_pos < margin:
tid_y_pos = y1 + vertical_offset_1
if cid_y_pos < margin:
cid_y_pos = y1 + vertical_offset_1 + vertical_offset_2

# When repositioned inside, CID should be below TID
self.assertGreater(cid_y_pos, tid_y_pos,
"When repositioned, CID should be below TID")

# The spacing should be approximately vertical_offset_2
spacing = cid_y_pos - tid_y_pos
self.assertAlmostEqual(spacing, vertical_offset_2, delta=1,
msg="Spacing between TID and CID should be maintained")


class TestImplementationInFiles(unittest.TestCase):
"""Test that the implementation is present in the actual files"""

def test_basenode_has_visibility_fix(self):
"""Test that basenode.py has the visibility fix"""
basenode_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
'node',
'basenode.py'
)

with open(basenode_path, 'r') as f:
content = f.read()

# Check for the fix logic
self.assertIn('margin = 5', content,
"Should define margin for edge detection")
self.assertIn('tid_y_pos = y1 - vertical_offset_1', content,
"Should calculate tid_y_pos")
self.assertIn('if tid_y_pos < margin:', content,
"Should check if tid_y_pos is less than margin")
self.assertIn('tid_y_pos = y1 + vertical_offset_1', content,
"Should reposition tid_y_pos when too close to edge")

self.assertIn('cid_y_pos = y1 - vertical_offset_2', content,
"Should calculate cid_y_pos")
self.assertIn('if cid_y_pos < margin:', content,
"Should check if cid_y_pos is less than margin")
self.assertIn('cid_y_pos = y1 + vertical_offset_1 + vertical_offset_2', content,
"Should reposition cid_y_pos when too close to edge")

# Check that positioning uses the new variables
self.assertIn('(x1, tid_y_pos)', content,
"Should use tid_y_pos for positioning")
self.assertIn('(x1, cid_y_pos)', content,
"Should use cid_y_pos for positioning")

def test_draw_util_has_visibility_fix(self):
"""Test that draw_util.py has the visibility fix"""
draw_util_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
'node',
'OverlayNode',
'draw_util',
'draw_util.py'
)

with open(draw_util_path, 'r') as f:
content = f.read()

# Check for the fix logic
self.assertIn('margin = 5', content,
"Should define margin for edge detection")
self.assertIn('tid_y_pos = y1 - vertical_offset_1', content,
"Should calculate tid_y_pos")
self.assertIn('if tid_y_pos < margin:', content,
"Should check if tid_y_pos is less than margin")
self.assertIn('tid_y_pos = y1 + vertical_offset_1', content,
"Should reposition tid_y_pos when too close to edge")

self.assertIn('cid_y_pos = y1 - vertical_offset_2', content,
"Should calculate cid_y_pos")
self.assertIn('if cid_y_pos < margin:', content,
"Should check if cid_y_pos is less than margin")
self.assertIn('cid_y_pos = y1 + vertical_offset_1 + vertical_offset_2', content,
"Should reposition cid_y_pos when too close to edge")

# Check that positioning uses the new variables
self.assertIn('(x1, tid_y_pos)', content,
"Should use tid_y_pos for positioning")
self.assertIn('(x1, cid_y_pos)', content,
"Should use cid_y_pos for positioning")


if __name__ == '__main__':
unittest.main()