Browse Source

0925 commit

ZihaoYANG 8 tháng trước cách đây
mục cha
commit
65ed4c2409
100 tập tin đã thay đổi với 10561 bổ sung1 xóa
  1. 57 0
      ACC_Trigger_0920.py
  2. 52 0
      LKA_Trigger.py
  3. 1104 0
      close_beta_test/config_test/builtin+acc.json
  4. 0 0
      close_beta_test/config_test/builtin.json
  5. 5 1
      comfort.py
  6. 19 0
      common.py
  7. 3 0
      compliance.py
  8. 0 0
      config/config_voyah_0912.json
  9. 21 0
      custom/voyah/ACC/cicv_acc_01_delay_time_cruise.json
  10. 196 0
      custom/voyah/ACC/cicv_acc_01_delay_time_cruise.py
  11. 21 0
      custom/voyah/ACC/cicv_acc_02_rise_time_cruise.json
  12. 226 0
      custom/voyah/ACC/cicv_acc_02_rise_time_cruise.py
  13. 21 0
      custom/voyah/ACC/cicv_acc_03_peak_time_cruise.json
  14. 153 0
      custom/voyah/ACC/cicv_acc_03_peak_time_cruise.py
  15. 21 0
      custom/voyah/ACC/cicv_acc_04_overshoot_cruise.json
  16. 208 0
      custom/voyah/ACC/cicv_acc_04_overshoot_cruise.py
  17. 21 0
      custom/voyah/ACC/cicv_acc_05_steady_error_cruise.json
  18. 205 0
      custom/voyah/ACC/cicv_acc_05_steady_error_cruise.py
  19. 21 0
      custom/voyah/ACC/cicv_acc_06_delay_time_THW.json
  20. 191 0
      custom/voyah/ACC/cicv_acc_06_delay_time_THW.py
  21. 21 0
      custom/voyah/ACC/cicv_acc_07_rise_time_THW.json
  22. 231 0
      custom/voyah/ACC/cicv_acc_07_rise_time_THW.py
  23. 21 0
      custom/voyah/ACC/cicv_acc_08_peak_time_THW.json
  24. 148 0
      custom/voyah/ACC/cicv_acc_08_peak_time_THW.py
  25. 21 0
      custom/voyah/ACC/cicv_acc_09_overshoot_THW.json
  26. 201 0
      custom/voyah/ACC/cicv_acc_09_overshoot_THW.py
  27. 21 0
      custom/voyah/ACC/cicv_acc_10_steady_error_THW.json
  28. 201 0
      custom/voyah/ACC/cicv_acc_10_steady_error_THW.py
  29. 21 0
      custom/voyah/ACC/cicv_acc_11_reasonable_acceleration_percentage.json
  30. 129 0
      custom/voyah/ACC/cicv_acc_11_reasonable_acceleration_percentage.py
  31. 21 0
      custom/voyah/ACC/cicv_acc_12_reasonable_acceleration_change_rate_percentage.json
  32. 133 0
      custom/voyah/ACC/cicv_acc_12_reasonable_acceleration_change_rate_percentage.py
  33. 22 0
      custom/voyah/ICA/cicv_ica_lateral_control01_distance_nearby_lane.json
  34. 161 0
      custom/voyah/ICA/cicv_ica_lateral_control01_distance_nearby_lane.py
  35. 22 0
      custom/voyah/ICA/cicv_ica_lateral_control02_lateral_offset.json
  36. 164 0
      custom/voyah/ICA/cicv_ica_lateral_control02_lateral_offset.py
  37. 22 0
      custom/voyah/ICA/cicv_ica_lateral_control03_relative_center_distance_expectation.json
  38. 158 0
      custom/voyah/ICA/cicv_ica_lateral_control03_relative_center_distance_expectation.py
  39. 22 0
      custom/voyah/ICA/cicv_ica_lateral_control04_relative_center_distance_standard_deviation.json
  40. 155 0
      custom/voyah/ICA/cicv_ica_lateral_control04_relative_center_distance_standard_deviation.py
  41. 22 0
      custom/voyah/ICA/cicv_ica_lateral_control05_absolute_center_distance_expectation.json
  42. 161 0
      custom/voyah/ICA/cicv_ica_lateral_control05_absolute_center_distance_expectation.py
  43. 22 0
      custom/voyah/ICA/cicv_ica_lateral_control06_absolute_center_distance_standard_deviation.json
  44. 158 0
      custom/voyah/ICA/cicv_ica_lateral_control06_absolute_center_distance_standard_deviation.py
  45. 22 0
      custom/voyah/ICA/cicv_ica_lateral_control07_center_distance_max.json
  46. 160 0
      custom/voyah/ICA/cicv_ica_lateral_control07_center_distance_max.py
  47. 22 0
      custom/voyah/ICA/cicv_ica_lateral_control08_center_distance_min.json
  48. 160 0
      custom/voyah/ICA/cicv_ica_lateral_control08_center_distance_min.py
  49. 22 0
      custom/voyah/ICA/cicv_ica_lateral_control09_absolute_position_oscillation_frequency.json
  50. 179 0
      custom/voyah/ICA/cicv_ica_lateral_control09_absolute_position_oscillation_frequency.py
  51. 22 0
      custom/voyah/ICA/cicv_ica_lateral_control10_absolute_position_oscillation_difference.json
  52. 211 0
      custom/voyah/ICA/cicv_ica_lateral_control10_absolute_position_oscillation_difference.py
  53. 22 0
      custom/voyah/ICA/cicv_ica_lateral_control11_heading_deviation_max.json
  54. 164 0
      custom/voyah/ICA/cicv_ica_lateral_control11_heading_deviation_max.py
  55. 22 0
      custom/voyah/ICA/cicv_ica_lateral_control12_relative_position_oscillation_frequency.json
  56. 236 0
      custom/voyah/ICA/cicv_ica_lateral_control12_relative_position_oscillation_frequency.py
  57. 22 0
      custom/voyah/ICA/cicv_ica_lateral_control13_relative_position_oscillation_difference.json
  58. 186 0
      custom/voyah/ICA/cicv_ica_lateral_control13_relative_position_oscillation_difference.py
  59. 21 0
      custom/voyah/ICA/cicv_ica_longitudinal_control01_delay_time_cruise.json
  60. 192 0
      custom/voyah/ICA/cicv_ica_longitudinal_control01_delay_time_cruise.py
  61. 21 0
      custom/voyah/ICA/cicv_ica_longitudinal_control02_rise_time_cruise.json
  62. 222 0
      custom/voyah/ICA/cicv_ica_longitudinal_control02_rise_time_cruise.py
  63. 21 0
      custom/voyah/ICA/cicv_ica_longitudinal_control03_peak_time_cruise.json
  64. 149 0
      custom/voyah/ICA/cicv_ica_longitudinal_control03_peak_time_cruise.py
  65. 21 0
      custom/voyah/ICA/cicv_ica_longitudinal_control04_overshoot_cruise.json
  66. 205 0
      custom/voyah/ICA/cicv_ica_longitudinal_control04_overshoot_cruise.py
  67. 21 0
      custom/voyah/ICA/cicv_ica_longitudinal_control05_steady_error_cruise.json
  68. 202 0
      custom/voyah/ICA/cicv_ica_longitudinal_control05_steady_error_cruise.py
  69. 21 0
      custom/voyah/ICA/cicv_ica_longitudinal_control06_delay_time_THW.json
  70. 191 0
      custom/voyah/ICA/cicv_ica_longitudinal_control06_delay_time_THW.py
  71. 21 0
      custom/voyah/ICA/cicv_ica_longitudinal_control07_rise_time_THW.json
  72. 231 0
      custom/voyah/ICA/cicv_ica_longitudinal_control07_rise_time_THW.py
  73. 21 0
      custom/voyah/ICA/cicv_ica_longitudinal_control08_peak_time_THW.json
  74. 148 0
      custom/voyah/ICA/cicv_ica_longitudinal_control08_peak_time_THW.py
  75. 21 0
      custom/voyah/ICA/cicv_ica_longitudinal_control09_overshoot_THW.json
  76. 201 0
      custom/voyah/ICA/cicv_ica_longitudinal_control09_overshoot_THW.py
  77. 21 0
      custom/voyah/ICA/cicv_ica_longitudinal_control10_steady_error_THW.json
  78. 201 0
      custom/voyah/ICA/cicv_ica_longitudinal_control10_steady_error_THW.py
  79. 21 0
      custom/voyah/ICA/cicv_ica_longitudinal_control11_reasonable_acceleration_percentage.json
  80. 129 0
      custom/voyah/ICA/cicv_ica_longitudinal_control11_reasonable_acceleration_percentage.py
  81. 21 0
      custom/voyah/ICA/cicv_ica_longitudinal_control12_reasonable_acceleration_change_rate_percentage.json
  82. 133 0
      custom/voyah/ICA/cicv_ica_longitudinal_control12_reasonable_acceleration_change_rate_percentage.py
  83. 0 0
      custom/voyah/LKA/cicv_LKA_01_distance_nearby_lane.json
  84. 170 0
      custom/voyah/LKA/cicv_LKA_01_distance_nearby_lane.py
  85. 24 0
      custom/voyah/LKA/cicv_LKA_02_lateral_offset.json
  86. 155 0
      custom/voyah/LKA/cicv_LKA_02_lateral_offset.py
  87. 24 0
      custom/voyah/LKA/cicv_LKA_03_heading_deviation_max.json
  88. 160 0
      custom/voyah/LKA/cicv_LKA_03_heading_deviation_max.py
  89. 24 0
      custom/voyah/LKA/cicv_LKA_04_relative_position_oscillation_frequency.json
  90. 226 0
      custom/voyah/LKA/cicv_LKA_04_relative_position_oscillation_frequency.py
  91. 24 0
      custom/voyah/LKA/cicv_LKA_05_relative_position_oscillation_difference.json
  92. 194 0
      custom/voyah/LKA/cicv_LKA_05_relative_position_oscillation_difference.py
  93. 24 0
      custom/voyah/LKA/cicv_LKA_06_absolute_center_distance_expectation.json
  94. 165 0
      custom/voyah/LKA/cicv_LKA_06_absolute_center_distance_expectation.py
  95. 24 0
      custom/voyah/LKA/cicv_LKA_07_absolute_center_distance_standard_deviation.json
  96. 162 0
      custom/voyah/LKA/cicv_LKA_07_absolute_center_distance_standard_deviation.py
  97. 24 0
      custom/voyah/LKA/cicv_LKA_08_fixed_driving_direction_TLC.json
  98. 206 0
      custom/voyah/LKA/cicv_LKA_08_fixed_driving_direction_TLC.py
  99. 24 0
      custom/voyah/LKA/cicv_LKA_09_fixed_steering_wheel_angle_TLC.json
  100. 222 0
      custom/voyah/LKA/cicv_LKA_09_fixed_steering_wheel_angle_TLC.py

+ 57 - 0
ACC_Trigger_0920.py

@@ -0,0 +1,57 @@
+import numpy as np
+import pandas as pd
+import warnings
+warnings.filterwarnings("ignore", category=FutureWarning, module='pandas')
+pd.set_option('future.no_silent_downcasting', True)
+
+SHUT_ACC_STATUS = 0
+ACC_STATUS = 3
+
+EGO_ID = 1
+OBJ_ID = 2
+
+class ACC_Trigger():
+    def __init__(self, df_vehState):
+        self.df_vehState = df_vehState
+
+    def find_start_end_time(self, change_speed_time_list):
+        start_end_time_list = []
+        # 初始化一个变量来跟踪当前序列的开始值
+        # 和一个变量来跟踪当前序列的长度
+        start_of_sequence = None
+        sequence_length = 0
+
+        # 遍历sim_times列表(从第二个元素开始比较)
+        for i in range(1, len(change_speed_time_list)):
+            # 检查当前元素与上一个元素的差是否接近0.04(考虑浮点数精度)
+            if abs(change_speed_time_list[i] - change_speed_time_list[i - 1] - 0.01) < 0.04:
+                # 如果这是序列的开始,则记录开始值
+                if start_of_sequence is None:
+                    start_of_sequence = change_speed_time_list[i - 1]
+                    # 增加序列长度
+                sequence_length += 1
+            else:
+                # 如果序列中断且长度足够(例如,至少为2),则添加序列到结果列表中
+                if start_of_sequence is not None and sequence_length >= 0:
+                    start_end_time_list.append([start_of_sequence, change_speed_time_list[i - 1]])
+                    # 重置开始值和序列长度
+                start_of_sequence = None
+                sequence_length = 0
+
+            # 处理最后一个序列(如果存在且长度足够)
+        if start_of_sequence is not None and sequence_length >= 2:
+            start_end_time_list.append([start_of_sequence, change_speed_time_list[-1]])
+        return start_end_time_list
+
+    def ACC_active_time_statistics(self):
+        start_end_time_dict = {}
+        ACC_time = self.df_vehState[self.df_vehState['ACC_status'] == ACC_STATUS]['simTime'].tolist()
+        ACC_start_end_time_list = self.find_start_end_time(ACC_time)
+        start_end_time_dict['ACC_status_active_time'] = ACC_start_end_time_list
+        return start_end_time_dict
+
+if __name__ == "__main__":
+    df_vehState = pd.read_csv(r"D:\Cicv\Lantu\数据\ACC_04_Straight_Front_vehicle_biased_driving\data\VehState.csv")
+    ACC = ACC_Trigger(df_vehState)
+    start_end_time_dict = ACC.ACC_active_time_statistics()
+    print(start_end_time_dict)

+ 52 - 0
LKA_Trigger.py

@@ -0,0 +1,52 @@
+import numpy as np
+import pandas as pd
+
+STANDBY = 0
+RIGHT_WARNING = 2
+
+class LKA_Trigger():
+    def __init__(self, df_vehState):
+        self.df_vehState = df_vehState
+
+    def lane_keep_warning_status(self, left_or_right):
+        '''
+
+        :return:
+        start_vehstate: 状态机开始的时间,以列表形式存储
+        close_vehstate:状态机结束的时间,以列表形式存储
+        '''
+        groups = (self.df_vehState['LKA_status'] != self.df_vehState['LKA_status'].shift()).cumsum()
+        LKA_left_groups = self.df_vehState[self.df_vehState['LKA_status'] == left_or_right].groupby(groups)
+        # 初始化结果列表
+        result_times = []
+
+        # 使用enumerate来跟踪分组的顺序(模拟全局索引的奇偶性)
+        for idx, (name, group) in enumerate(LKA_left_groups, start=1):
+            result_time = []
+            # 获取片段的simTime值
+            sim_times = group['simTime'].tolist()
+            # 根据分组的奇偶性(即这里的idx)选择simTime
+            # if idx % 2 == 0:
+                # 偶数组,选择第一个
+            if sim_times:  # 确保列表不为空
+                result_time.append(sim_times[-1])
+                result_time.append(sim_times[-1])
+            result_times.append(result_time)
+        return result_times
+
+    def LKA_warning_active_time_statistics(self):
+        warning_status_time_dict = {}
+        left_warning = 1
+        right_warning = 2
+        LKA_left_vehstate = self.lane_keep_warning_status(left_warning)
+        LKA_right_vehstate = self.lane_keep_warning_status(right_warning)
+        warning_status_time_dict['LKA_left_active_time'] = LKA_left_vehstate
+        warning_status_time_dict['LKA_right_active_time'] = LKA_right_vehstate
+        return warning_status_time_dict
+
+
+if __name__ == '__main__':
+    df_vehState = pd.read_csv(r"D:\Cicv\Lantu\数据\LKA_data2\data\VehState.csv")
+    LKA = LKA_Trigger(df_vehState)
+    vehstate = LKA.LKA_warning_active_time_statistics()
+    print(vehstate)

+ 1104 - 0
close_beta_test/config_test/builtin+acc.json

@@ -0,0 +1,1104 @@
+{
+  "safe": {
+    "safeTime": {
+      "TTC": {
+        "name": "TTC",
+        "priority": "0",
+        "paramList": [
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "2.86",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          },
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "s"
+      },
+      "MTTC": {
+        "priority": "0",
+        "paramList": [
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1.2",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          },
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "s",
+        "name": "MTTC"
+      },
+      "THW": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "0.4",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          },
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "s",
+        "name": "THW"
+      }
+    },
+    "safeDistance": {
+      "LonSD": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "10",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          },
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "m",
+        "name": "LonSD"
+      },
+      "LatSD": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "2",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          },
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "m",
+        "name": "LatSD"
+      }
+    },
+    "safeAcceleration": {
+      "DRAC": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "5",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          },
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "m/s^2",
+        "name": "DRAC"
+      },
+      "BTN": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          },
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "%",
+        "name": "BTN"
+      },
+      "STN": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          },
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "%",
+        "name": "STN"
+      }
+    },
+    "safeProbability": {
+      "collisionRisk": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "10",
+            "multiple": [
+              "0.19",
+              "5.4"
+            ]
+          },
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "%",
+        "name": "碰撞风险概率"
+      },
+      "collisionSeverity": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "10",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          },
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "%",
+        "name": "碰撞严重程度"
+      }
+    }
+  },
+  "function": {
+    "functionACC": {
+      "followSpeedDeviation": {
+        "priority": "0",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "2",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "km/h",
+        "name": "跟车速度偏差"
+      },
+      "followDistanceDeviation": {
+        "priority": "0",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "3",
+            "multiple": [
+              "0.5",
+              "2"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "s",
+        "name": "跟车距离偏差"
+      },
+      "followStopDistance": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "4",
+            "multiple": [
+              "0.25",
+              "4"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "m",
+        "name": "跟停最短距离"
+      },
+      "followResponseTime": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1.2",
+            "multiple": [
+              "0.5",
+              "2"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "s",
+        "name": "跟车启动响应时间"
+      }
+    },
+    "function_ACC": {
+      "cicv_acc_01_delay_time_cruise": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1.0",
+            "multiple": [
+              "0.5",
+              "2"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "s",
+        "name": "定速巡航延迟时间"
+      },
+      "cicv_acc_02_rise_time_cruise": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1.0",
+            "multiple": [
+              "0.5",
+              "2"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "s",
+        "name": "定速巡航上升时间"
+      },
+      "cicv_acc_03_peak_time_cruise": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1.0",
+            "multiple": [
+              "0.5",
+              "2"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "s",
+        "name": "定速巡航峰值时间"
+      },
+      "cicv_acc_04_overshoot_cruise": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1.0",
+            "multiple": [
+              "0.5",
+              "2"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "%",
+        "name": "定速巡航超调量"
+      },
+      "cicv_acc_05_steady_error_cruise": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1.0",
+            "multiple": [
+              "0.5",
+              "2"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "%",
+        "name": "定速巡航速度稳态误差"
+      }
+    }
+  },
+  "comfort": {
+    "comfortLat": {
+      "zigzag": {
+        "priority": "0",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          },
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "10",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          },
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "3",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": null,
+        "name": "画龙"
+      },
+      "shake": {
+        "priority": "0",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          },
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "10",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          },
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "3",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": null,
+        "name": "晃动"
+      }
+    },
+    "comfortLon": {
+      "cadence": {
+        "priority": "0",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          },
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "10",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          },
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "5",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": null,
+        "name": "顿挫"
+      },
+      "slamBrake": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          },
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "10",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          },
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "5",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": null,
+        "name": "急刹"
+      },
+      "slamAccelerate": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          },
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "10",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          },
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "5",
+            "multiple": [
+              "0.2",
+              "5"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": null,
+        "name": "急加速"
+      }
+    }
+  },
+  "efficient": {
+    "efficientDrive": {
+      "averageSpeed": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "24",
+            "multiple": [
+              "0.8",
+              "1.25"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "km/h",
+        "name": "平均速度"
+      }
+    },
+    "efficientStop": {
+      "stopDuration": {
+        "priority": "0",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "5",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "s",
+        "name": "停车平均时长"
+      },
+      "stopCount": {
+        "priority": "1",
+        "paramList": [
+          {
+            "kind": "-1",
+            "spare": [
+              {
+                "param": null
+              },
+              {
+                "param": null
+              }
+            ],
+            "optimal": "1",
+            "multiple": [
+              "0.33",
+              "3"
+            ]
+          }
+        ],
+        "weight": null,
+        "unit": "次",
+        "name": "停车次数"
+      }
+    }
+  },
+  "compliance": {
+    "deduct1": {
+      "overspeed10": {
+        "weight": null,
+        "unit": null,
+        "name": "超速,但未超过10%"
+      },
+      "overspeed10_20": {
+        "weight": null,
+        "unit": null,
+        "name": "超速10%-20%"
+      }
+    },
+    "deduct3": {
+      "pressSolidLine": {
+        "weight": null,
+        "unit": null,
+        "name": "压实线"
+      }
+    },
+    "deduct6": {
+      "runRedLight": {
+        "weight": null,
+        "unit": null,
+        "name": "闯红灯"
+      },
+      "overspeed20_50": {
+        "weight": null,
+        "unit": null,
+        "name": "超速20%-50%"
+      }
+    },
+    "deduct12": {
+      "overspeed50": {
+        "name": "超速50%以上",
+        "weight": null,
+        "unit": null
+      }
+    }
+  },
+  "dimensionWeight": {
+    "efficient": 0.2,
+    "compliance": 0.2,
+    "function": 0.2,
+    "safe": 0.2,
+    "comfort": 0.2
+  },
+  "typeWeight": {
+    "efficient": {
+      "efficientDrive": null,
+      "efficientStop": null
+    },
+    "compliance": {
+      "deduct3": null,
+      "deduct6": null,
+      "deduct12": null,
+      "deduct1": null
+    },
+    "function": {
+      "functionACC": null,
+      "function_ACC": null
+    },
+    "safe": {
+      "safeDistance": null,
+      "safeTime": null,
+      "safeProbability": null,
+      "safeAcceleration": null
+    },
+    "comfort": {
+      "comfortLat": null,
+      "comfortLon": null
+    }
+  },
+  "dimensionName": {
+    "efficient": "高效性",
+    "compliance": "合规性",
+    "function": "功能性",
+    "safe": "安全性",
+    "comfort": "舒适性"
+  },
+  "typeName": {
+    "efficient": {
+      "efficientDrive": "行驶",
+      "efficientStop": "停车"
+    },
+    "compliance": {
+      "deduct3": "中等违规(扣3分)",
+      "deduct6": "危险违规(扣6分)",
+      "deduct12": "重大违规(扣12分)",
+      "deduct1": "轻微违规(扣1分)"
+    },
+    "function": {
+      "functionACC": "ACC",
+      "function_ACC": "function_ACC"
+    },
+    "safe": {
+      "safeDistance": "距离类型",
+      "safeTime": "时间类型",
+      "safeProbability": "概率类型",
+      "safeAcceleration": "加速度类型"
+    },
+    "comfort": {
+      "comfortLat": "横向舒适性",
+      "comfortLon": "纵向舒适性"
+    }
+  }
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
close_beta_test/config_test/builtin.json


+ 5 - 1
comfort.py

@@ -417,6 +417,8 @@ class Comfort(object):
             return 1
         elif lon_acc < 0 and lon_acc > ip_dec:
             return -1
+        else:
+            return 0
 
     def _cadence_detector(self):
         """
@@ -1098,7 +1100,7 @@ class Comfort(object):
                         "strengthReal": f"{self.strength_dict[metric]:.2f}",
                         "strengthRef": f"{self.optimal3_dict[metric]}"
                     }
-
+                    str_uncomf_over_optimal = str_uncomf_over_optimal[:-1]
                     # for description
                     if self.count_dict[metric] > 0:
                         str_uncomf_count += f'{self.count_dict[metric]}次{self.name_dict[metric]}行为、'
@@ -1185,8 +1187,10 @@ class Comfort(object):
 
             type_details_dict[type] = type_dict
 
+        str_uncomf_over_optimal = str_uncomf_over_optimal[:-1]
         report_dict["details"] = type_details_dict
 
+        comf_description1 = ""
         # str for comf description2
         if grade_comfort == '优秀':
             comf_description1 = '乘客在本轮测试中体验舒适;'

+ 19 - 0
common.py

@@ -27,6 +27,25 @@ import zipfile
 import importlib
 
 
+def get_status_active_data(active_time_ranges, df):
+    # 给定的双层列表
+    # active_time_ranges = status_trigger_dict['LKA']['LKA_active_time']
+
+    # 使用Pandas的between函数(注意between是闭区间)结合列表推导来过滤
+    # 这里需要遍历所有时间范围,然后使用`|`(逻辑或)将它们的结果合并
+    filtered_df = pd.DataFrame(columns=df.columns)
+    for start, end in active_time_ranges:
+        mask = (df['simTime'] >= start) & (df['simTime'] <= end)
+        if filtered_df is None:
+            filtered_df = df[mask]
+        else:
+            filtered_df = pd.concat([filtered_df, df[mask]])
+
+    # 上述方式可能引入重复行,使用drop_duplicates去重
+    df_result = filtered_df.drop_duplicates()
+    return df_result
+
+
 def custom_metric_param_parser(param_list):
     """
     param_dict = {

+ 3 - 0
compliance.py

@@ -218,6 +218,9 @@ class Compliance(object):
 
             ego_df["traffic_light_distance_absolute"] = ego_df[['posX', 'posY']].apply( \
                 lambda x: euclidean((trafficLight_position_x, trafficLight_position_y), (x['posX'], x['posY'])), axis=1)
+
+            # ego_df["traffic_light_distance_absolute"] = ego_df.apply(lambda x: euclidean((trafficLight_position_x, trafficLight_position_y), (x['posX'], x['posY'])), axis=1)
+
             ego_df["traffic_light_h_diff"] = ego_df.apply(
                 lambda x: abs(x['posH'] - trafficLight_position_heading) * 57.3, axis=1)
             ego_df["traffic_light_h_diff"] = ego_df.apply(

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
config/config_voyah_0912.json


+ 21 - 0
custom/voyah/ACC/cicv_acc_01_delay_time_cruise.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 196 - 0
custom/voyah/ACC/cicv_acc_01_delay_time_cruise.py

@@ -0,0 +1,196 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_acc = pd.DataFrame()
+
+        # self.stable_start_time_cruise = None
+        self.stable_average_speed = None
+        self.delay_time_cruise = None
+
+        self.result = {
+            "name": "定速巡航延迟时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "定速巡航延迟时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"定速巡航延迟时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+
+        active_time_ranges = self.status_trigger_dict['ACC']['ACC_active_time']
+        self.df_acc = get_status_active_data(active_time_ranges, self.df)
+        # self.df_acc = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Line"].copy()  # 数字3对应ICA的 LLC_Follow_Line
+        # self.df_acc = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_acc = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_acc.empty:
+            self.result['statusFlag']['functionACC'] = False
+        else:
+            self.result['statusFlag']['functionACC'] = True
+
+    def _find_stable_speed_cruise(self, window_size, percent_deviation, set_value):
+        """
+        在给定的速度数据中查找稳定的巡航速度段,并计算该段的平均速度。
+
+        Args:
+            window_size (int): 滑动窗口的大小,表示用于计算平均速度的速度数据点数量。
+            percent_deviation (float): 设定值的允许偏差百分比。
+            set_value (float): 期望的稳定速度设定值。
+
+        Returns:
+            None
+
+        """
+        # speed_data = self.df['speedX'].values
+        speed_data = self.df_acc['speedX'].values  # .tolist()
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_speed = None
+
+        for i in range(len(speed_data) - window_size + 1):
+            window_data = speed_data[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_speed = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(speed_data) - window_size + 1:
+                    next_window_data = speed_data[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_speed = (stable_average_speed * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_speed = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_cruise = self.df['simTime'].iloc[stable_start]
+        self.stable_average_speed = stable_average_speed
+
+    def data_analyze(self):
+        change_indices = self.df_acc[self.df_acc['set_cruise_speed'] != self.df_acc['set_cruise_speed'].shift()].index
+        print(f"Change indices of set speed: {change_indices}")
+
+        set_cruise_speed = self.df_acc.loc[change_indices[0], 'set_cruise_speed']
+        self._find_stable_speed_cruise(window_size=4, percent_deviation=5, set_value=set_cruise_speed)
+
+        if not change_indices.empty:
+            first_change_index = change_indices[change_indices != 0].min()
+            set_cruise_speed_at_change = self.df_acc.loc[first_change_index, 'set_cruise_speed']
+            timestamp_at_change = self.df_acc.loc[first_change_index, 'simTime']
+            print(f"Set speed at first change: {set_cruise_speed_at_change}, Timestamp: {timestamp_at_change}")
+
+            target_speed = (self.stable_average_speed + self.df_acc.loc[first_change_index, 'speedX']) / 2
+            closest_index = (self.df_acc['speedX'] - target_speed).abs().idxmin()
+            closest_current_speed = self.df_acc.loc[closest_index, 'speedX']
+            closest_timestamp = self.df_acc.loc[closest_index, 'simTime']
+            print(f"Closest speed: {closest_current_speed} at time: {closest_timestamp}")
+
+            self.delay_time_cruise = closest_timestamp - timestamp_at_change
+            self.result['value'] = [round(self.delay_time_cruise, 3)]
+            print(f"Delay time: {self.delay_time_cruise}")
+
+        else:
+            self.delay_time_cruise = 0
+            self.result['value'] = [round(self.delay_time_cruise, 3)]
+            print("No valid change point for further calculation.")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_acc.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[delay_time_cruise:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ACC/cicv_acc_02_rise_time_cruise.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 226 - 0
custom/voyah/ACC/cicv_acc_02_rise_time_cruise.py

@@ -0,0 +1,226 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_acc = pd.DataFrame()
+
+        self.stable_average_speed = None
+        self.rise_time_cruise = None
+
+        self.result = {
+            "name": "定速巡航上升时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "定速巡航上升时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"定速巡航上升时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+
+        active_time_ranges = self.status_trigger_dict['ACC']['ACC_active_time']
+        self.df_acc = get_status_active_data(active_time_ranges, self.df)
+        # self.df_acc = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Line"].copy()  # 数字3对应ICA的Active
+        # self.df_acc = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_acc = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_acc.empty:
+            self.result['statusFlag']['functionACC'] = False
+        else:
+            self.result['statusFlag']['functionACC'] = True
+
+    def _get_first_change_index_cruise(self):
+        """
+        获取数据集中第一次巡航速度发生变化的索引。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在巡航速度发生变化的索引,则返回第一个发生变化的索引(int类型);
+            如果不存在,则返回None。
+
+        """
+        change_indices = self.df_acc[self.df_acc['set_cruise_speed'] != self.df_acc['set_cruise_speed'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def _find_stable_speed_cruise(self, window_size, percent_deviation, set_value):
+        """
+        在给定的速度数据中查找稳定的巡航速度段,并计算该段的平均速度。
+
+        Args:
+            window_size (int): 滑动窗口的大小,表示用于计算平均速度的速度数据点数量。
+            percent_deviation (float): 设定值的允许偏差百分比。
+            set_value (float): 期望的稳定速度设定值。
+
+        Returns:
+            None
+
+        """
+        # speed_data = self.df['speedX'].values
+        speed_data = self.df_acc['speedX'].values   #.tolist()
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_speed = None
+
+        for i in range(len(speed_data) - window_size + 1):
+            window_data = speed_data[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_speed = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(speed_data) - window_size + 1:
+                    next_window_data = speed_data[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_speed = (stable_average_speed * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_speed = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_cruise = self.df['simTime'].iloc[stable_start]
+        self.stable_average_speed = stable_average_speed
+
+    def _find_closest_time_stamp_cruise(self, df, target_speed, start_index):
+        """
+        在给定的数据帧df中,从start_index索引位置开始,查找与目标速度target_speed最接近的时间戳。
+
+        Args:
+            df (pd.DataFrame): 包含速度和时间戳等信息的数据帧,需要至少包含'speedX'和'simTime'两列。
+            target_speed (float): 目标速度值,用于在数据帧中查找最接近此值的时间戳。
+            start_index (int): 开始查找的索引位置,即在数据帧df中从该索引位置开始向后查找。
+
+        Returns:
+            pd.Timestamp: 与目标速度最接近的时间戳。
+
+        """
+        subset = df.loc[start_index + 1:]
+        speed_diff = np.abs(subset['speedX'] - target_speed)
+        closest_index = speed_diff.idxmin()
+        closest_timestamp = subset.loc[closest_index, 'simTime']
+        return closest_timestamp
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_cruise()
+
+        set_cruise_speed = self.df_acc.loc[first_change_index, 'set_cruise_speed']
+        self._find_stable_speed_cruise(window_size=4, percent_deviation=5, set_value=set_cruise_speed)
+
+        initial_speed = self.df_acc.loc[first_change_index, 'speedX']
+
+        target_speed_90 = initial_speed + (self.stable_average_speed - initial_speed) * 0.9
+        target_speed_10 = initial_speed + (self.stable_average_speed - initial_speed) * 0.1
+
+        timestamp_at_10 = self._find_closest_time_stamp_cruise(self.df_acc, target_speed_10, first_change_index)
+        timestamp_at_90 = self._find_closest_time_stamp_cruise(self.df_acc, target_speed_90, first_change_index)
+
+        print(f"Closest speed at 10% range from set speed: {timestamp_at_10}")
+        print(f"Closest speed at 90% range from set speed: {timestamp_at_90}")
+
+        self.rise_time_cruise = timestamp_at_90 - timestamp_at_10
+        self.result['value'] = [round(self.rise_time_cruise, 3)]
+        print(f"Rise time: {self.rise_time_cruise}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_acc.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[rise_time_cruise:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ACC/cicv_acc_03_peak_time_cruise.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 153 - 0
custom/voyah/ACC/cicv_acc_03_peak_time_cruise.py

@@ -0,0 +1,153 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_acc = pd.DataFrame()
+
+        self.peak_time_cruise = None
+
+        self.result = {
+            "name": "定速巡航峰值时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "定速巡航峰值时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"定速巡航峰值时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+
+        active_time_ranges = self.status_trigger_dict['ACC']['ACC_active_time']
+        self.df_acc = get_status_active_data(active_time_ranges, self.df)
+        # self.df_acc = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Line"].copy()  # 数字3对应ICA的Active
+        # self.df_acc = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_acc = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_acc.empty:
+            self.result['statusFlag']['functionACC'] = False
+        else:
+            self.result['statusFlag']['functionACC'] = True
+
+    def _get_first_change_index_cruise(self):
+        """
+        获取数据集中第一次巡航速度发生变化的索引。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在巡航速度发生变化的索引,则返回第一个发生变化的索引(int类型);
+            如果不存在,则返回None。
+
+        """
+        change_indices = self.df_acc[self.df_acc['set_cruise_speed'] != self.df_acc['set_cruise_speed'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_cruise()
+
+        if not first_change_index:
+            self.peak_time_cruise = 0
+            self.result['value'] = [round(self.peak_time_cruise, 3)]
+            print(f"peak_time_cruise: {self.peak_time_cruise}")
+        else:
+            start_time = self.df_acc.loc[first_change_index, 'simTime']
+            peak_time = self.df_acc.loc[self.df_acc['speedX'].idxmax(), 'simTime']
+            self.peak_time_cruise = peak_time - start_time
+            self.result['value'] = [round(self.peak_time_cruise, 3)]
+            print(f"peak_time_cruise: {self.peak_time_cruise}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_acc.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[peak_time_cruise:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ACC/cicv_acc_04_overshoot_cruise.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 208 - 0
custom/voyah/ACC/cicv_acc_04_overshoot_cruise.py

@@ -0,0 +1,208 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_acc = pd.DataFrame()
+
+        self.stable_average_speed = None
+        self.overshoot_cruise = None
+
+        self.result = {
+            "name": "定速巡航超调量",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "定速巡航超调量(%)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"定速巡航超调量: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        active_time_ranges = self.status_trigger_dict['ACC']['ACC_active_time']
+        self.df_acc = get_status_active_data(active_time_ranges, self.df)
+        # self.df_acc = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Line"].copy()  # 数字3对应ICA的Active
+        # self.df_acc = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_acc = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_acc.empty:
+            self.result['statusFlag']['functionACC'] = False
+        else:
+            self.result['statusFlag']['functionACC'] = True
+
+    def _get_first_change_index_cruise(self):
+        """
+        获取数据集中第一次巡航速度发生变化的索引。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在巡航速度发生变化的索引,则返回第一个发生变化的索引(int类型);
+            如果不存在,则返回None。
+
+        """
+        change_indices = self.df_acc[self.df_acc['set_cruise_speed'] != self.df_acc['set_cruise_speed'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def _find_stable_speed_cruise(self, window_size, percent_deviation, set_value):
+        """
+        在给定的速度数据中查找稳定的巡航速度段,并计算该段的平均速度。
+
+        Args:
+            window_size (int): 滑动窗口的大小,表示用于计算平均速度的速度数据点数量。
+            percent_deviation (float): 设定值的允许偏差百分比。
+            set_value (float): 期望的稳定速度设定值。
+
+        Returns:
+            None
+
+        """
+        # speed_data = self.df['speedX'].values
+        speed_data = self.df_acc['speedX'].values  # .tolist()
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_speed = None
+
+        for i in range(len(speed_data) - window_size + 1):
+            window_data = speed_data[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_speed = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(speed_data) - window_size + 1:
+                    next_window_data = speed_data[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_speed = (stable_average_speed * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_speed = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_cruise = self.df['simTime'].iloc[stable_start]
+        self.stable_average_speed = stable_average_speed
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_cruise()
+
+        set_cruise_speed = self.df_acc.loc[first_change_index, 'set_cruise_speed']
+        self._find_stable_speed_cruise(window_size=4, percent_deviation=5, set_value=set_cruise_speed)
+
+        if not first_change_index:
+            self.overshoot_cruise = 0
+        else:
+            initial_speed = self.df.loc[first_change_index, 'speedX']
+
+            if initial_speed > self.stable_average_speed:
+                self.overshoot_cruise = (self.stable_average_speed - self.df[
+                    'speedX'].min()) * 100 / self.stable_average_speed
+            elif initial_speed < self.stable_average_speed:
+                self.overshoot_cruise = (self.df[
+                                             'speedX'].max() - self.stable_average_speed) * 100 / self.stable_average_speed
+            else:
+                self.overshoot_cruise = 0
+
+        self.result['value'] = [round(self.overshoot_cruise, 3)]
+        print(f"overshoot_cruise: {self.overshoot_cruise}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_acc.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[overshoot_cruise:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ACC/cicv_acc_05_steady_error_cruise.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 205 - 0
custom/voyah/ACC/cicv_acc_05_steady_error_cruise.py

@@ -0,0 +1,205 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_acc = pd.DataFrame()
+
+        self.stable_average_speed = None
+        self.steady_error_cruise = None
+
+        self.result = {
+            "name": "定速巡航速度稳态误差",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "定速巡航速度稳态误差(%)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"定速巡航速度稳态误差: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        active_time_ranges = self.status_trigger_dict['ACC']['ACC_active_time']
+        self.df_acc = get_status_active_data(active_time_ranges, self.df)
+        # self.df_acc = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Line"].copy()  # 数字3对应ICA的Active
+        # self.df_acc = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_acc = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_acc.empty:
+            self.result['statusFlag']['functionACC'] = False
+        else:
+            self.result['statusFlag']['functionACC'] = True
+
+    def _get_first_change_index_cruise(self):
+        """
+        获取数据集中第一次巡航速度发生变化的索引。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在巡航速度发生变化的索引,则返回第一个发生变化的索引(int类型);
+            如果不存在,则返回None。
+
+        """
+        change_indices = self.df_acc[self.df_acc['set_cruise_speed'] != self.df_acc['set_cruise_speed'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def _find_stable_speed_cruise(self, window_size, percent_deviation, set_value):
+        """
+        在给定的速度数据中查找稳定的巡航速度段,并计算该段的平均速度。
+
+        Args:
+            window_size (int): 滑动窗口的大小,表示用于计算平均速度的速度数据点数量。
+            percent_deviation (float): 设定值的允许偏差百分比。
+            set_value (float): 期望的稳定速度设定值。
+
+        Returns:
+            None
+
+        """
+        # speed_data = self.df['speedX'].values
+        speed_data = self.df_acc['speedX'].values  # .tolist()
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_speed = None
+
+        for i in range(len(speed_data) - window_size + 1):
+            window_data = speed_data[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_speed = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(speed_data) - window_size + 1:
+                    next_window_data = speed_data[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_speed = (stable_average_speed * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_speed = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_cruise = self.df['simTime'].iloc[stable_start]
+        self.stable_average_speed = stable_average_speed
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_cruise()
+
+        if not first_change_index:
+            self.steady_error_cruise = 0
+            self.result['value'] = [round(self.steady_error_cruise, 3)]
+            print(f"steady_error_cruise: {self.steady_error_cruise}")
+        else:
+            set_cruise_speed = self.df_acc.loc[first_change_index, 'set_cruise_speed']
+            self._find_stable_speed_cruise(window_size=4, percent_deviation=5, set_value=set_cruise_speed)
+
+            if self.stable_average_speed:
+                self.steady_error_cruise = (self.stable_average_speed - set_cruise_speed) * 100 / self.stable_average_speed
+            else:
+                self.steady_error_cruise = 0
+                raise ValueError("Stable average speed is None.")
+
+            self.result['value'] = [round(self.steady_error_cruise, 3)]
+            print(f"steady_error_cruise: {self.steady_error_cruise}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_acc.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[steady_error_cruise:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ACC/cicv_acc_06_delay_time_THW.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 191 - 0
custom/voyah/ACC/cicv_acc_06_delay_time_THW.py

@@ -0,0 +1,191 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        # self.stable_start_time_THW = None
+        self.stable_average_THW = None
+        self.delay_time_THW = None
+
+        self.result = {
+            "name": "跟车延迟时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "跟车延迟时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"跟车延迟时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Vehicle"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _find_stable_THW(self, window_size, percent_deviation, set_value):
+        """
+        在给定的数据窗口中查找稳定跟车时距离THW,并计算该段内THW的平均值。
+
+        Args:
+            window_size (int): 窗口大小,表示在数据中寻找稳定段时考虑的连续数据点数量。
+            percent_deviation (float): THW值相对于设定值的允许偏差百分比。
+            set_value (float): THW的设定值。
+
+        Returns:
+            None
+
+        """
+        THW = self.df_ica['THW'].values
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_THW = None
+
+        for i in range(len(THW) - window_size + 1):
+            window_data = THW[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_THW = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(THW) - window_size + 1:
+                    next_window_data = THW[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_THW = (stable_average_THW * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_THW = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_THW = self.df_ica['simTime'].iloc[stable_start]
+        self.stable_average_THW = stable_average_THW
+
+    def data_analyze(self):
+        change_indices = self.df_ica[self.df_ica['set_headway_time'] != self.df_ica['set_headway_time'].shift()].index
+        print(f"Change indices of set speed: {change_indices}")
+
+        set_headway_time = self.df_ica.loc[change_indices[0], 'set_headway_time']
+        self._find_stable_THW(window_size=4, percent_deviation=5, set_value=set_headway_time)
+
+        if not change_indices.empty:
+            first_change_index = change_indices[change_indices != 0].min()
+            set_THW_at_change = self.df_ica.loc[first_change_index, 'set_headway_time']
+            timestamp_at_change = self.df_ica.loc[first_change_index, 'simTime']
+            print(f"Set THW at first change: {set_THW_at_change}, Timestamp: {timestamp_at_change}")
+
+            target_THW = (self.stable_average_THW + self.df_ica.loc[first_change_index, 'set_headway_time']) / 2
+            closest_index = (self.df_ica['set_headway_time'] - target_THW).abs().idxmin()
+            closest_current_THW = self.df_ica.loc[closest_index, 'set_headway_time']
+            closest_timestamp = self.df_ica.loc[closest_index, 'simTime']
+            print(f"Closest speed: {closest_current_THW} at time: {closest_timestamp}")
+
+            self.delay_time_THW = closest_timestamp - timestamp_at_change
+            self.result['value'] = [round(self.delay_time_THW, 3)]
+            print(f"Delay time: {self.delay_time_THW}")
+
+        else:
+            self.delay_time_THW = 0
+            self.result['value'] = [round(self.delay_time_THW, 3)]
+            print("No valid change point for further calculation.")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[delay_time_THW:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ACC/cicv_acc_07_rise_time_THW.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 231 - 0
custom/voyah/ACC/cicv_acc_07_rise_time_THW.py

@@ -0,0 +1,231 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        # self.stable_start_time_THW = None
+        self.stable_average_THW = None
+        self.rise_time_THW = None
+
+        self.result = {
+            "name": "跟车上升时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "跟车上升时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"跟车上升时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Vehicle"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _get_first_change_index_THW(self):
+        """
+        获取DataFrame中'set_headway_time'列首次发生变化的索引值。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在变化,则返回首次发生变化的索引值(int类型),否则返回None。
+
+        """
+        change_indices = self.df_ica[self.df_ica['set_headway_time'] != self.df_ica['set_headway_time'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def _find_stable_THW(self, window_size, percent_deviation, set_value):
+        """
+        在给定的数据窗口中查找稳定跟车时距离THW,并计算该段内THW的平均值。
+
+        Args:
+            window_size (int): 窗口大小,表示在数据中寻找稳定段时考虑的连续数据点数量。
+            percent_deviation (float): THW值相对于设定值的允许偏差百分比。
+            set_value (float): THW的设定值。
+
+        Returns:
+            None
+
+        """
+        THW = self.df_ica['THW'].values
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_THW = None
+
+        for i in range(len(THW) - window_size + 1):
+            window_data = THW[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_THW = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(THW) - window_size + 1:
+                    next_window_data = THW[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_THW = (stable_average_THW * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_THW = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_THW = self.df_ica['simTime'].iloc[stable_start]
+        self.stable_average_THW = stable_average_THW
+
+    def _find_closest_time_stamp_THW(self, df, target_THW, start_index):
+        """
+        在DataFrame中找到与目标THW值最接近的时间戳。
+
+        Args:
+            df (pandas.DataFrame): 包含'THW'和'simTime'列的DataFrame,其中'THW'表示目标变量,'simTime'表示时间戳。
+            target_THW (float): 目标THW值。
+            start_index (int): 开始搜索的索引位置(不包含)。
+
+        Returns:
+            pandas.Timestamp: 与目标THW值最接近的时间戳。
+
+        """
+        subset = df.loc[start_index + 1:]
+        THW_diff = np.abs(subset['THW'] - target_THW)
+        closest_index = THW_diff.idxmin()
+        closest_timestamp = subset.loc[closest_index, 'simTime']
+        return closest_timestamp
+
+    def data_analyze(self):
+        """
+        计算巡航时从初THW到达稳定THW的90%的所需时间(rise time)。
+
+        Args:
+            无。
+
+        Returns:
+            无返回值,但会设置实例属性self.rise_time_THW为计算得到的rise time。
+
+        """
+        first_change_index = self._get_first_change_index_THW()
+
+        set_headway_time = self.df_ica.loc[first_change_index, 'set_headway_time']
+        self._find_stable_THW(window_size=4, percent_deviation=5, set_value=set_headway_time)
+
+        initial_THW = self.df_ica.loc[first_change_index, 'THW']
+
+        target_THW_90 = initial_THW + (self.stable_average_THW - initial_THW) * 0.9
+        target_THW_10 = initial_THW + (self.stable_average_THW - initial_THW) * 0.1
+
+        timestamp_at_10 = self._find_closest_time_stamp_THW(self.df_ica, target_THW_10, first_change_index)
+        timestamp_at_90 = self._find_closest_time_stamp_THW(self.df_ica, target_THW_90, first_change_index)
+
+        print(f"Closest speed at 10% range from set speed: {timestamp_at_10}")
+        print(f"Closest speed at 90% range from set speed: {timestamp_at_90}")
+
+        self.rise_time_THW = timestamp_at_90 - timestamp_at_10
+        self.result['value'] = [round(self.rise_time_THW, 3)]
+        print(f"Rise time: {self.rise_time_THW}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[rise_time_THW:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ACC/cicv_acc_08_peak_time_THW.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 148 - 0
custom/voyah/ACC/cicv_acc_08_peak_time_THW.py

@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.peak_time_THW = None
+
+        self.result = {
+            "name": "跟车峰值时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "跟车峰值时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"跟车峰值时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Vehicle"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _get_first_change_index_THW(self):
+        """
+        获取DataFrame中'set_headway_time'列首次发生变化的索引值。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在变化,则返回首次发生变化的索引值(int类型),否则返回None。
+
+        """
+        change_indices = self.df_ica[self.df_ica['set_headway_time'] != self.df_ica['set_headway_time'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_THW()
+
+        if not first_change_index:
+            self.peak_time_THW = 0
+            self.result['value'] = [round(self.peak_time_THW, 3)]
+            print(f"peak_time_THW: {self.peak_time_THW}")
+        else:
+            start_time = self.df_ica.loc[first_change_index, 'simTime']
+            peak_time = self.df_ica.loc[self.df_ica['THW'].idxmax(), 'simTime']
+            self.peak_time_THW = peak_time - start_time
+            self.result['value'] = [round(self.peak_time_THW, 3)]
+            print(f"peak_time_THW: {self.peak_time_THW}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[peak_time_THW:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ACC/cicv_acc_09_overshoot_THW.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 201 - 0
custom/voyah/ACC/cicv_acc_09_overshoot_THW.py

@@ -0,0 +1,201 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.stable_average_THW = None
+        self.overshoot_THW = None
+
+        self.result = {
+            "name": "跟车超调量",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "跟车超调量(%)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"跟车超调量: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Vehicle"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _get_first_change_index_THW(self):
+        """
+        获取DataFrame中'set_headway_time'列首次发生变化的索引值。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在变化,则返回首次发生变化的索引值(int类型),否则返回None。
+
+        """
+        change_indices = self.df_ica[self.df_ica['set_headway_time'] != self.df_ica['set_headway_time'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def _find_stable_THW(self, window_size, percent_deviation, set_value):
+        """
+        在给定的数据窗口中查找稳定跟车时距离THW,并计算该段内THW的平均值。
+
+        Args:
+            window_size (int): 窗口大小,表示在数据中寻找稳定段时考虑的连续数据点数量。
+            percent_deviation (float): THW值相对于设定值的允许偏差百分比。
+            set_value (float): THW的设定值。
+
+        Returns:
+            None
+
+        """
+        THW = self.df_ica['THW'].values
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_THW = None
+
+        for i in range(len(THW) - window_size + 1):
+            window_data = THW[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_THW = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(THW) - window_size + 1:
+                    next_window_data = THW[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_THW = (stable_average_THW * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_THW = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_THW = self.df_ica['simTime'].iloc[stable_start]
+        self.stable_average_THW = stable_average_THW
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_THW()
+
+        set_headway_time = self.df_ica.loc[first_change_index, 'set_headway_time']
+        self._find_stable_THW(window_size=4, percent_deviation=5, set_value=set_headway_time)
+
+        if not first_change_index:
+            self.overshoot_THW = 0
+        else:
+            initial_THW = self.df_ica.loc[first_change_index, 'THW']
+
+            if initial_THW > self.stable_average_THW:
+                self.overshoot_THW = (self.stable_average_THW - self.df_ica['THW'].min()) * 100 / self.stable_average_THW
+            elif initial_THW < self.stable_average_THW:
+                self.overshoot_THW = (self.df_ica['THW'].max() - self.stable_average_THW) * 100 / self.stable_average_THW
+            else:
+                self.overshoot_THW = 0
+
+        self.result['value'] = [round(self.overshoot_THW, 3)]
+        print(f"overshoot_THW: {self.overshoot_THW}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[overshoot_THW:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ACC/cicv_acc_10_steady_error_THW.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 201 - 0
custom/voyah/ACC/cicv_acc_10_steady_error_THW.py

@@ -0,0 +1,201 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.stable_average_THW = None
+        self.steady_error_THW = None
+
+        self.result = {
+            "name": "跟车速度稳态误差",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "跟车速度稳态误差(%)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"跟车速度稳态误差: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Vehicle"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _get_first_change_index_THW(self):
+        """
+        获取DataFrame中'set_headway_time'列首次发生变化的索引值。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在变化,则返回首次发生变化的索引值(int类型),否则返回None。
+
+        """
+        change_indices = self.df_ica[self.df_ica['set_headway_time'] != self.df_ica['set_headway_time'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def _find_stable_THW(self, window_size, percent_deviation, set_value):
+        """
+        在给定的数据窗口中查找稳定跟车时距离THW,并计算该段内THW的平均值。
+
+        Args:
+            window_size (int): 窗口大小,表示在数据中寻找稳定段时考虑的连续数据点数量。
+            percent_deviation (float): THW值相对于设定值的允许偏差百分比。
+            set_value (float): THW的设定值。
+
+        Returns:
+            None
+
+        """
+        THW = self.df_ica['THW'].values
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_THW = None
+
+        for i in range(len(THW) - window_size + 1):
+            window_data = THW[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_THW = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(THW) - window_size + 1:
+                    next_window_data = THW[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_THW = (stable_average_THW * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_THW = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_THW = self.df_ica['simTime'].iloc[stable_start]
+        self.stable_average_THW = stable_average_THW
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_THW()
+
+
+        if not first_change_index:
+            self.steady_error_THW = 0
+            self.result['value'] = [round(self.steady_error_THW, 3)]
+            print(f"steady_error_THW: {self.steady_error_THW}")
+        else:
+            set_headway_time = self.df_ica.loc[first_change_index, 'set_headway_time']
+            self._find_stable_THW(window_size=4, percent_deviation=5, set_value=set_headway_time)
+
+            if self.stable_average_THW:
+                self.steady_error_THW = (self.stable_average_THW - set_headway_time) * 100 / self.stable_average_THW
+            else:
+                self.steady_error_THW = 0
+                raise ValueError("Stable average speed is None.")
+
+            self.result['value'] = [round(self.steady_error_THW, 3)]
+            print(f"steady_error_THW: {self.steady_error_THW}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[steady_error_THW:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ACC/cicv_acc_11_reasonable_acceleration_percentage.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "1",
+      "optimal": "100",
+      "multiple": [
+        "0.6",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 129 - 0
custom/voyah/ACC/cicv_acc_11_reasonable_acceleration_percentage.py

@@ -0,0 +1,129 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.percentage_accel = 0
+
+        self.result = {
+            "name": "合理加减速度占比",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "合理加减速度占比(%)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"合理加减速度占比: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def data_analyze(self):
+        col_list = ['simTime', 'simFrame', 'playerId', 'v', 'accel', 'lon_acc_roc']  # target_id
+        df = self.df_ica[col_list].copy()
+        count_accel_in_range = 0
+        total_accel_frames = 0
+
+        self.graph_list = df['accel'].values.tolist()
+        filtered_data = df[(df['accel'] >= -5) & (df['accel'] <= 4)]
+        count_accel_in_range = count_accel_in_range + len(filtered_data)
+        total_accel_frames = total_accel_frames + len(df)
+        self.percentage_accel = (count_accel_in_range / total_accel_frames) * 100
+
+        self.result['value'] = [round(self.percentage_accel, 3)]
+        print(f"percentage_accel: {self.percentage_accel}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 100]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[percentage_accel:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ACC/cicv_acc_12_reasonable_acceleration_change_rate_percentage.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "1",
+      "optimal": "100",
+      "multiple": [
+        "0.6",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 133 - 0
custom/voyah/ACC/cicv_acc_12_reasonable_acceleration_change_rate_percentage.py

@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.percentage_lon_acc_roc = 0
+
+        self.result = {
+            "name": "合理加减速度变化率占比",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "合理加减速度变化率占比(%)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"合理加减速度变化率占比: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        active_status_list = ["LLC_Follow_Line", "LLC_Follow_Vehicle", "Only_Longitudinal_Control"]
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'].isin(active_status_list)].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def data_analyze(self):
+        col_list = ['simTime', 'simFrame', 'playerId', 'v', 'accel', 'lon_acc_roc']  # target_id
+        df = self.df_ica[col_list].copy()
+
+        count_lon_acc_roc_in_range = 0
+        total_lon_acc_roc_frames = 0
+
+        self.graph_list = df['lon_acc_roc'].values.tolist()
+        filtered_data = df[(df['lon_acc_roc'] >= -5) & (df['lon_acc_roc'] <= 6)]
+        count_lon_acc_roc_in_range = count_lon_acc_roc_in_range + len(filtered_data)
+        total_lon_acc_roc_frames = total_lon_acc_roc_frames + len(df)
+        self.percentage_lon_acc_roc = (count_lon_acc_roc_in_range / total_lon_acc_roc_frames) * 100
+
+        self.result['value'] = [round(self.percentage_lon_acc_roc, 3)]
+        print(f"percentage_lon_acc_roc: {self.percentage_lon_acc_roc}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 100]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[percentage_lon_acc_roc:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 22 - 0
custom/voyah/ICA/cicv_ica_lateral_control01_distance_nearby_lane.json

@@ -0,0 +1,22 @@
+
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "optimal": "0.00001",
+      "multiple": [
+        "1",
+        "10000000"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 161 - 0
custom/voyah/ICA/cicv_ica_lateral_control01_distance_nearby_lane.py

@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+车道宽度:3.75m
+车宽:1.8m
+车的一边距离车道边界线为0.975m为最佳,值越小,分值越低
+
+"""
+
+
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "离近侧车道线最小距离",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "离近侧车道线最小距离(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标01: 离近侧车道线最小距离: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        self.df_follow = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_follow = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+        self.roadMark_df = self.data.road_mark_df
+
+        if self.df_follow.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego/2
+        else:
+            return abs(x.right_lateral_distance) - width_ego/2
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadMark_df = self.roadMark_df
+        player_df = self.df
+        ego_df = player_df[player_df.playerId == 1]
+        width_ego = ego_df['dimY'].values.tolist()[0]
+        # 提取距离左车道线和右车道线距离
+        # roadMark_df['nearby_distance']
+        roadMark_left_df = roadMark_df[roadMark_df.id == 0].reset_index(drop=True)
+        roadMark_right_df = roadMark_df[roadMark_df.id == 2].reset_index(drop=True)
+        roadMark_left_df['right_lateral_distance'] = roadMark_right_df['lateralDist']
+        # 计算到车道边界线距离
+        roadMark_left_df['nearby_distance_to_lane_boundary'] = roadMark_left_df.apply(lambda x: self.Compute_nearby_distance_to_lane_boundary(x, width_ego), axis=1)
+        nearby_distance_to_lane_boundary = min(roadMark_left_df['nearby_distance_to_lane_boundary'])
+        self.result['value'] = [round(nearby_distance_to_lane_boundary, 3)]
+        self.time_list_follow = roadMark_left_df['simTime'].values.tolist()
+        self.frame_list_follow = roadMark_left_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadMark_left_df['nearby_distance_to_lane_boundary'].values.tolist()
+        # print("hello world")
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+
+        v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[0, 0.975]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 22 - 0
custom/voyah/ICA/cicv_ica_lateral_control02_lateral_offset.json

@@ -0,0 +1,22 @@
+
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "optimal": "0.00001",
+      "multiple": [
+        "1",
+        "10000000"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 164 - 0
custom/voyah/ICA/cicv_ica_lateral_control02_lateral_offset.py

@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大横向偏移量
+zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "最大横向偏移量",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "最大横向偏移量(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标02: 最大横向偏移量: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        self.df_follow = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_follow = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+        self.roadMark_df = self.data.road_mark_df
+        self.roadPos_df = self.data.road_pos_df
+
+        if self.df_follow.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego/2
+        else:
+            return abs(x.right_lateral_distance) - width_ego/2
+
+    def func_laneOffset_abs(self, x):
+        return abs(x.laneOffset)
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+        player_df = self.df
+        ego_df = player_df[player_df.playerId == 1]
+        width_ego = ego_df['dimY'].values.tolist()[0]
+
+        # 提取距离左车道线和右车道线距离
+        # roadMark_df['nearby_distance']
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        # roadMark_left_df['right_lateral_distance'] = roadMark_right_df['lateralDist']
+        # # 计算到车道边界线距离
+        roadPos_ego_df['laneOffset_abs'] = roadPos_ego_df.apply(lambda x: self.func_laneOffset_abs(x), axis=1)
+        # max_laneOffset_abs_index = max(roadPos_ego_df['laneOffset_abs'])
+        max_laneOffset_abs_index = roadPos_ego_df['laneOffset_abs'].idxmax()
+        row_with_max_value = roadPos_ego_df.iloc[max_laneOffset_abs_index].laneOffset
+        self.result['value'] = [row_with_max_value]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset'].values.tolist()
+
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 22 - 0
custom/voyah/ICA/cicv_ica_lateral_control03_relative_center_distance_expectation.json

@@ -0,0 +1,22 @@
+
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "optimal": "0.00001",
+      "multiple": [
+        "1",
+        "10000000"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 158 - 0
custom/voyah/ICA/cicv_ica_lateral_control03_relative_center_distance_expectation.py

@@ -0,0 +1,158 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大横向偏移量
+zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "相对横向偏移量分布期望",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "相对横向偏移量分布期望(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标03: 相对横向偏移量分布期望: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        self.df_follow = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_follow = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+        self.roadMark_df = self.data.road_mark_df
+        self.roadPos_df = self.data.road_pos_df
+
+        if self.df_follow.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego/2
+        else:
+            return abs(x.right_lateral_distance) - width_ego/2
+
+    def func_laneOffset_abs(self, x):
+        return abs(x.laneOffset)
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+        player_df = self.df
+        ego_df = player_df[player_df.playerId == 1]
+        width_ego = ego_df['dimY'].values.tolist()[0]
+
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        # # 计算到车道边界线距离
+        mean_laneOffset_index = roadPos_ego_df['laneOffset'].mean()
+        self.result['value'] = [round(mean_laneOffset_index, 3)]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 22 - 0
custom/voyah/ICA/cicv_ica_lateral_control04_relative_center_distance_standard_deviation.json

@@ -0,0 +1,22 @@
+
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "optimal": "0.00001",
+      "multiple": [
+        "1",
+        "10000000"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 155 - 0
custom/voyah/ICA/cicv_ica_lateral_control04_relative_center_distance_standard_deviation.py

@@ -0,0 +1,155 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+    设计思路:
+    最大横向偏移量
+    zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "相对横向偏移量分布标准差",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "相对横向偏移量分布标准差(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标04: 相对横向偏移量分布标准差: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        self.df_follow = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_follow = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+        self.roadMark_df = self.data.road_mark_df
+        self.roadPos_df = self.data.road_pos_df
+
+        if self.df_follow.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego/2
+        else:
+            return abs(x.right_lateral_distance) - width_ego/2
+
+    def func_laneOffset_abs(self, x):
+        return abs(x.laneOffset)
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+        player_df = self.df
+        ego_df = player_df[player_df.playerId == 1]
+        width_ego = ego_df['dimY'].values.tolist()[0]
+
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        mean_laneOffset_index = roadPos_ego_df['laneOffset'].std()
+        self.result['value'] = [round(mean_laneOffset_index, 3)]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 22 - 0
custom/voyah/ICA/cicv_ica_lateral_control05_absolute_center_distance_expectation.json

@@ -0,0 +1,22 @@
+
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "optimal": "0.00001",
+      "multiple": [
+        "1",
+        "10000000"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 161 - 0
custom/voyah/ICA/cicv_ica_lateral_control05_absolute_center_distance_expectation.py

@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大横向偏移量
+zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "绝对横向偏移量分布期望",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "绝对横向偏移量分布期望(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标05: 绝对横向偏移量分布期望: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        self.df_follow = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_follow = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+        self.roadMark_df = self.data.road_mark_df
+        self.roadPos_df = self.data.road_pos_df
+
+        if self.df_follow.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego/2
+        else:
+            return abs(x.right_lateral_distance) - width_ego/2
+
+    def func_laneOffset_abs(self, x):
+        return abs(x.laneOffset)
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+        player_df = self.df
+        ego_df = player_df[player_df.playerId == 1]
+        width_ego = ego_df['dimY'].values.tolist()[0]
+
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        roadPos_ego_df['laneOffset_abs'] = roadPos_ego_df.apply( \
+            lambda x: self.func_laneOffset_abs(x), axis=1)
+        # # 计算到车道边界线距离
+        mean_laneOffset_index = roadPos_ego_df['laneOffset_abs'].mean()
+        self.result['value'] = [round(mean_laneOffset_index, 3)]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset_abs'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 22 - 0
custom/voyah/ICA/cicv_ica_lateral_control06_absolute_center_distance_standard_deviation.json

@@ -0,0 +1,22 @@
+
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "optimal": "0.00001",
+      "multiple": [
+        "1",
+        "10000000"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 158 - 0
custom/voyah/ICA/cicv_ica_lateral_control06_absolute_center_distance_standard_deviation.py

@@ -0,0 +1,158 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+    设计思路:
+    最大横向偏移量
+    zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "绝对横向偏移量分布标准差",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "绝对横向偏移量分布标准差(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标06: 绝对横向偏移量分布标准差: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        self.df_follow = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_follow = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+        self.roadMark_df = self.data.road_mark_df
+        self.roadPos_df = self.data.road_pos_df
+
+        if self.df_follow.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego/2
+        else:
+            return abs(x.right_lateral_distance) - width_ego/2
+
+    def func_laneOffset_abs(self, x):
+        return abs(x.laneOffset)
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+        player_df = self.df
+        ego_df = player_df[player_df.playerId == 1]
+        width_ego = ego_df['dimY'].values.tolist()[0]
+
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        roadPos_ego_df['laneOffset_abs'] = roadPos_ego_df.apply( \
+            lambda x: self.func_laneOffset_abs(x), axis=1)
+        mean_laneOffset_index = roadPos_ego_df['laneOffset_abs'].std()
+        self.result['value'] = [round(mean_laneOffset_index, 3)]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset_abs'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 22 - 0
custom/voyah/ICA/cicv_ica_lateral_control07_center_distance_max.json

@@ -0,0 +1,22 @@
+
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "optimal": "0.00001",
+      "multiple": [
+        "1",
+        "10000000"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 160 - 0
custom/voyah/ICA/cicv_ica_lateral_control07_center_distance_max.py

@@ -0,0 +1,160 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大横向偏移量
+zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "横向距离极大值",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "横向距离极大值(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标07: 横向距离极大值: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        self.df_follow = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_follow = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+        self.roadMark_df = self.data.road_mark_df
+        self.roadPos_df = self.data.road_pos_df
+
+        if self.df_follow.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego/2
+        else:
+            return abs(x.right_lateral_distance) - width_ego/2
+
+    def func_laneOffset_abs(self, x):
+        return abs(x.laneOffset)
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+        player_df = self.df
+        ego_df = player_df[player_df.playerId == 1]
+        width_ego = ego_df['dimY'].values.tolist()[0]
+
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        # # 计算到车道边界线距离
+        roadPos_ego_df['laneOffset_abs'] = roadPos_ego_df.apply(lambda x: self.func_laneOffset_abs(x), axis=1)
+        max_laneOffset_abs_index = roadPos_ego_df['laneOffset_abs'].idxmax()
+        row_with_max_value = roadPos_ego_df.iloc[max_laneOffset_abs_index].laneOffset
+        self.result['value'] = [row_with_max_value]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 22 - 0
custom/voyah/ICA/cicv_ica_lateral_control08_center_distance_min.json

@@ -0,0 +1,22 @@
+
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "optimal": "0.00001",
+      "multiple": [
+        "1",
+        "10000000"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 160 - 0
custom/voyah/ICA/cicv_ica_lateral_control08_center_distance_min.py

@@ -0,0 +1,160 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大横向偏移量
+zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "横向距离极小值",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "横向距离极小值(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标08: 横向距离极小值: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        self.df_follow = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_follow = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+        self.roadMark_df = self.data.road_mark_df
+        self.roadPos_df = self.data.road_pos_df
+
+        if self.df_follow.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego/2
+        else:
+            return abs(x.right_lateral_distance) - width_ego/2
+
+    def func_laneOffset_abs(self, x):
+        return abs(x.laneOffset)
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+        player_df = self.df
+        ego_df = player_df[player_df.playerId == 1]
+        width_ego = ego_df['dimY'].values.tolist()[0]
+
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        # # 计算到车道边界线距离
+        roadPos_ego_df['laneOffset_abs'] = roadPos_ego_df.apply(lambda x: self.func_laneOffset_abs(x), axis=1)
+        min_laneOffset_abs_index = roadPos_ego_df['laneOffset_abs'].idxmin()
+        row_with_min_value = roadPos_ego_df.iloc[min_laneOffset_abs_index].laneOffset
+        self.result['value'] = [row_with_min_value]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 22 - 0
custom/voyah/ICA/cicv_ica_lateral_control09_absolute_position_oscillation_frequency.json

@@ -0,0 +1,22 @@
+
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "optimal": "0.00001",
+      "multiple": [
+        "1",
+        "10000000"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 179 - 0
custom/voyah/ICA/cicv_ica_lateral_control09_absolute_position_oscillation_frequency.py

@@ -0,0 +1,179 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大横向偏移量
+zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "横向相对位置振荡频率",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "横向相对位置振荡频率(Hz)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标09: 横向绝对位置振荡频率: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        self.df_follow = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_follow = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+        self.roadMark_df = self.data.road_mark_df
+        self.roadPos_df = self.data.road_pos_df
+
+        if self.df_follow.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def func_center_line_cycle(self, l):
+        # print("\n穿过y=0的周期数: ")
+        length = len(l)
+        number_peak = 0
+        number_trough = 0
+        for number, value in enumerate(l):
+            if number == 0:
+                if value > l[number + 1] and value > 0:
+                    number_peak += 1
+                if value < l[number + 1] and value < 0:
+                    number_trough += 1
+                continue
+            if number == length - 1:
+                if value > l[number - 1] and value > 0:
+                    number_peak += 1
+                if value < l[number - 1] and value < 0:
+                    number_trough += 1
+                continue
+            if value >= l[number - 1] and value > l[number + 1] and value > 0:
+                number_peak += 1
+            if value < l[number - 1] and value <= l[number + 1] and value < 0:
+                number_trough += 1
+        # print("number_peak: ", number_peak)
+        # print("number_trough: ", number_trough)
+        cycle = min(number_peak, number_trough) - 1
+        # print("cycle: ", cycle)
+        if cycle == -1:
+            return 0
+        return cycle
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+        player_df = self.df
+        ego_df = player_df[player_df.playerId == 1]
+        width_ego = ego_df['dimY'].values.tolist()[0]
+
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        roadPos_ego_list = roadPos_ego_df['laneOffset'].values.tolist()
+        cycle_number = self.func_center_line_cycle(roadPos_ego_list)
+        if not roadPos_ego_df['simTime'].empty:
+            frenquency = cycle_number / roadPos_ego_df['simTime'].values[-1]
+        else:
+            frenquency = cycle_number
+        self.result['value'] = [round(frenquency, 3)]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 22 - 0
custom/voyah/ICA/cicv_ica_lateral_control10_absolute_position_oscillation_difference.json

@@ -0,0 +1,22 @@
+
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "optimal": "0.00001",
+      "multiple": [
+        "1",
+        "10000000"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 211 - 0
custom/voyah/ICA/cicv_ica_lateral_control10_absolute_position_oscillation_difference.py

@@ -0,0 +1,211 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大横向偏移量
+zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "横向相对位置振荡极差",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "横向相对位置振荡极差(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标10: 横向绝对位置振荡极差: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        self.df_follow = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_follow = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+        self.roadMark_df = self.data.road_mark_df
+        self.roadPos_df = self.data.road_pos_df
+
+        if self.df_follow.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def func_center_line_cycle_optical(self, l):
+        # print("\n穿过y=0的周期数: ")
+        length = len(l)
+        number_peak = 0
+        number_trough = 0
+        # 生成新的、只含有波峰波谷的列表
+        new_list = []
+        plus = []
+        minus = []
+        for number, value in enumerate(l):
+            if number == 0:
+                if value > l[number + 1] and value > 0:
+                    number_peak += 1
+                    plus.append(value)
+                if value < l[number + 1] and value < 0:
+                    number_trough += 1
+                    minus.append(value)
+                continue
+
+            if number == length - 1:
+                if value > l[number - 1] and value > 0:
+                    number_peak += 1
+                    plus.append(value)
+                    if len(minus) != 0:
+                        new_list.append(min(minus))
+                        minus = []
+                if value < l[number - 1] and value < 0:
+                    number_trough += 1
+                    minus.append(value)
+                    if len(plus) != 0:
+                        new_list.append(max(plus))
+                        plus = []
+                if len(minus) != 0:
+                    new_list.append(min(minus))
+                    minus = []
+                if len(plus) != 0:
+                    new_list.append(max(plus))
+                    plus = []
+                continue
+
+            if value >= l[number - 1] and value > l[number + 1] and value > 0:
+                number_peak += 1
+                plus.append(value)
+                if len(minus) != 0:
+                    new_list.append(min(minus))
+                    minus = []
+            if value < l[number - 1] and value <= l[number + 1] and value < 0:
+                number_trough += 1
+                minus.append(value)
+                if len(plus) != 0:
+                    new_list.append(max(plus))
+                    plus = []
+        cycle = min(number_peak, number_trough) - 1
+        difference_list = []
+        for i in range(len(new_list) - 1):
+            difference = abs(new_list[i] - new_list[i + 1])
+            difference_list.append(difference)
+        if len(difference_list) == 0:
+            maximum_range = -1
+            # print(f"极差: {maximum_range}")
+        else:
+            maximum_range = max(difference_list)
+            # print(f"极差: {maximum_range}")
+        return maximum_range
+
+    def data_analyze(self):
+        roadPos_df = self.roadPos_df
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        # laneOffset_max = max(roadPos_ego_df['laneOffset'])
+        # laneOffset_min = min(roadPos_ego_df['laneOffset'])
+        # oscillation_difference = laneOffset_max-laneOffset_min
+        # self.result['value'] = round(oscillation_difference, 3)
+
+        roadPos_ego_list = roadPos_ego_df['laneOffset'].values.tolist()
+        maximum_range = self.func_center_line_cycle_optical(roadPos_ego_list)
+
+        self.result['value'] = [round(maximum_range, 3)]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 22 - 0
custom/voyah/ICA/cicv_ica_lateral_control11_heading_deviation_max.json

@@ -0,0 +1,22 @@
+
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "optimal": "0.00001",
+      "multiple": [
+        "1",
+        "10000000"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 164 - 0
custom/voyah/ICA/cicv_ica_lateral_control11_heading_deviation_max.py

@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大航向偏差角
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "最大航向角偏差",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "最大航向角偏差(rad)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标11: 最大航向角偏差: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        self.df_follow = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_follow = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+        self.roadMark_df = self.data.road_mark_df
+        self.roadPos_df = self.data.road_pos_df
+
+        if self.df_follow.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego/2
+        else:
+            return abs(x.right_lateral_distance) - width_ego/2
+
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+        player_df = self.df
+        road_mark_df = self.roadMark_df
+        ego_df = player_df[player_df.playerId == 1].reset_index(drop=True)
+        # width_ego = ego_df['dimY'].values.tolist()[0]
+
+        # 左车道线曲率,右车道线曲率,求二者平均值,计算车道线曲率,再与自车朝向相减
+        road_mark_left_df = road_mark_df[road_mark_df.id == 0].reset_index(drop=True)
+        road_mark_right_df = road_mark_df[road_mark_df.id == 2].reset_index(drop=True)
+        road_mark_left_df['curvHor_left'] = road_mark_left_df['curvHor']
+        road_mark_left_df['curvHor_right'] = road_mark_right_df['curvHor']
+        road_mark_left_df['curvHor_middle'] = road_mark_left_df[['curvHor_left', 'curvHor_right']].apply( \
+            lambda x: (x['curvHor_left'] + x['curvHor_right'])/2, axis=1)
+        ego_df['curvHor_middle'] = road_mark_left_df['curvHor_middle']
+        ego_df['heading_deviation_abs'] = ego_df[['curvHor_middle', 'posH']].apply( \
+            lambda x: abs(x['posH'] - x['curvHor_middle']), axis=1)
+        row_with_max_value = max(ego_df['heading_deviation_abs'])
+
+        self.result['value'] = [row_with_max_value]
+        self.time_list_follow = ego_df['simTime'].values.tolist()
+        self.frame_list_follow = ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = ego_df['heading_deviation_abs'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 22 - 0
custom/voyah/ICA/cicv_ica_lateral_control12_relative_position_oscillation_frequency.json

@@ -0,0 +1,22 @@
+
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "optimal": "0.00001",
+      "multiple": [
+        "1",
+        "10000000"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 236 - 0
custom/voyah/ICA/cicv_ica_lateral_control12_relative_position_oscillation_frequency.py

@@ -0,0 +1,236 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大横向偏移量
+zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "横向相对位置振荡频率",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "横向相对位置振荡频率(Hz)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标12: 横向相对位置振荡频率: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        self.df_follow = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_follow = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+        self.roadMark_df = self.data.road_mark_df
+        self.roadPos_df = self.data.road_pos_df
+
+        if self.df_follow.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def func_center_line_cycle(self, l):
+        # print("\n穿过y=0的周期数: ")
+        length = len(l)
+        number_peak = 0
+        number_trough = 0
+        for number, value in enumerate(l):
+            if number == 0:
+                if value > l[number + 1] and value > 0:
+                    number_peak += 1
+                if value < l[number + 1] and value < 0:
+                    number_trough += 1
+                continue
+            if number == length - 1:
+                if value > l[number - 1] and value > 0:
+                    number_peak += 1
+                if value < l[number - 1] and value < 0:
+                    number_trough += 1
+                continue
+            if value >= l[number - 1] and value > l[number + 1] and value > 0:
+                number_peak += 1
+            if value < l[number - 1] and value <= l[number + 1] and value < 0:
+                number_trough += 1
+        # print("number_peak: ", number_peak)
+        # print("number_trough: ", number_trough)
+        cycle = min(number_peak, number_trough) - 1
+        # print("cycle: ", cycle)
+        if cycle == -1:
+            return 0
+        return cycle
+
+    def func_normal_cycle_optical(self, l):
+        # print("正常的周期数: ")
+        length = len(l)
+        number_peak = 0
+        number_trough = 0
+        # value_peak = []
+        # value_trough = []
+        # 生成新的、只含有波峰波谷的列表
+        new_list = []
+        for number, value in enumerate(l):
+            if number == 0:
+                if value > l[number + 1]:
+                    number_peak += 1
+                    new_list.append(value)
+                if value < l[number + 1]:
+                    number_trough += 1
+                    new_list.append(value)
+                continue
+            if number == length - 1:
+                if value > l[number - 1]:
+                    number_peak += 1
+                    new_list.append(value)
+                if value < l[number - 1]:
+                    number_trough += 1
+                    new_list.append(value)
+                continue
+            if value >= l[number - 1] and value > l[number + 1]:
+                number_peak += 1
+                new_list.append(value)
+            if value < l[number - 1] and value <= l[number + 1]:
+                number_trough += 1
+                new_list.append(value)
+        if abs(number_peak - number_trough) > 1:
+            # print("计算波峰波谷有误")
+            pass
+        else:
+            # print("number_peak: ", number_peak)
+            # print("number_trough: ", number_trough)
+            cycle = max(number_peak, number_trough) - 1
+            # print("cycle: ", cycle)
+        # print(f"value_peak: {value_peak}")
+        # print(f"value_trough: {value_trough}")
+
+        # print(f"new_list: {new_list}")
+        difference_list = []
+        for i in range(len(new_list) - 1):
+            difference = abs(new_list[i] - new_list[i + 1])
+            difference_list.append(difference)
+        if len(difference_list) == 0:
+            maximum_range = -1
+            # print(f"极差: {maximum_range}")
+        else:
+            maximum_range = max(difference_list)
+            # print(f"极差: {maximum_range}")
+        return cycle
+
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+        player_df = self.df
+        ego_df = player_df[player_df.playerId == 1]
+        width_ego = ego_df['dimY'].values.tolist()[0]
+
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        roadPos_ego_list = roadPos_ego_df['laneOffset'].values.tolist()
+        cycle_number = self.func_normal_cycle_optical(roadPos_ego_list)
+        if not roadPos_ego_df['simTime'].empty:
+            frenquency = cycle_number / roadPos_ego_df['simTime'].values[-1]
+        else:
+            frenquency = cycle_number
+        self.result['value'] = [round(frenquency, 3)]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 22 - 0
custom/voyah/ICA/cicv_ica_lateral_control13_relative_position_oscillation_difference.json

@@ -0,0 +1,22 @@
+
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "optimal": "0.00001",
+      "multiple": [
+        "1",
+        "10000000"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 186 - 0
custom/voyah/ICA/cicv_ica_lateral_control13_relative_position_oscillation_difference.py

@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大横向偏移量
+zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "横向相对位置振荡极差",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "横向相对位置振荡极差(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标13: 横向相对位置振荡极差: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        self.df_follow = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_follow = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+        self.roadMark_df = self.data.road_mark_df
+        self.roadPos_df = self.data.road_pos_df
+
+        if self.df_follow.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def func_normal_cycle_optical(self, l):
+        # print("正常的周期数: ")
+        length = len(l)
+        number_peak = 0
+        number_trough = 0
+        # 生成新的、只含有波峰波谷的列表
+        new_list = []
+        for number, value in enumerate(l):
+            if number == 0:
+                if value > l[number + 1]:
+                    number_peak += 1
+                    new_list.append(value)
+                if value < l[number + 1]:
+                    number_trough += 1
+                    new_list.append(value)
+                continue
+            if number == length - 1:
+                if value > l[number - 1]:
+                    number_peak += 1
+                    new_list.append(value)
+                if value < l[number - 1]:
+                    number_trough += 1
+                    new_list.append(value)
+                continue
+            if value >= l[number - 1] and value > l[number + 1]:
+                number_peak += 1
+                new_list.append(value)
+            if value < l[number - 1] and value <= l[number + 1]:
+                number_trough += 1
+                new_list.append(value)
+        if abs(number_peak - number_trough) > 1:
+            pass
+        else:
+            cycle = max(number_peak, number_trough) - 1
+        difference_list = []
+        for i in range(len(new_list) - 1):
+            difference = abs(new_list[i] - new_list[i + 1])
+            difference_list.append(difference)
+        if len(difference_list) == 0:
+            maximum_range = -1
+            # print(f"极差: {maximum_range}")
+        else:
+            maximum_range = max(difference_list)
+            # print(f"极差: {maximum_range}")
+        return maximum_range
+
+    def data_analyze(self):
+        roadPos_df = self.roadPos_df
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        roadPos_ego_list = roadPos_ego_df['laneOffset'].values.tolist()
+        oscillation_difference = self.func_normal_cycle_optical(roadPos_ego_list)
+        self.result['value'] = [round(oscillation_difference, 3)]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control01_delay_time_cruise.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 192 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control01_delay_time_cruise.py

@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        # self.stable_start_time_cruise = None
+        self.stable_average_speed = None
+        self.delay_time_cruise = None
+
+        self.result = {
+            "name": "定速巡航延迟时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "定速巡航延迟时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"定速巡航延迟时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Line"].copy()  # 数字3对应ICA的 LLC_Follow_Line
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _find_stable_speed_cruise(self, window_size, percent_deviation, set_value):
+        """
+        在给定的速度数据中查找稳定的巡航速度段,并计算该段的平均速度。
+
+        Args:
+            window_size (int): 滑动窗口的大小,表示用于计算平均速度的速度数据点数量。
+            percent_deviation (float): 设定值的允许偏差百分比。
+            set_value (float): 期望的稳定速度设定值。
+
+        Returns:
+            None
+
+        """
+        # speed_data = self.df['speedX'].values
+        speed_data = self.df_ica['speedX'].values  # .tolist()
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_speed = None
+
+        for i in range(len(speed_data) - window_size + 1):
+            window_data = speed_data[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_speed = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(speed_data) - window_size + 1:
+                    next_window_data = speed_data[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_speed = (stable_average_speed * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_speed = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_cruise = self.df['simTime'].iloc[stable_start]
+        self.stable_average_speed = stable_average_speed
+
+    def data_analyze(self):
+        change_indices = self.df_ica[self.df_ica['set_cruise_speed'] != self.df_ica['set_cruise_speed'].shift()].index
+        print(f"Change indices of set speed: {change_indices}")
+
+        set_cruise_speed = self.df_ica.loc[change_indices[0], 'set_cruise_speed']
+        self._find_stable_speed_cruise(window_size=4, percent_deviation=5, set_value=set_cruise_speed)
+
+        if not change_indices.empty:
+            first_change_index = change_indices[change_indices != 0].min()
+            set_cruise_speed_at_change = self.df_ica.loc[first_change_index, 'set_cruise_speed']
+            timestamp_at_change = self.df_ica.loc[first_change_index, 'simTime']
+            print(f"Set speed at first change: {set_cruise_speed_at_change}, Timestamp: {timestamp_at_change}")
+
+            target_speed = (self.stable_average_speed + self.df_ica.loc[first_change_index, 'speedX']) / 2
+            closest_index = (self.df_ica['speedX'] - target_speed).abs().idxmin()
+            closest_current_speed = self.df_ica.loc[closest_index, 'speedX']
+            closest_timestamp = self.df_ica.loc[closest_index, 'simTime']
+            print(f"Closest speed: {closest_current_speed} at time: {closest_timestamp}")
+
+            self.delay_time_cruise = closest_timestamp - timestamp_at_change
+            self.result['value'] = [round(self.delay_time_cruise, 3)]
+            print(f"Delay time: {self.delay_time_cruise}")
+
+        else:
+            self.delay_time_cruise = 0
+            self.result['value'] = [round(self.delay_time_cruise, 3)]
+            print("No valid change point for further calculation.")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[delay_time_cruise:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control02_rise_time_cruise.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 222 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control02_rise_time_cruise.py

@@ -0,0 +1,222 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.stable_average_speed = None
+        self.rise_time_cruise = None
+
+        self.result = {
+            "name": "定速巡航上升时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "定速巡航上升时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"定速巡航上升时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Line"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _get_first_change_index_cruise(self):
+        """
+        获取数据集中第一次巡航速度发生变化的索引。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在巡航速度发生变化的索引,则返回第一个发生变化的索引(int类型);
+            如果不存在,则返回None。
+
+        """
+        change_indices = self.df_ica[self.df_ica['set_cruise_speed'] != self.df_ica['set_cruise_speed'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def _find_stable_speed_cruise(self, window_size, percent_deviation, set_value):
+        """
+        在给定的速度数据中查找稳定的巡航速度段,并计算该段的平均速度。
+
+        Args:
+            window_size (int): 滑动窗口的大小,表示用于计算平均速度的速度数据点数量。
+            percent_deviation (float): 设定值的允许偏差百分比。
+            set_value (float): 期望的稳定速度设定值。
+
+        Returns:
+            None
+
+        """
+        # speed_data = self.df['speedX'].values
+        speed_data = self.df_ica['speedX'].values   #.tolist()
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_speed = None
+
+        for i in range(len(speed_data) - window_size + 1):
+            window_data = speed_data[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_speed = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(speed_data) - window_size + 1:
+                    next_window_data = speed_data[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_speed = (stable_average_speed * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_speed = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_cruise = self.df['simTime'].iloc[stable_start]
+        self.stable_average_speed = stable_average_speed
+
+    def _find_closest_time_stamp_cruise(self, df, target_speed, start_index):
+        """
+        在给定的数据帧df中,从start_index索引位置开始,查找与目标速度target_speed最接近的时间戳。
+
+        Args:
+            df (pd.DataFrame): 包含速度和时间戳等信息的数据帧,需要至少包含'speedX'和'simTime'两列。
+            target_speed (float): 目标速度值,用于在数据帧中查找最接近此值的时间戳。
+            start_index (int): 开始查找的索引位置,即在数据帧df中从该索引位置开始向后查找。
+
+        Returns:
+            pd.Timestamp: 与目标速度最接近的时间戳。
+
+        """
+        subset = df.loc[start_index + 1:]
+        speed_diff = np.abs(subset['speedX'] - target_speed)
+        closest_index = speed_diff.idxmin()
+        closest_timestamp = subset.loc[closest_index, 'simTime']
+        return closest_timestamp
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_cruise()
+
+        set_cruise_speed = self.df_ica.loc[first_change_index, 'set_cruise_speed']
+        self._find_stable_speed_cruise(window_size=4, percent_deviation=5, set_value=set_cruise_speed)
+
+        initial_speed = self.df_ica.loc[first_change_index, 'speedX']
+
+        target_speed_90 = initial_speed + (self.stable_average_speed - initial_speed) * 0.9
+        target_speed_10 = initial_speed + (self.stable_average_speed - initial_speed) * 0.1
+
+        timestamp_at_10 = self._find_closest_time_stamp_cruise(self.df_ica, target_speed_10, first_change_index)
+        timestamp_at_90 = self._find_closest_time_stamp_cruise(self.df_ica, target_speed_90, first_change_index)
+
+        print(f"Closest speed at 10% range from set speed: {timestamp_at_10}")
+        print(f"Closest speed at 90% range from set speed: {timestamp_at_90}")
+
+        self.rise_time_cruise = timestamp_at_90 - timestamp_at_10
+        self.result['value'] = [round(self.rise_time_cruise, 3)]
+        print(f"Rise time: {self.rise_time_cruise}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[rise_time_cruise:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control03_peak_time_cruise.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 149 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control03_peak_time_cruise.py

@@ -0,0 +1,149 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.peak_time_cruise = None
+
+        self.result = {
+            "name": "定速巡航峰值时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "定速巡航峰值时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"定速巡航峰值时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Line"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _get_first_change_index_cruise(self):
+        """
+        获取数据集中第一次巡航速度发生变化的索引。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在巡航速度发生变化的索引,则返回第一个发生变化的索引(int类型);
+            如果不存在,则返回None。
+
+        """
+        change_indices = self.df_ica[self.df_ica['set_cruise_speed'] != self.df_ica['set_cruise_speed'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_cruise()
+
+        if not first_change_index:
+            self.peak_time_cruise = 0
+            self.result['value'] = [round(self.peak_time_cruise, 3)]
+            print(f"peak_time_cruise: {self.peak_time_cruise}")
+        else:
+            start_time = self.df_ica.loc[first_change_index, 'simTime']
+            peak_time = self.df_ica.loc[self.df_ica['speedX'].idxmax(), 'simTime']
+            self.peak_time_cruise = peak_time - start_time
+            self.result['value'] = [round(self.peak_time_cruise, 3)]
+            print(f"peak_time_cruise: {self.peak_time_cruise}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[peak_time_cruise:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control04_overshoot_cruise.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 205 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control04_overshoot_cruise.py

@@ -0,0 +1,205 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.stable_average_speed = None
+        self.overshoot_cruise = None
+
+        self.result = {
+            "name": "定速巡航超调量",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "定速巡航超调量(%)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"定速巡航超调量: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Line"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _get_first_change_index_cruise(self):
+        """
+        获取数据集中第一次巡航速度发生变化的索引。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在巡航速度发生变化的索引,则返回第一个发生变化的索引(int类型);
+            如果不存在,则返回None。
+
+        """
+        change_indices = self.df_ica[self.df_ica['set_cruise_speed'] != self.df_ica['set_cruise_speed'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def _find_stable_speed_cruise(self, window_size, percent_deviation, set_value):
+        """
+        在给定的速度数据中查找稳定的巡航速度段,并计算该段的平均速度。
+
+        Args:
+            window_size (int): 滑动窗口的大小,表示用于计算平均速度的速度数据点数量。
+            percent_deviation (float): 设定值的允许偏差百分比。
+            set_value (float): 期望的稳定速度设定值。
+
+        Returns:
+            None
+
+        """
+        # speed_data = self.df['speedX'].values
+        speed_data = self.df_ica['speedX'].values  # .tolist()
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_speed = None
+
+        for i in range(len(speed_data) - window_size + 1):
+            window_data = speed_data[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_speed = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(speed_data) - window_size + 1:
+                    next_window_data = speed_data[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_speed = (stable_average_speed * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_speed = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_cruise = self.df['simTime'].iloc[stable_start]
+        self.stable_average_speed = stable_average_speed
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_cruise()
+
+        set_cruise_speed = self.df_ica.loc[first_change_index, 'set_cruise_speed']
+        self._find_stable_speed_cruise(window_size=4, percent_deviation=5, set_value=set_cruise_speed)
+
+        if not first_change_index:
+            self.overshoot_cruise = 0
+        else:
+            initial_speed = self.df.loc[first_change_index, 'speedX']
+
+            if initial_speed > self.stable_average_speed:
+                self.overshoot_cruise = (self.stable_average_speed - self.df[
+                    'speedX'].min()) * 100 / self.stable_average_speed
+            elif initial_speed < self.stable_average_speed:
+                self.overshoot_cruise = (self.df[
+                                             'speedX'].max() - self.stable_average_speed) * 100 / self.stable_average_speed
+            else:
+                self.overshoot_cruise = 0
+
+        self.result['value'] = [round(self.overshoot_cruise, 3)]
+        print(f"overshoot_cruise: {self.overshoot_cruise}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[overshoot_cruise:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control05_steady_error_cruise.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 202 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control05_steady_error_cruise.py

@@ -0,0 +1,202 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.stable_average_speed = None
+        self.steady_error_cruise = None
+
+        self.result = {
+            "name": "定速巡航速度稳态误差",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "定速巡航速度稳态误差(%)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"定速巡航速度稳态误差: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Line"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _get_first_change_index_cruise(self):
+        """
+        获取数据集中第一次巡航速度发生变化的索引。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在巡航速度发生变化的索引,则返回第一个发生变化的索引(int类型);
+            如果不存在,则返回None。
+
+        """
+        change_indices = self.df_ica[self.df_ica['set_cruise_speed'] != self.df_ica['set_cruise_speed'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def _find_stable_speed_cruise(self, window_size, percent_deviation, set_value):
+        """
+        在给定的速度数据中查找稳定的巡航速度段,并计算该段的平均速度。
+
+        Args:
+            window_size (int): 滑动窗口的大小,表示用于计算平均速度的速度数据点数量。
+            percent_deviation (float): 设定值的允许偏差百分比。
+            set_value (float): 期望的稳定速度设定值。
+
+        Returns:
+            None
+
+        """
+        # speed_data = self.df['speedX'].values
+        speed_data = self.df_ica['speedX'].values  # .tolist()
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_speed = None
+
+        for i in range(len(speed_data) - window_size + 1):
+            window_data = speed_data[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_speed = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(speed_data) - window_size + 1:
+                    next_window_data = speed_data[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_speed = (stable_average_speed * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_speed = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_cruise = self.df['simTime'].iloc[stable_start]
+        self.stable_average_speed = stable_average_speed
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_cruise()
+
+        if not first_change_index:
+            self.steady_error_cruise = 0
+            self.result['value'] = [round(self.steady_error_cruise, 3)]
+            print(f"steady_error_cruise: {self.steady_error_cruise}")
+        else:
+            set_cruise_speed = self.df_ica.loc[first_change_index, 'set_cruise_speed']
+            self._find_stable_speed_cruise(window_size=4, percent_deviation=5, set_value=set_cruise_speed)
+
+            if self.stable_average_speed:
+                self.steady_error_cruise = (self.stable_average_speed - set_cruise_speed) * 100 / self.stable_average_speed
+            else:
+                self.steady_error_cruise = 0
+                raise ValueError("Stable average speed is None.")
+
+            self.result['value'] = [round(self.steady_error_cruise, 3)]
+            print(f"steady_error_cruise: {self.steady_error_cruise}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[steady_error_cruise:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control06_delay_time_THW.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 191 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control06_delay_time_THW.py

@@ -0,0 +1,191 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        # self.stable_start_time_THW = None
+        self.stable_average_THW = None
+        self.delay_time_THW = None
+
+        self.result = {
+            "name": "跟车延迟时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "跟车延迟时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"跟车延迟时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Vehicle"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _find_stable_THW(self, window_size, percent_deviation, set_value):
+        """
+        在给定的数据窗口中查找稳定跟车时距离THW,并计算该段内THW的平均值。
+
+        Args:
+            window_size (int): 窗口大小,表示在数据中寻找稳定段时考虑的连续数据点数量。
+            percent_deviation (float): THW值相对于设定值的允许偏差百分比。
+            set_value (float): THW的设定值。
+
+        Returns:
+            None
+
+        """
+        THW = self.df_ica['THW'].values
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_THW = None
+
+        for i in range(len(THW) - window_size + 1):
+            window_data = THW[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_THW = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(THW) - window_size + 1:
+                    next_window_data = THW[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_THW = (stable_average_THW * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_THW = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_THW = self.df_ica['simTime'].iloc[stable_start]
+        self.stable_average_THW = stable_average_THW
+
+    def data_analyze(self):
+        change_indices = self.df_ica[self.df_ica['set_headway_time'] != self.df_ica['set_headway_time'].shift()].index
+        print(f"Change indices of set speed: {change_indices}")
+
+        set_headway_time = self.df_ica.loc[change_indices[0], 'set_headway_time']
+        self._find_stable_THW(window_size=4, percent_deviation=5, set_value=set_headway_time)
+
+        if not change_indices.empty:
+            first_change_index = change_indices[change_indices != 0].min()
+            set_THW_at_change = self.df_ica.loc[first_change_index, 'set_headway_time']
+            timestamp_at_change = self.df_ica.loc[first_change_index, 'simTime']
+            print(f"Set THW at first change: {set_THW_at_change}, Timestamp: {timestamp_at_change}")
+
+            target_THW = (self.stable_average_THW + self.df_ica.loc[first_change_index, 'set_headway_time']) / 2
+            closest_index = (self.df_ica['set_headway_time'] - target_THW).abs().idxmin()
+            closest_current_THW = self.df_ica.loc[closest_index, 'set_headway_time']
+            closest_timestamp = self.df_ica.loc[closest_index, 'simTime']
+            print(f"Closest speed: {closest_current_THW} at time: {closest_timestamp}")
+
+            self.delay_time_THW = closest_timestamp - timestamp_at_change
+            self.result['value'] = [round(self.delay_time_THW, 3)]
+            print(f"Delay time: {self.delay_time_THW}")
+
+        else:
+            self.delay_time_THW = 0
+            self.result['value'] = [round(self.delay_time_THW, 3)]
+            print("No valid change point for further calculation.")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[delay_time_THW:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control07_rise_time_THW.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 231 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control07_rise_time_THW.py

@@ -0,0 +1,231 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        # self.stable_start_time_THW = None
+        self.stable_average_THW = None
+        self.rise_time_THW = None
+
+        self.result = {
+            "name": "跟车上升时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "跟车上升时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"跟车上升时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Vehicle"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _get_first_change_index_THW(self):
+        """
+        获取DataFrame中'set_headway_time'列首次发生变化的索引值。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在变化,则返回首次发生变化的索引值(int类型),否则返回None。
+
+        """
+        change_indices = self.df_ica[self.df_ica['set_headway_time'] != self.df_ica['set_headway_time'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def _find_stable_THW(self, window_size, percent_deviation, set_value):
+        """
+        在给定的数据窗口中查找稳定跟车时距离THW,并计算该段内THW的平均值。
+
+        Args:
+            window_size (int): 窗口大小,表示在数据中寻找稳定段时考虑的连续数据点数量。
+            percent_deviation (float): THW值相对于设定值的允许偏差百分比。
+            set_value (float): THW的设定值。
+
+        Returns:
+            None
+
+        """
+        THW = self.df_ica['THW'].values
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_THW = None
+
+        for i in range(len(THW) - window_size + 1):
+            window_data = THW[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_THW = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(THW) - window_size + 1:
+                    next_window_data = THW[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_THW = (stable_average_THW * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_THW = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_THW = self.df_ica['simTime'].iloc[stable_start]
+        self.stable_average_THW = stable_average_THW
+
+    def _find_closest_time_stamp_THW(self, df, target_THW, start_index):
+        """
+        在DataFrame中找到与目标THW值最接近的时间戳。
+
+        Args:
+            df (pandas.DataFrame): 包含'THW'和'simTime'列的DataFrame,其中'THW'表示目标变量,'simTime'表示时间戳。
+            target_THW (float): 目标THW值。
+            start_index (int): 开始搜索的索引位置(不包含)。
+
+        Returns:
+            pandas.Timestamp: 与目标THW值最接近的时间戳。
+
+        """
+        subset = df.loc[start_index + 1:]
+        THW_diff = np.abs(subset['THW'] - target_THW)
+        closest_index = THW_diff.idxmin()
+        closest_timestamp = subset.loc[closest_index, 'simTime']
+        return closest_timestamp
+
+    def data_analyze(self):
+        """
+        计算巡航时从初THW到达稳定THW的90%的所需时间(rise time)。
+
+        Args:
+            无。
+
+        Returns:
+            无返回值,但会设置实例属性self.rise_time_THW为计算得到的rise time。
+
+        """
+        first_change_index = self._get_first_change_index_THW()
+
+        set_headway_time = self.df_ica.loc[first_change_index, 'set_headway_time']
+        self._find_stable_THW(window_size=4, percent_deviation=5, set_value=set_headway_time)
+
+        initial_THW = self.df_ica.loc[first_change_index, 'THW']
+
+        target_THW_90 = initial_THW + (self.stable_average_THW - initial_THW) * 0.9
+        target_THW_10 = initial_THW + (self.stable_average_THW - initial_THW) * 0.1
+
+        timestamp_at_10 = self._find_closest_time_stamp_THW(self.df_ica, target_THW_10, first_change_index)
+        timestamp_at_90 = self._find_closest_time_stamp_THW(self.df_ica, target_THW_90, first_change_index)
+
+        print(f"Closest speed at 10% range from set speed: {timestamp_at_10}")
+        print(f"Closest speed at 90% range from set speed: {timestamp_at_90}")
+
+        self.rise_time_THW = timestamp_at_90 - timestamp_at_10
+        self.result['value'] = [round(self.rise_time_THW, 3)]
+        print(f"Rise time: {self.rise_time_THW}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[rise_time_THW:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control08_peak_time_THW.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 148 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control08_peak_time_THW.py

@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.peak_time_THW = None
+
+        self.result = {
+            "name": "跟车峰值时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "跟车峰值时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"跟车峰值时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Vehicle"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _get_first_change_index_THW(self):
+        """
+        获取DataFrame中'set_headway_time'列首次发生变化的索引值。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在变化,则返回首次发生变化的索引值(int类型),否则返回None。
+
+        """
+        change_indices = self.df_ica[self.df_ica['set_headway_time'] != self.df_ica['set_headway_time'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_THW()
+
+        if not first_change_index:
+            self.peak_time_THW = 0
+            self.result['value'] = [round(self.peak_time_THW, 3)]
+            print(f"peak_time_THW: {self.peak_time_THW}")
+        else:
+            start_time = self.df_ica.loc[first_change_index, 'simTime']
+            peak_time = self.df_ica.loc[self.df_ica['THW'].idxmax(), 'simTime']
+            self.peak_time_THW = peak_time - start_time
+            self.result['value'] = [round(self.peak_time_THW, 3)]
+            print(f"peak_time_THW: {self.peak_time_THW}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[peak_time_THW:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control09_overshoot_THW.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 201 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control09_overshoot_THW.py

@@ -0,0 +1,201 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.stable_average_THW = None
+        self.overshoot_THW = None
+
+        self.result = {
+            "name": "跟车超调量",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "跟车超调量(%)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"跟车超调量: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Vehicle"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _get_first_change_index_THW(self):
+        """
+        获取DataFrame中'set_headway_time'列首次发生变化的索引值。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在变化,则返回首次发生变化的索引值(int类型),否则返回None。
+
+        """
+        change_indices = self.df_ica[self.df_ica['set_headway_time'] != self.df_ica['set_headway_time'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def _find_stable_THW(self, window_size, percent_deviation, set_value):
+        """
+        在给定的数据窗口中查找稳定跟车时距离THW,并计算该段内THW的平均值。
+
+        Args:
+            window_size (int): 窗口大小,表示在数据中寻找稳定段时考虑的连续数据点数量。
+            percent_deviation (float): THW值相对于设定值的允许偏差百分比。
+            set_value (float): THW的设定值。
+
+        Returns:
+            None
+
+        """
+        THW = self.df_ica['THW'].values
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_THW = None
+
+        for i in range(len(THW) - window_size + 1):
+            window_data = THW[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_THW = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(THW) - window_size + 1:
+                    next_window_data = THW[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_THW = (stable_average_THW * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_THW = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_THW = self.df_ica['simTime'].iloc[stable_start]
+        self.stable_average_THW = stable_average_THW
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_THW()
+
+        set_headway_time = self.df_ica.loc[first_change_index, 'set_headway_time']
+        self._find_stable_THW(window_size=4, percent_deviation=5, set_value=set_headway_time)
+
+        if not first_change_index:
+            self.overshoot_THW = 0
+        else:
+            initial_THW = self.df_ica.loc[first_change_index, 'THW']
+
+            if initial_THW > self.stable_average_THW:
+                self.overshoot_THW = (self.stable_average_THW - self.df_ica['THW'].min()) * 100 / self.stable_average_THW
+            elif initial_THW < self.stable_average_THW:
+                self.overshoot_THW = (self.df_ica['THW'].max() - self.stable_average_THW) * 100 / self.stable_average_THW
+            else:
+                self.overshoot_THW = 0
+
+        self.result['value'] = [round(self.overshoot_THW, 3)]
+        print(f"overshoot_THW: {self.overshoot_THW}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[overshoot_THW:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control10_steady_error_THW.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "-1",
+      "optimal": "1.0",
+      "multiple": [
+        "0.5",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 201 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control10_steady_error_THW.py

@@ -0,0 +1,201 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.stable_average_THW = None
+        self.steady_error_THW = None
+
+        self.result = {
+            "name": "跟车速度稳态误差",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "跟车速度稳态误差(%)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"跟车速度稳态误差: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "LLC_Follow_Vehicle"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def _get_first_change_index_THW(self):
+        """
+        获取DataFrame中'set_headway_time'列首次发生变化的索引值。
+
+        Args:
+            无参数。
+
+        Returns:
+            Union[int, None]: 如果存在变化,则返回首次发生变化的索引值(int类型),否则返回None。
+
+        """
+        change_indices = self.df_ica[self.df_ica['set_headway_time'] != self.df_ica['set_headway_time'].shift()].index
+        if not change_indices.empty:
+            first_change_index = change_indices.min()
+        else:
+            first_change_index = None
+        return first_change_index
+
+    def _find_stable_THW(self, window_size, percent_deviation, set_value):
+        """
+        在给定的数据窗口中查找稳定跟车时距离THW,并计算该段内THW的平均值。
+
+        Args:
+            window_size (int): 窗口大小,表示在数据中寻找稳定段时考虑的连续数据点数量。
+            percent_deviation (float): THW值相对于设定值的允许偏差百分比。
+            set_value (float): THW的设定值。
+
+        Returns:
+            None
+
+        """
+        THW = self.df_ica['THW'].values
+        deviation = set_value * (percent_deviation / 100)
+        stable_start = None
+        stable_average_THW = None
+
+        for i in range(len(THW) - window_size + 1):
+            window_data = THW[i:i + window_size]
+
+            if all(set_value - deviation <= s <= set_value + deviation for s in window_data):
+                if stable_start is None:
+                    stable_start = i
+                    stable_end = i + window_size - 1
+                    stable_average_THW = np.mean(window_data)
+
+                j = i + window_size
+                while j < len(THW) - window_size + 1:
+                    next_window_data = THW[j:j + window_size]
+
+                    if all(set_value - deviation <= s <= set_value + deviation for s in next_window_data):
+                        stable_end = j + window_size - 1
+                        stable_average_THW = (stable_average_THW * (j - stable_start) + sum(next_window_data)) / (
+                                j - stable_start + window_size)
+                        j += window_size
+                    else:
+                        stable_start = j + window_size - 1
+                        stable_end = i + window_size - 1
+                        stable_average_THW = np.mean(window_data)
+                        break
+
+        # self.stable_start_time_THW = self.df_ica['simTime'].iloc[stable_start]
+        self.stable_average_THW = stable_average_THW
+
+    def data_analyze(self):
+        first_change_index = self._get_first_change_index_THW()
+
+
+        if not first_change_index:
+            self.steady_error_THW = 0
+            self.result['value'] = [round(self.steady_error_THW, 3)]
+            print(f"steady_error_THW: {self.steady_error_THW}")
+        else:
+            set_headway_time = self.df_ica.loc[first_change_index, 'set_headway_time']
+            self._find_stable_THW(window_size=4, percent_deviation=5, set_value=set_headway_time)
+
+            if self.stable_average_THW:
+                self.steady_error_THW = (self.stable_average_THW - set_headway_time) * 100 / self.stable_average_THW
+            else:
+                self.steady_error_THW = 0
+                raise ValueError("Stable average speed is None.")
+
+            self.result['value'] = [round(self.steady_error_THW, 3)]
+            print(f"steady_error_THW: {self.steady_error_THW}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        # time_list = self.ego_df['simTime'].values.tolist()
+        # graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = self.result['value'][0] if not self.df_ica.empty else '-'
+        self.result['tableData']['max'] = '-'
+        self.result['tableData']['min'] = '-'
+
+        # zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = []
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 1.2]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[steady_error_THW:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control11_reasonable_acceleration_percentage.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "1",
+      "optimal": "100",
+      "multiple": [
+        "0.6",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 129 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control11_reasonable_acceleration_percentage.py

@@ -0,0 +1,129 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.percentage_accel = 0
+
+        self.result = {
+            "name": "合理加减速度占比",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "合理加减速度占比(%)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"合理加减速度占比: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def data_analyze(self):
+        col_list = ['simTime', 'simFrame', 'playerId', 'v', 'accel', 'lon_acc_roc']  # target_id
+        df = self.df_ica[col_list].copy()
+        count_accel_in_range = 0
+        total_accel_frames = 0
+
+        self.graph_list = df['accel'].values.tolist()
+        filtered_data = df[(df['accel'] >= -5) & (df['accel'] <= 4)]
+        count_accel_in_range = count_accel_in_range + len(filtered_data)
+        total_accel_frames = total_accel_frames + len(df)
+        self.percentage_accel = (count_accel_in_range / total_accel_frames) * 100
+
+        self.result['value'] = [round(self.percentage_accel, 3)]
+        print(f"percentage_accel: {self.percentage_accel}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 100]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[percentage_accel:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 21 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control12_reasonable_acceleration_change_rate_percentage.json

@@ -0,0 +1,21 @@
+{
+  "priority": "1",
+  "paramList": [
+    {
+      "kind": "1",
+      "optimal": "100",
+      "multiple": [
+        "0.6",
+        "2"
+      ],
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ]
+    }
+  ]
+}

+ 133 - 0
custom/voyah/ICA/cicv_ica_longitudinal_control12_reasonable_acceleration_change_rate_percentage.py

@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhanghaiwen, yangzihao
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+        self.graph_list = []
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ica = pd.DataFrame()
+
+        self.percentage_lon_acc_roc = 0
+
+        self.result = {
+            "name": "合理加减速度变化率占比",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "合理加减速度变化率占比(%)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"合理加减速度变化率占比: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.ego_data
+        active_status_list = ["LLC_Follow_Line", "LLC_Follow_Vehicle", "Only_Longitudinal_Control"]
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'].isin(active_status_list)].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Shut_off"].copy()  # 数字3对应ICA的Active
+        # self.df_ica = self.df[self.df['ACC_status'] == "Active"].copy()  # 数字3对应ICA的Active
+
+        if self.df_ica.empty:
+            self.result['statusFlag']['function_ICA'] = False
+        else:
+            self.result['statusFlag']['function_ICA'] = True
+
+    def data_analyze(self):
+        col_list = ['simTime', 'simFrame', 'playerId', 'v', 'accel', 'lon_acc_roc']  # target_id
+        df = self.df_ica[col_list].copy()
+
+        count_lon_acc_roc_in_range = 0
+        total_lon_acc_roc_frames = 0
+
+        self.graph_list = df['lon_acc_roc'].values.tolist()
+        filtered_data = df[(df['lon_acc_roc'] >= -5) & (df['lon_acc_roc'] <= 6)]
+        count_lon_acc_roc_in_range = count_lon_acc_roc_in_range + len(filtered_data)
+        total_lon_acc_roc_frames = total_lon_acc_roc_frames + len(df)
+        self.percentage_lon_acc_roc = (count_lon_acc_roc_in_range / total_lon_acc_roc_frames) * 100
+
+        self.result['value'] = [round(self.percentage_lon_acc_roc, 3)]
+        print(f"percentage_lon_acc_roc: {self.percentage_lon_acc_roc}")
+
+    def markline_statistic(self):
+        pass
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.graph_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.2f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.2f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.graph_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        # self.markline_statistic()
+        # markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = []
+
+        self.result['reportData']['range'] = [0, 100]
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[percentage_lon_acc_roc:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 0 - 0
custom/voyah/00_cicv_LKA_01_distance_nearby_lane.json → custom/voyah/LKA/cicv_LKA_01_distance_nearby_lane.json


+ 170 - 0
custom/voyah/LKA/cicv_LKA_01_distance_nearby_lane.py

@@ -0,0 +1,170 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+车道宽度:3.75m
+车宽:1.8m
+车的一边距离车道边界线为0.975m为最佳,值越小,分值越低
+
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ego = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "离近侧车道线最小距离",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "离近侧车道线最小距离(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标01: 离近侧车道线最小距离: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+
+        # new active get code
+        active_time_ranges = self.status_trigger_dict['LKA']['LKA_active_time']
+        self.df_ego = get_status_active_data(active_time_ranges, self.ego_df)
+        self.roadMark_df = get_status_active_data(active_time_ranges, self.data.road_mark_df)
+
+        # self.df_ego = self.df[self.df['LKA_status'] == "Active"].copy()  # 数字3对应LKA的Active
+        # self.roadMark_df = self.data.road_mark_df
+
+        if self.df_ego.empty:
+            self.result['statusFlag']['function_LKA'] = False
+        else:
+            self.result['statusFlag']['function_LKA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego / 2
+        else:
+            return abs(x.right_lateral_distance) - width_ego / 2
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadMark_df = self.roadMark_df
+        ego_df = self.df_ego
+        # ego_df = player_df[player_df.playerId == 1]
+        width_ego = ego_df['dimY'].values.tolist()[0]
+        # 提取距离左车道线和右车道线距离
+        # roadMark_df['nearby_distance']
+        roadMark_left_df = roadMark_df[roadMark_df.id == 0].reset_index(drop=True)
+        roadMark_right_df = roadMark_df[roadMark_df.id == 2].reset_index(drop=True)
+        roadMark_left_df['right_lateral_distance'] = roadMark_right_df['lateralDist']
+        # 计算到车道边界线距离
+        roadMark_left_df['nearby_distance_to_lane_boundary'] = roadMark_left_df.apply(
+            lambda x: self.Compute_nearby_distance_to_lane_boundary(x, width_ego), axis=1)
+        nearby_distance_to_lane_boundary = min(roadMark_left_df['nearby_distance_to_lane_boundary'])
+
+        self.result['value'] = [round(nearby_distance_to_lane_boundary, 3)]
+
+        self.time_list_follow = roadMark_left_df['simTime'].values.tolist()
+        self.frame_list_follow = roadMark_left_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadMark_left_df['nearby_distance_to_lane_boundary'].values.tolist()
+        # print("hello world")
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+
+        v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "LKA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.3f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.3f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.3f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+
+        self.result['reportData']['range'] = f"[0, 0.975]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 24 - 0
custom/voyah/LKA/cicv_LKA_02_lateral_offset.json

@@ -0,0 +1,24 @@
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "0",
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ],
+      "optimal": "0.0001",
+      "multiple": [
+        "-10000",
+        "10000"
+      ]
+    }
+  ],
+  "weight": null,
+  "unit": "m",
+  "name": "最大横向偏移量"
+}

+ 155 - 0
custom/voyah/LKA/cicv_LKA_02_lateral_offset.py

@@ -0,0 +1,155 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大横向偏移量
+zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "最大横向偏移量",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "最大横向偏移量(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标02: 最大横向偏移量: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+
+        # new active get code
+        active_time_ranges = self.status_trigger_dict['LKA']['LKA_active_time']
+        self.roadPos_df = get_status_active_data(active_time_ranges, self.data.road_pos_df)
+
+        if self.roadPos_df.empty:
+            self.result['statusFlag']['function_LKA'] = False
+        else:
+            self.result['statusFlag']['function_LKA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego/2
+        else:
+            return abs(x.right_lateral_distance) - width_ego/2
+
+    def func_laneOffset_abs(self, x):
+        return abs(x.laneOffset)
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        # # 计算到车道边界线距离
+        roadPos_ego_df['laneOffset_abs'] = roadPos_ego_df.apply(lambda x: self.func_laneOffset_abs(x), axis=1)
+        max_laneOffset_abs_index = roadPos_ego_df['laneOffset_abs'].idxmax()
+        row_with_max_value = roadPos_ego_df.iloc[max_laneOffset_abs_index].laneOffset
+        self.result['value'] = [row_with_max_value]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.3f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.3f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.3f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        # self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 24 - 0
custom/voyah/LKA/cicv_LKA_03_heading_deviation_max.json

@@ -0,0 +1,24 @@
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "-1",
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ],
+      "optimal": "0.175",
+      "multiple": [
+        "0.5",
+        "3"
+      ]
+    }
+  ],
+  "weight": null,
+  "unit": "rad",
+  "name": "最大航向偏差"
+}

+ 160 - 0
custom/voyah/LKA/cicv_LKA_03_heading_deviation_max.py

@@ -0,0 +1,160 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大航向偏差角
+"""
+
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ego = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        # self.roadPos_df = pd.DataFrame()
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "最大航向角偏差",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "最大航向角偏差(rad)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标03: 最大航向角偏差: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+
+        # new active get code
+        active_time_ranges = self.status_trigger_dict['LKA']['LKA_active_time']
+        self.df_ego = get_status_active_data(active_time_ranges, self.ego_df)
+        self.roadMark_df = get_status_active_data(active_time_ranges, self.data.road_mark_df)
+
+        if self.df_ego.empty:
+            self.result['statusFlag']['function_LKA'] = False
+        else:
+            self.result['statusFlag']['function_LKA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego / 2
+        else:
+            return abs(x.right_lateral_distance) - width_ego / 2
+
+    def data_analyze(self):
+        # 提取自车宽度
+        ego_df = self.df_ego.reset_index(drop=True)
+        road_mark_df = self.roadMark_df
+
+        # 左车道线曲率,右车道线曲率,求二者平均值,计算车道线曲率,再与自车朝向相减
+        road_mark_left_df = road_mark_df[road_mark_df.id == 0].reset_index(drop=True)
+        road_mark_right_df = road_mark_df[road_mark_df.id == 2].reset_index(drop=True)
+        road_mark_left_df['curvHor_left'] = road_mark_left_df['curvHor']
+        road_mark_left_df['curvHor_right'] = road_mark_right_df['curvHor']
+        road_mark_left_df['curvHor_middle'] = road_mark_left_df[['curvHor_left', 'curvHor_right']].apply( \
+            lambda x: (x['curvHor_left'] + x['curvHor_right']) / 2, axis=1)
+        ego_df['curvHor_middle'] = road_mark_left_df['curvHor_middle']
+        ego_df['heading_deviation_abs'] = ego_df[['curvHor_middle', 'posH']].apply( \
+            lambda x: abs(x['posH'] - x['curvHor_middle']), axis=1)
+        row_with_max_value = max(ego_df['heading_deviation_abs'])
+        self.result['value'] = [row_with_max_value]
+        self.time_list_follow = ego_df['simTime'].values.tolist()
+        self.frame_list_follow = ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = ego_df['heading_deviation_abs'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f'{np.mean(graph_list):.3f}' if graph_list else 0
+        self.result['tableData']['max'] = f'{max(graph_list):.3f}' if graph_list else 0
+        self.result['tableData']['min'] = f'{min(graph_list):.3f}' if graph_list else 0
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 24 - 0
custom/voyah/LKA/cicv_LKA_04_relative_position_oscillation_frequency.json

@@ -0,0 +1,24 @@
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "-1",
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ],
+      "optimal": "2",
+      "multiple": [
+        "0.5",
+        "2.5"
+      ]
+    }
+  ],
+  "weight": null,
+  "unit": "rad",
+  "name": "横向相对位置振荡频率"
+}

+ 226 - 0
custom/voyah/LKA/cicv_LKA_04_relative_position_oscillation_frequency.py

@@ -0,0 +1,226 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大横向偏移量
+zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        # self.df_follow = pd.DataFrame()
+        # self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "横向相对位置振荡频率",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "横向相对位置振荡频率(Hz)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标04: 横向相对位置振荡频率: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+
+        # new active get code
+        active_time_ranges = self.status_trigger_dict['LKA']['LKA_active_time']
+        # self.df_follow = get_status_active_data(active_time_ranges, self.ego_df)
+        self.roadPos_df = get_status_active_data(active_time_ranges, self.data.road_pos_df)
+
+        # self.df_follow = self.df[self.df['LKA_status'] == "Active"].copy()  # 数字3对应LKA的Active
+        # self.roadMark_df = self.data.road_mark_df
+        # self.roadPos_df = self.data.road_pos_df
+
+        if self.roadPos_df.empty:
+            self.result['statusFlag']['function_LKA'] = False
+        else:
+            self.result['statusFlag']['function_LKA'] = True
+
+    def func_center_line_cycle(self, l):
+        # print("\n穿过y=0的周期数: ")
+        length = len(l)
+        number_peak = 0
+        number_trough = 0
+        for number, value in enumerate(l):
+            if number == 0:
+                if value > l[number + 1] and value > 0:
+                    number_peak += 1
+                if value < l[number + 1] and value < 0:
+                    number_trough += 1
+                continue
+            if number == length - 1:
+                if value > l[number - 1] and value > 0:
+                    number_peak += 1
+                if value < l[number - 1] and value < 0:
+                    number_trough += 1
+                continue
+            if value >= l[number - 1] and value > l[number + 1] and value > 0:
+                number_peak += 1
+            if value < l[number - 1] and value <= l[number + 1] and value < 0:
+                number_trough += 1
+        cycle = min(number_peak, number_trough) - 1
+        if cycle == -1:
+            return 0
+        return cycle
+
+    def func_normal_cycle_optical(self, l):
+        # print("正常的周期数: ")
+        length = len(l)
+        number_peak = 0
+        number_trough = 0
+        new_list = []
+        for number, value in enumerate(l):
+            if number == 0:
+                if value > l[number + 1]:
+                    number_peak += 1
+                    new_list.append(value)
+                if value < l[number + 1]:
+                    number_trough += 1
+                    new_list.append(value)
+                continue
+            if number == length - 1:
+                if value > l[number - 1]:
+                    number_peak += 1
+                    new_list.append(value)
+                if value < l[number - 1]:
+                    number_trough += 1
+                    new_list.append(value)
+                continue
+            if value >= l[number - 1] and value > l[number + 1]:
+                number_peak += 1
+                new_list.append(value)
+            if value < l[number - 1] and value <= l[number + 1]:
+                number_trough += 1
+                new_list.append(value)
+        if abs(number_peak - number_trough) > 1:
+            pass
+        else:
+            cycle = max(number_peak, number_trough) - 1
+
+        # difference_list = []
+        # for i in range(len(new_list) - 1):
+        #     difference = abs(new_list[i] - new_list[i + 1])
+        #     difference_list.append(difference)
+        # if len(difference_list) == 0:
+        #     maximum_range = -1
+        # else:
+        #     maximum_range = max(difference_list)
+        return cycle
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+        # player_df = self.df
+        # ego_df = player_df[player_df.playerId == 1]
+        # width_ego = ego_df['dimY'].values.tolist()[0]
+
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        roadPos_ego_list = roadPos_ego_df['laneOffset'].values.tolist()
+        cycle_number = self.func_normal_cycle_optical(roadPos_ego_list)
+        simTime_list = roadPos_ego_df['simTime'].values.tolist()
+        time_dif = max(simTime_list) - min(simTime_list)
+        frequency = cycle_number / time_dif
+        self.result['value'] = [round(frequency, 3)]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+
+        self.result['tableData']['avg'] = f"{self.result['value'][0]:.3f}"
+        self.result['tableData']['max'] = "-"
+        self.result['tableData']['min'] = "-"
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 24 - 0
custom/voyah/LKA/cicv_LKA_05_relative_position_oscillation_difference.json

@@ -0,0 +1,24 @@
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "-1",
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ],
+      "optimal": "1.6",
+      "multiple": [
+        "0.5",
+        "2"
+      ]
+    }
+  ],
+  "weight": null,
+  "unit": "rad",
+  "name": "横向相对位置振荡极差"
+}

+ 194 - 0
custom/voyah/LKA/cicv_LKA_05_relative_position_oscillation_difference.py

@@ -0,0 +1,194 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大横向偏移量
+zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        # self.df_follow = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "横向相对位置振荡极差",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "横向相对位置振荡极差(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标05: 横向相对位置振荡极差: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+
+        # new active get code
+        active_time_ranges = self.status_trigger_dict['LKA']['LKA_active_time']
+        # self.df_follow = get_status_active_data(active_time_ranges, self.ego_df)
+        self.roadPos_df = get_status_active_data(active_time_ranges, self.data.road_pos_df)
+
+        # self.df_follow = self.df[self.df['LKA_status'] == "Active"].copy()  # 数字3对应LKA的Active
+        # self.roadMark_df = self.data.road_mark_df
+        # self.roadPos_df = self.data.road_pos_df
+
+        if self.roadPos_df.empty:
+            self.result['statusFlag']['function_LKA'] = False
+        else:
+            self.result['statusFlag']['function_LKA'] = True
+
+    def func_normal_cycle_optical(self, l):
+        # print("正常的周期数: ")
+        length = len(l)
+        number_peak = 0
+        number_trough = 0
+        # 生成新的、只含有波峰波谷的列表
+        new_list = []
+        for number, value in enumerate(l):
+            if number == 0:
+                if value > l[number + 1]:
+                    number_peak += 1
+                    new_list.append(value)
+                if value < l[number + 1]:
+                    number_trough += 1
+                    new_list.append(value)
+                continue
+            if number == length - 1:
+                if value > l[number - 1]:
+                    number_peak += 1
+                    new_list.append(value)
+                if value < l[number - 1]:
+                    number_trough += 1
+                    new_list.append(value)
+                continue
+            if value >= l[number - 1] and value > l[number + 1]:
+                number_peak += 1
+                new_list.append(value)
+            if value < l[number - 1] and value <= l[number + 1]:
+                number_trough += 1
+                new_list.append(value)
+        if abs(number_peak - number_trough) > 1:
+            pass
+        else:
+            cycle = max(number_peak, number_trough) - 1
+        difference_list = []
+        for i in range(len(new_list) - 1):
+            difference = abs(new_list[i] - new_list[i + 1])
+            difference_list.append(difference)
+        if len(difference_list) == 0:
+            maximum_range = 0
+            # print(f"极差: {maximum_range}")
+        else:
+            maximum_range = max(difference_list)
+            # print(f"极差: {maximum_range}")
+        return maximum_range
+
+    def data_analyze(self):
+        roadPos_df = self.roadPos_df
+
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        roadPos_ego_list = roadPos_ego_df['laneOffset'].values.tolist()
+        oscillation_difference = self.func_normal_cycle_optical(roadPos_ego_list)
+        self.result['value'] = [round(oscillation_difference, 3)]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+
+        self.result['tableData']['avg'] = f"{self.result['value'][0]:.3f}"
+        self.result['tableData']['max'] = "-"
+        self.result['tableData']['min'] = "-"
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 24 - 0
custom/voyah/LKA/cicv_LKA_06_absolute_center_distance_expectation.json

@@ -0,0 +1,24 @@
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "-1",
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ],
+      "optimal": "1.6",
+      "multiple": [
+        "0.5",
+        "1.2"
+      ]
+    }
+  ],
+  "weight": null,
+  "unit": "rad",
+  "name": "绝对横向偏移量分布期望"
+}

+ 165 - 0
custom/voyah/LKA/cicv_LKA_06_absolute_center_distance_expectation.py

@@ -0,0 +1,165 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+设计思路:
+最大横向偏移量
+zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        # self.df_follow = pd.DataFrame()
+        # self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "绝对横向偏移量分布期望",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "绝对横向偏移量分布期望(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标06: 绝对横向偏移量分布期望: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        # new active get code
+        active_time_ranges = self.status_trigger_dict['LKA']['LKA_active_time']
+        # self.df_follow = get_status_active_data(active_time_ranges, self.ego_df)
+        self.roadPos_df = get_status_active_data(active_time_ranges, self.data.road_pos_df)
+
+        # self.df_follow = self.df[self.df['LKA_status'] == "Active"].copy()  # 数字3对应LKA的Active
+        # self.roadMark_df = self.data.road_mark_df
+        # self.roadPos_df = self.data.road_pos_df
+
+        if self.roadPos_df.empty:
+            self.result['statusFlag']['function_LKA'] = False
+        else:
+            self.result['statusFlag']['function_LKA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego/2
+        else:
+            return abs(x.right_lateral_distance) - width_ego/2
+
+    def func_laneOffset_abs(self, x):
+        return abs(x.laneOffset)
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+        # player_df = self.df
+        # ego_df = player_df[player_df.playerId == 1]
+        # width_ego = ego_df['dimY'].values.tolist()[0]
+
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        roadPos_ego_df['laneOffset_abs'] = roadPos_ego_df.apply( \
+            lambda x: self.func_laneOffset_abs(x), axis=1)
+        # # 计算到车道边界线距离
+        mean_laneOffset_index = roadPos_ego_df['laneOffset_abs'].mean()
+        self.result['value'] = [round(mean_laneOffset_index, 3)]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset_abs'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        # v_df = unfunc_df[unfunc_df['dist_deviation'] > 0]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+
+        self.result['tableData']['avg'] = f"{self.result['value'][0]:.3f}"
+        self.result['tableData']['max'] = "-"
+        self.result['tableData']['min'] = "-"
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 24 - 0
custom/voyah/LKA/cicv_LKA_07_absolute_center_distance_standard_deviation.json

@@ -0,0 +1,24 @@
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "-1",
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ],
+      "optimal": "1.6",
+      "multiple": [
+        "0.5",
+        "1.2"
+      ]
+    }
+  ],
+  "weight": null,
+  "unit": "rad",
+  "name": "绝对横向偏移量分布标准差"
+}

+ 162 - 0
custom/voyah/LKA/cicv_LKA_07_absolute_center_distance_standard_deviation.py

@@ -0,0 +1,162 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+    设计思路:
+    最大横向偏移量
+    zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        # self.df_follow = pd.DataFrame()
+        # self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "绝对横向偏移量分布标准差",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "绝对横向偏移量分布标准差(m)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标07: 绝对横向偏移量分布标准差: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        # new active get code
+        active_time_ranges = self.status_trigger_dict['LKA']['LKA_active_time']
+        # self.df_follow = get_status_active_data(active_time_ranges, self.ego_df)
+        self.roadPos_df = get_status_active_data(active_time_ranges, self.data.road_pos_df)
+
+        # self.df_follow = self.df[self.df['LKA_status'] == "Active"].copy()  # 数字3对应LKA的Active
+        # self.roadMark_df = self.data.road_mark_df
+        # self.roadPos_df = self.data.road_pos_df
+
+        if self.roadPos_df.empty:
+            self.result['statusFlag']['function_LKA'] = False
+        else:
+            self.result['statusFlag']['function_LKA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def Compute_nearby_distance_to_lane_boundary(self, x, width_ego):
+        if x.lateralDist < abs(x.right_lateral_distance):
+            return x.lateralDist - width_ego/2
+        else:
+            return abs(x.right_lateral_distance) - width_ego/2
+
+    def func_laneOffset_abs(self, x):
+        return abs(x.laneOffset)
+
+    def data_analyze(self):
+        # 提取自车宽度
+        roadPos_df = self.roadPos_df
+        # player_df = self.df
+        # ego_df = player_df[player_df.playerId == 1]
+        # width_ego = ego_df['dimY'].values.tolist()[0]
+
+        # 提取距离左车道线和右车道线距离
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        roadPos_ego_df['laneOffset_abs'] = roadPos_ego_df.apply( \
+            lambda x: self.func_laneOffset_abs(x), axis=1)
+        mean_laneOffset_index = roadPos_ego_df['laneOffset_abs'].std()
+        self.result['value'] = [round(mean_laneOffset_index, 3)]
+        self.time_list_follow = roadPos_ego_df['simTime'].values.tolist()
+        self.frame_list_follow = roadPos_ego_df['simFrame'].values.tolist()
+        self.dist_deviation_list = roadPos_ego_df['laneOffset_abs'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+        self.result['tableData']['avg'] = f"{self.result['value'][0]:.3f}"
+        self.result['tableData']['max'] = "-"
+        self.result['tableData']['min'] = "-"
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 24 - 0
custom/voyah/LKA/cicv_LKA_08_fixed_driving_direction_TLC.json

@@ -0,0 +1,24 @@
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "1",
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ],
+      "optimal": "10",
+      "multiple": [
+        "0.1",
+        "2"
+      ]
+    }
+  ],
+  "weight": null,
+  "unit": "rad",
+  "name": "固定行驶方向车辆跨道时间"
+}

+ 206 - 0
custom/voyah/LKA/cicv_LKA_08_fixed_driving_direction_TLC.py

@@ -0,0 +1,206 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+    设计思路:
+    最大横向偏移量
+    zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ego = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+        self.laneInfo_df = pd.DataFrame()
+        # self.data.lane_info_df
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "固定行驶方向车辆跨道时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "固定行驶方向车辆跨道时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标08: 固定行驶方向车辆跨道时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        # new active get code
+        active_time_ranges = self.status_trigger_dict['LKA']['LKA_active_time']
+        self.df_ego = get_status_active_data(active_time_ranges, self.ego_df)
+        self.roadMark_df = get_status_active_data(active_time_ranges, self.data.road_mark_df)
+        self.roadPos_df = get_status_active_data(active_time_ranges, self.data.road_pos_df)
+        self.laneInfo_df = get_status_active_data(active_time_ranges, self.data.lane_info_df)
+
+        if self.df_ego.empty:
+            self.result['statusFlag']['function_LKA'] = False
+        else:
+            self.result['statusFlag']['function_LKA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def func_laneOffset_abs(self, x):
+        return abs(x.laneOffset)
+
+    def fixed_driving_direction_TLC(self, x):
+        cosA = math.cos(x['heading_deviation_abs'])
+        sinA = math.sin(x['heading_deviation_abs'])
+        V = x['velocity_resultant']
+        # if cosA==0:
+        #     cosA=0.001
+        if sinA == 0:
+            sinA = 0.001
+        if V == 0:
+            V = 0.001
+        TLC = (x['laneWidth'] / 2 - x['laneOffset_abs'] - x['dimY'] / 2 * cosA) / (V * sinA)
+        return TLC
+
+    def data_analyze(self):
+        ego_df = self.df_ego.reset_index(drop=True)
+        road_mark_df = self.roadMark_df
+        
+        # player_df = self.df
+        # mask = (player_df.playerId == 0) | (player_df.playerId == 1)
+        # ego_df = player_df[mask].reset_index(drop=True)
+        # width_ego = ego_df['dimY']  # 自车宽度e
+        # road_mark_df = self.roadMark_df
+        
+        # 左车道线曲率,右车道线曲率,求二者平均值,计算车道线曲率,再与自车朝向相减
+        road_mark_left_df = road_mark_df[road_mark_df.id == 0].reset_index(drop=True)
+        road_mark_right_df = road_mark_df[road_mark_df.id == 2].reset_index(drop=True)
+        road_mark_left_df['curvHor_left'] = road_mark_left_df['curvHor']
+        road_mark_left_df['curvHor_right'] = road_mark_right_df['curvHor']
+        road_mark_left_df['curvHor_middle'] = road_mark_left_df[['curvHor_left', 'curvHor_right']].apply( \
+            lambda x: (x['curvHor_left'] + x['curvHor_right']) / 2, axis=1)
+        ego_df['curvHor_middle'] = road_mark_left_df['curvHor_middle']
+        ego_df['heading_deviation_abs'] = ego_df[['curvHor_middle', 'posH']].apply( \
+            lambda x: abs(x['posH'] - x['curvHor_middle']), axis=1)  # 偏航角θ
+
+        laneInfo_df = self.laneInfo_df
+        laneInfo_df = laneInfo_df[laneInfo_df.laneId == -1].reset_index(drop=True)  # laneInfo_df['width'] 车道宽度
+
+        ego_df['velocity_resultant'] = ego_df[['speedX', 'speedY']].apply( \
+            lambda x: math.sqrt(x['speedX'] ** 2 - x['speedY'] ** 2) / 3.6, axis=1)  # 汽车行驶速度v
+
+        roadPos_df = self.roadPos_df
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        roadPos_ego_df['laneOffset_abs'] = roadPos_ego_df.apply(lambda x: self.func_laneOffset_abs(x),
+                                                                axis=1)  # 横向偏移量y0
+
+        merged_df = pd.merge(roadPos_ego_df, ego_df, on='simFrame', how='inner')
+        merged_df["laneWidth"] = 3.5
+        merged_df['TLC'] = merged_df.apply(lambda x: self.fixed_driving_direction_TLC(x), axis=1)
+        row_with_min_value = min(merged_df['TLC'])
+        # self.lateral_control10_fixed_driving_direction_TLC = row_with_min_value
+        merged_df['simTime'] = merged_df['simTime_x']
+        self.result['value'] = [round(row_with_min_value, 3)]
+        self.time_list_follow = merged_df['simTime'].values.tolist()
+        self.frame_list_follow = merged_df['simFrame'].values.tolist()
+        self.dist_deviation_list = merged_df['TLC'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+
+        avg = np.mean(graph_list) if graph_list else 0
+        avg_ = f"{avg:.3f}" if avg < 999 else "999"
+
+        maxx = max(graph_list) if graph_list else 0
+        max_ = f"{maxx:.3f}" if maxx < 999 else "999"
+
+        minn = min(graph_list) if graph_list else 0
+        min_ = f"{minn:.3f}" if minn < 999 else "999"
+
+        self.result['tableData']['avg'] = avg_
+        self.result['tableData']['max'] = max_
+        self.result['tableData']['min'] = min_
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

+ 24 - 0
custom/voyah/LKA/cicv_LKA_09_fixed_steering_wheel_angle_TLC.json

@@ -0,0 +1,24 @@
+{
+  "priority": "0",
+  "paramList": [
+    {
+      "kind": "1",
+      "spare": [
+        {
+          "param": null
+        },
+        {
+          "param": null
+        }
+      ],
+      "optimal": "10",
+      "multiple": [
+        "0.1",
+        "2"
+      ]
+    }
+  ],
+  "weight": null,
+  "unit": "rad",
+  "name": "固定方向盘转角车辆跨道时间"
+}

+ 222 - 0
custom/voyah/LKA/cicv_LKA_09_fixed_steering_wheel_angle_TLC.py

@@ -0,0 +1,222 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################
+#
+# Copyright (c) 2023 CICV, Inc. All Rights Reserved
+#
+##################################################################
+"""
+@Authors:           zhangyu
+@Data:              2024/02/21
+@Last Modified:     2024/02/21
+@Summary:           The template of custom indicator.
+"""
+
+"""
+    设计思路:
+    最大横向偏移量
+    zy_center_distance_expectation
+"""
+import math
+import pandas as pd
+import numpy as np
+from common import zip_time_pairs, continuous_group, get_status_active_data
+from log import logger
+
+"""import functions"""
+
+
+# custom metric codes
+class CustomMetric(object):
+    def __init__(self, all_data, case_name):
+        self.data = all_data
+        self.optimal_dict = self.data.config
+        self.status_trigger_dict = self.data.status_trigger_dict
+        self.case_name = case_name
+        self.markline_df = pd.DataFrame(columns=['start_time', 'end_time', 'start_frame', 'end_frame', 'type'])
+
+        self.df = pd.DataFrame()
+        self.ego_df = pd.DataFrame()
+        self.df_ego = pd.DataFrame()
+        self.roadMark_df = pd.DataFrame()
+        self.roadPos_df = pd.DataFrame()
+
+        self.time_list_follow = list()
+        self.frame_list_follow = list()
+        self.dist_list = list()
+        self.dist_deviation_list = list()
+        self.dist_deviation_list_full_time = list()
+
+        self.result = {
+            "name": "固定方向盘转角车辆跨道时间",
+            "value": [],
+            # "weight": [],
+            "tableData": {
+                "avg": "",  # 平均值,或指标值
+                "max": "",
+                "min": ""
+            },
+            "reportData": {
+                "name": "固定方向盘转角车辆跨道时间(s)",
+                # "legend": [], # 如果有多个data,则需要增加data对应的说明,如:["横向加速度", "纵向加速度"]
+                "data": [],
+                "markLine": [],
+                "range": [],
+            },
+            "statusFlag": {}
+        }
+        self.run()
+        print(f"指标09: 固定方向盘转角车辆跨道时间: {self.result['value']}")
+
+    def data_extract(self):
+        self.df = self.data.object_df
+        self.ego_df = self.data.object_df[self.data.object_df.playerId == 1]
+        # new active get code
+        active_time_ranges = self.status_trigger_dict['LKA']['LKA_active_time']
+        self.df_ego = get_status_active_data(active_time_ranges, self.ego_df)
+        self.roadMark_df = get_status_active_data(active_time_ranges, self.data.road_mark_df)
+        self.roadPos_df = get_status_active_data(active_time_ranges, self.data.road_pos_df)
+
+        if self.df_ego.empty:
+            self.result['statusFlag']['function_LKA'] = False
+        else:
+            self.result['statusFlag']['function_LKA'] = True
+
+    def dist(self, x1, y1, x2, y2):
+        dis = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
+        return dis
+
+    def solve_equation(self, heading_deviation_abs, GF, BF, omiga):
+        a = 0.0  # 初始猜测
+        epsilon = 0.001  # 误差容限
+        max_iterations = 6283  # 最大迭代次数
+
+        list_demo = []
+
+        for i in range(max_iterations):
+            GB = BF * math.cos(heading_deviation_abs) * math.tan(a + heading_deviation_abs) - BF * math.sin(
+                heading_deviation_abs)
+            fx = math.cos(a) * 2 * GF * BF - GF ** 2 - BF ** 2 - GB ** 2
+            if abs(fx) < epsilon:
+                list_demo.append(a)
+            # 调整猜测范围
+            a += 0.001
+
+        if len(list_demo) != 0:
+            return list_demo[0] / omiga
+
+        return 10000  # 未找到解
+
+    def fixed_steering_wheel_angle_TLC(self, x):
+        heading_deviation_abs = x['heading_deviation_abs']
+        GF = x['GF']
+        BF = x['BF']
+        omiga = x['speedH']
+        return self.solve_equation(heading_deviation_abs, GF, BF, omiga)
+
+    def func_laneOffset_abs(self, x):
+        return abs(x.laneOffset)
+
+    def data_analyze(self):
+        ego_df = self.df_ego.reset_index(drop=True)
+        road_mark_df = self.roadMark_df
+
+        # 左车道线曲率,右车道线曲率,求二者平均值,计算车道线曲率,再与自车朝向相减
+        road_mark_left_df = road_mark_df[road_mark_df.id == 0].reset_index(drop=True)
+        road_mark_right_df = road_mark_df[road_mark_df.id == 2].reset_index(drop=True)
+        road_mark_left_df['curvHor_left'] = road_mark_left_df['curvHor']
+        road_mark_left_df['curvHor_right'] = road_mark_right_df['curvHor']
+        road_mark_left_df['curvHor_middle'] = road_mark_left_df[['curvHor_left', 'curvHor_right']].apply( \
+            lambda x: (x['curvHor_left'] + x['curvHor_right']) / 2, axis=1)
+        ego_df['curvHor_middle'] = road_mark_left_df['curvHor_middle']
+        ego_df['heading_deviation_abs'] = ego_df[['curvHor_middle', 'posH']].apply( \
+            lambda x: abs(x['posH'] - x['curvHor_middle']), axis=1)  # 偏航角θ
+
+        # laneInfo_df = self.laneInfo_df
+        # laneInfo_df = laneInfo_df[laneInfo_df.id == -1].reset_index(drop=True)  # laneInfo_df['width'] 车道宽度
+
+        ego_df['velocity_resultant'] = ego_df[['speedX', 'speedY']].apply( \
+            lambda x: math.sqrt(x['speedX'] ** 2 - x['speedY'] ** 2) / 3.6, axis=1)  # 汽车行驶速度v
+
+        roadPos_df = self.roadPos_df
+        roadPos_ego_df = roadPos_df[roadPos_df.playerId == 1].reset_index(drop=True)
+        roadPos_ego_df['laneOffset_abs'] = roadPos_ego_df.apply(lambda x: self.func_laneOffset_abs(x),
+                                                                axis=1)  # 横向偏移量y0
+
+        merged_df = pd.merge(roadPos_ego_df, ego_df, on='simFrame', how='inner')
+        merged_df["laneWidth"] = 3.5
+        merged_df['GF'] = merged_df[['velocity_resultant', 'speedH']].apply(
+            lambda x: x['velocity_resultant'] / x['speedH'], axis=1)
+        merged_df['GF'] = 10000
+        merged_df['AD'] = merged_df[['laneWidth', 'laneOffset_abs', 'dimY', 'heading_deviation_abs']].apply( \
+            lambda x: x['laneWidth'] - x['laneOffset_abs'] - x['dimY'] / 2 * math.cos(x['heading_deviation_abs']),
+            axis=1)
+        merged_df['AB'] = merged_df[['AD', 'heading_deviation_abs']].apply( \
+            lambda x: x['AD'] / math.cos(x['heading_deviation_abs']), axis=1)
+        merged_df['BF'] = merged_df[['GF', 'AB']].apply(lambda x: x['GF'] - x['AB'], axis=1)
+
+        merged_df['TLC_steer_wheel'] = merged_df.apply(lambda x: self.fixed_steering_wheel_angle_TLC(x), axis=1)
+        row_with_min_value = min(merged_df['TLC_steer_wheel'])
+        merged_df['simTime'] = merged_df['simTime_x']
+        # self.lateral_control11_fixed_steering_wheel_angle_TLC = row_with_min_value
+        self.result['value'] = [round(row_with_min_value, 3)]
+        self.time_list_follow = merged_df['simTime'].values.tolist()
+        self.frame_list_follow = merged_df['simFrame'].values.tolist()
+        self.dist_deviation_list = merged_df['TLC_steer_wheel'].values.tolist()
+
+    def markline_statistic(self):
+        unfunc_df = pd.DataFrame({'simTime': self.time_list_follow, 'simFrame': self.frame_list_follow,
+                                  'dist_deviation': self.dist_deviation_list})
+        unfunc_df = unfunc_df[unfunc_df['simFrame'] > 1]
+        v_df = unfunc_df
+        v_df = v_df[['simTime', 'simFrame', 'dist_deviation']]
+        v_follow_df = continuous_group(v_df)
+        v_follow_df['type'] = "ICA"
+        self.markline_df = pd.concat([self.markline_df, v_follow_df], ignore_index=True)
+
+    def report_data_statistic(self):
+        time_list = self.ego_df['simTime'].values.tolist()
+        graph_list = [x for x in self.dist_deviation_list if not np.isnan(x)]
+
+        avg = np.mean(graph_list) if graph_list else 0
+        avg_ = f"{avg:.3f}" if avg < 999 else "999"
+
+        maxx = max(graph_list) if graph_list else 0
+        max_ = f"{maxx:.3f}" if maxx < 999 else "999"
+
+        minn = min(graph_list) if graph_list else 0
+        min_ = f"{minn:.3f}" if minn < 999 else "999"
+
+        self.result['tableData']['avg'] = avg_
+        self.result['tableData']['max'] = max_
+        self.result['tableData']['min'] = min_
+
+        zip_vs_time = zip_time_pairs(time_list, self.dist_deviation_list)
+        self.result['reportData']['data'] = zip_vs_time
+
+        self.markline_statistic()
+        markline_slices = self.markline_df.to_dict('records')
+        self.result['reportData']['markLine'] = markline_slices
+        self.result['reportData']['range'] = f"[-1.875, 1.875]"
+
+    def run(self):
+        # logger.info(f"Custom metric run:[{self.result['name']}].")
+        logger.info(f"[case:{self.case_name}] Custom metric:[ica_distance_deviation:{self.result['name']}] evaluate.")
+
+        try:
+            self.data_extract()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data extract ERROR!", e)
+
+        try:
+            self.data_analyze()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} data analyze ERROR!", e)
+
+        try:
+            self.report_data_statistic()
+        except Exception as e:
+            logger.error(f"[case:{self.case_name}] Custom metric:{self.result['name']} report data statistic ERROR!", e)
+
+# if __name__ == "__main__":
+#     pass

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác