File size: 18,266 Bytes
e6a18b7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488

import logging
import traceback
from typing import Dict, List, Any

logger = logging.getLogger(__name__)

class RegionAnalyzer:
    """
    負責處理圖像區域劃分和基礎空間分析功能
    專注於3x3網格的區域劃分、物件分布分析和空間多樣性計算
    """

    def __init__(self):
        """初始化區域分析器,定義3x3網格區域"""
        try:
            # 定義圖像的3x3網格區域
            self.regions = {
                "top_left": (0, 0, 1/3, 1/3),
                "top_center": (1/3, 0, 2/3, 1/3),
                "top_right": (2/3, 0, 1, 1/3),
                "middle_left": (0, 1/3, 1/3, 2/3),
                "middle_center": (1/3, 1/3, 2/3, 2/3),
                "middle_right": (2/3, 1/3, 1, 2/3),
                "bottom_left": (0, 2/3, 1/3, 1),
                "bottom_center": (1/3, 2/3, 2/3, 1),
                "bottom_right": (2/3, 2/3, 1, 1)
            }
            logger.info("RegionAnalyzer initialized successfully with 3x3 grid regions")
        except Exception as e:
            logger.error(f"Failed to initialize RegionAnalyzer: {str(e)}")
            logger.error(traceback.format_exc())
            raise

    def determine_region(self, x: float, y: float) -> str:
        """
        判斷點位於哪個區域

        Args:
            x: 標準化x座標 (0-1)
            y: 標準化y座標 (0-1)

        Returns:
            區域名稱
        """
        try:
            for region_name, (x1, y1, x2, y2) in self.regions.items():
                if x1 <= x < x2 and y1 <= y < y2:
                    return region_name

            logger.warning(f"Point ({x}, {y}) does not fall into any defined region")
            return "unknown"

        except Exception as e:
            logger.error(f"Error determining region for point ({x}, {y}): {str(e)}")
            logger.error(traceback.format_exc())
            return "unknown"

    def get_spatial_description_phrase(self, region: str) -> str:
        """
        將region ID轉換為完整的空間描述短語,包含適當的介詞結構

        Args:
            region: 區域標識符(如 "middle_center", "top_left")

        Returns:
            str: 完整的空間描述短語,空值時返回空字串
        """
        try:
            # 處理空值或無效輸入
            if not region or region.strip() == "" or region == "unknown":
                return "within the visible area"

            # 清理region格式,移除底線
            clean_region = region.replace('_', ' ').strip().lower()

            # 根據區域位置生成自然語言描述
            region_mappings = {
                "top left": "in the upper left area",
                "top center": "in the upper area",
                "top right": "in the upper right area",
                "middle left": "on the left side",
                "middle center": "in the center",
                "center": "in the center",
                "middle right": "on the right side",
                "bottom left": "in the lower left area",
                "bottom center": "in the lower area",
                "bottom right": "in the lower right area"
            }

            # 直接映射匹配
            if clean_region in region_mappings:
                return region_mappings[clean_region]

            # 模糊匹配方位的處理
            if "top" in clean_region and "left" in clean_region:
                return "in the upper left area"
            elif "top" in clean_region and "right" in clean_region:
                return "in the upper right area"
            elif "bottom" in clean_region and "left" in clean_region:
                return "in the lower left area"
            elif "bottom" in clean_region and "right" in clean_region:
                return "in the lower right area"
            elif "top" in clean_region:
                return "in the upper area"
            elif "bottom" in clean_region:
                return "in the lower area"
            elif "left" in clean_region:
                return "on the left side"
            elif "right" in clean_region:
                return "on the right side"
            elif "center" in clean_region or "middle" in clean_region:
                return "in the center"
            else:
                # 對於無法辨識的區域,返回通用描述
                return f"in the {clean_region} area"

        except Exception as e:
            logger.warning(f"Error generating spatial description for region '{region}': {str(e)}")
            return ""

    def get_contextual_spatial_description(self, region: str, object_type: str = "") -> str:
        """
        根據物件類型提供更具情境的空間描述

        Args:
            region: 區域標識符
            object_type: 物件類型,用於優化描述語境

        Returns:
            str: 情境化的空間描述短語
        """
        try:
            # 獲取基礎空間描述
            base_description = self.get_spatial_description_phrase(region)

            if not base_description:
                return ""

            # 根據物件類型調整描述語境
            if object_type:
                object_type_lower = object_type.lower()

                # 對於辨識到人相關,用更自然的位置描述
                if "person" in object_type_lower or "people" in object_type_lower:
                    if "center" in base_description:
                        return "in the central area"
                    elif "upper" in base_description:
                        return "in the background"
                    elif "lower" in base_description:
                        return "in the foreground"

                # 對於車輛,強調道路位置
                elif any(vehicle in object_type_lower for vehicle in ["car", "vehicle", "truck", "bus"]):
                    if "left" in base_description:
                        return "on the left side of the scene"
                    elif "right" in base_description:
                        return "on the right side of the scene"
                    elif "center" in base_description:
                        return "in the central area"

                # 對於交通設施,使用更具體的位置描述
                elif "traffic" in object_type_lower:
                    if "upper" in base_description:
                        return "positioned in the upper portion"
                    elif "center" in base_description:
                        return "centrally positioned"
                    else:
                        return base_description.replace("in the", "positioned in the")

            return base_description

        except Exception as e:
            logger.warning(f"Error generating contextual spatial description: {str(e)}")
            return self.get_spatial_description_phrase(region)


    def validate_region_input(self, region: str) -> bool:
        """
        驗證region輸入是否有效

        Args:
            region: 待驗證的區域標識符

        Returns:
            bool: 是否為有效的region
        """
        try:
            if not region or region.strip() == "":
                return False

            # 清理並檢查是否為已知區域
            clean_region = region.replace('_', ' ').strip().lower()

            known_regions = [
                "top left", "top center", "top right",
                "middle left", "middle center", "middle right",
                "bottom left", "bottom center", "bottom right",
                "center", "unknown"
            ]

            # 直接匹配或包含關鍵詞匹配
            if clean_region in known_regions:
                return True

            # 檢查是否包含有效的位置關鍵詞組合
            position_keywords = ["top", "bottom", "left", "right", "center", "middle"]
            has_valid_keyword = any(keyword in clean_region for keyword in position_keywords)

            return has_valid_keyword

        except Exception as e:
            logger.warning(f"Error validating region input '{region}': {str(e)}")
            return False

    def get_enhanced_directional_description(self, region: str) -> str:
        """
        增強版的方位描述生成,提供更豐富的方位資訊
        擴展原有的get_directional_description方法功能

        Args:
            region: 區域名稱

        Returns:
            str: 增強的方位描述字串
        """
        try:
            if not self.validate_region_input(region):
                return "central"

            region_lower = region.replace('_', ' ').strip().lower()

            # 用比較準確的方位映射
            direction_mappings = {
                "top left": "northwest",
                "top center": "north",
                "top right": "northeast",
                "middle left": "west",
                "middle center": "central",
                "center": "central",
                "middle right": "east",
                "bottom left": "southwest",
                "bottom center": "south",
                "bottom right": "southeast"
            }

            if region_lower in direction_mappings:
                return direction_mappings[region_lower]

            # 模糊匹配邏輯保持與原方法相同
            if "top" in region_lower and "left" in region_lower:
                return "northwest"
            elif "top" in region_lower and "right" in region_lower:
                return "northeast"
            elif "bottom" in region_lower and "left" in region_lower:
                return "southwest"
            elif "bottom" in region_lower and "right" in region_lower:
                return "southeast"
            elif "top" in region_lower:
                return "north"
            elif "bottom" in region_lower:
                return "south"
            elif "left" in region_lower:
                return "west"
            elif "right" in region_lower:
                return "east"
            else:
                return "central"

        except Exception as e:
            logger.error(f"Error getting enhanced directional description for region '{region}': {str(e)}")
            return "central"

    def analyze_regions(self, detected_objects: List[Dict]) -> Dict:
        """
        分析物件在各區域的分布情況

        Args:
            detected_objects: 包含位置資訊的檢測物件列表

        Returns:
            包含區域分析結果的字典
        """
        try:
            if not detected_objects:
                logger.warning("No detected objects provided for region analysis")
                return {
                    "counts": {region: 0 for region in self.regions.keys()},
                    "main_focus": [],
                    "objects_by_region": {region: [] for region in self.regions.keys()}
                }

            # 計算每個區域的物件數量
            region_counts = {region: 0 for region in self.regions.keys()}
            region_objects = {region: [] for region in self.regions.keys()}

            for obj in detected_objects:
                try:
                    region = obj.get("region", "unknown")
                    if region in region_counts:
                        region_counts[region] += 1
                        region_objects[region].append({
                            "class_id": obj.get("class_id"),
                            "class_name": obj.get("class_name")
                        })
                    else:
                        logger.warning(f"Unknown region '{region}' found in object")

                except Exception as e:
                    logger.error(f"Error processing object in region analysis: {str(e)}")
                    continue

            # 確定主要焦點區域(按物件數量排序的前1-2個區域)
            sorted_regions = sorted(region_counts.items(), key=lambda x: x[1], reverse=True)
            main_regions = [region for region, count in sorted_regions if count > 0][:2]

            result = {
                "counts": region_counts,
                "main_focus": main_regions,
                "objects_by_region": region_objects
            }

            logger.info(f"Region analysis completed. Main focus areas: {main_regions}")
            return result

        except Exception as e:
            logger.error(f"Error in region analysis: {str(e)}")
            logger.error(traceback.format_exc())
            # 返回空的結果結構而不是拋出異常
            return {
                "counts": {region: 0 for region in self.regions.keys()},
                "main_focus": [],
                "objects_by_region": {region: [] for region in self.regions.keys()}
            }

    def create_distribution_map(self, detected_objects: List[Dict]) -> Dict:
        """
        創建物件在各區域分布的詳細地圖,用於空間分析

        Args:
            detected_objects: 檢測到的物件列表

        Returns:
            包含各區域分布詳情的字典
        """
        try:
            if not detected_objects:
                logger.warning("No detected objects provided for distribution map creation")
                return self._get_empty_distribution_map()

            distribution = {}

            # 初始化所有區域
            for region in self.regions.keys():
                distribution[region] = {
                    "total": 0,
                    "objects": {},
                    "density": 0
                }

            # 填充分布資料
            for obj in detected_objects:
                try:
                    region = obj.get("region", "unknown")
                    class_id = obj.get("class_id")
                    class_name = obj.get("class_name", "unknown")

                    if region not in distribution:
                        logger.warning(f"Unknown region '{region}' found, skipping object")
                        continue

                    distribution[region]["total"] += 1

                    if class_id not in distribution[region]["objects"]:
                        distribution[region]["objects"][class_id] = {
                            "name": class_name,
                            "count": 0,
                            "positions": []
                        }

                    distribution[region]["objects"][class_id]["count"] += 1

                    # 儲存位置資訊用於空間關係分析
                    normalized_center = obj.get("normalized_center")
                    if normalized_center:
                        distribution[region]["objects"][class_id]["positions"].append(normalized_center)

                except Exception as e:
                    logger.error(f"Error processing object in distribution map: {str(e)}")
                    continue

            # 計算每個區域的物件密度
            for region, data in distribution.items():
                # 假設所有區域在網格中大小相等
                data["density"] = data["total"] / 1

            logger.info("Distribution map created successfully")
            return distribution

        except Exception as e:
            logger.error(f"Error creating distribution map: {str(e)}")
            logger.error(traceback.format_exc())
            return self._get_empty_distribution_map()

    def calculate_spatial_diversity(self, detected_objects: List[Dict]) -> float:
        """
        計算物件空間分布的多樣性
        評估物件是否分散在不同區域,避免所有物件集中在單一區域

        Args:
            detected_objects: 檢測到的物件列表

        Returns:
            空間多樣性評分 (0.0-1.0)
        """
        try:
            if not detected_objects:
                logger.warning("No detected objects provided for spatial diversity calculation")
                return 0.0

            regions = set()
            for obj in detected_objects:
                region = obj.get("region", "center")
                regions.add(region)

            unique_regions = len(regions)
            diversity_score = min(unique_regions / 2.0, 1.0)

            logger.info(f"Spatial diversity calculated: {diversity_score:.3f} (regions: {unique_regions})")
            return diversity_score

        except Exception as e:
            logger.error(f"Error calculating spatial diversity: {str(e)}")
            logger.error(traceback.format_exc())
            return 0.0

    def get_directional_description(self, region: str) -> str:
        """
        將區域名稱轉換為方位描述(東西南北)

        Args:
            region: 區域名稱

        Returns:
            方位描述字串
        """
        try:
            region_lower = region.lower()

            if "top" in region_lower and "left" in region_lower:
                return "northwest"
            elif "top" in region_lower and "right" in region_lower:
                return "northeast"
            elif "bottom" in region_lower and "left" in region_lower:
                return "southwest"
            elif "bottom" in region_lower and "right" in region_lower:
                return "southeast"
            elif "top" in region_lower:
                return "north"
            elif "bottom" in region_lower:
                return "south"
            elif "left" in region_lower:
                return "west"
            elif "right" in region_lower:
                return "east"
            else:
                return "central"

        except Exception as e:
            logger.error(f"Error getting directional description for region '{region}': {str(e)}")
            return "central"

    def _get_empty_distribution_map(self) -> Dict:
        """
        返回空的分布地圖結構

        Returns:
            空的分布地圖字典
        """
        distribution = {}
        for region in self.regions.keys():
            distribution[region] = {
                "total": 0,
                "objects": {},
                "density": 0
            }
        return distribution