Alanone преди 10 месеца
родител
ревизия
c06ec3ef2e
променени са 65 файла, в които са добавени 7201 реда и са изтрити 0 реда
  1. 0 0
      config/config_ica_0731-2.json
  2. 0 0
      config/config_ica_0731.json
  3. 22 0
      custom/cicv_ICA/cicv_ica_lateral_control01_distance_nearby_lane.json
  4. 161 0
      custom/cicv_ICA/cicv_ica_lateral_control01_distance_nearby_lane.py
  5. 22 0
      custom/cicv_ICA/cicv_ica_lateral_control02_lateral_offset.json
  6. 164 0
      custom/cicv_ICA/cicv_ica_lateral_control02_lateral_offset.py
  7. 22 0
      custom/cicv_ICA/cicv_ica_lateral_control03_relative_center_distance_expectation.json
  8. 158 0
      custom/cicv_ICA/cicv_ica_lateral_control03_relative_center_distance_expectation.py
  9. 22 0
      custom/cicv_ICA/cicv_ica_lateral_control04_relative_center_distance_standard_deviation.json
  10. 155 0
      custom/cicv_ICA/cicv_ica_lateral_control04_relative_center_distance_standard_deviation.py
  11. 22 0
      custom/cicv_ICA/cicv_ica_lateral_control05_absolute_center_distance_expectation.json
  12. 161 0
      custom/cicv_ICA/cicv_ica_lateral_control05_absolute_center_distance_expectation.py
  13. 22 0
      custom/cicv_ICA/cicv_ica_lateral_control06_absolute_center_distance_standard_deviation.json
  14. 158 0
      custom/cicv_ICA/cicv_ica_lateral_control06_absolute_center_distance_standard_deviation.py
  15. 22 0
      custom/cicv_ICA/cicv_ica_lateral_control07_center_distance_max.json
  16. 160 0
      custom/cicv_ICA/cicv_ica_lateral_control07_center_distance_max.py
  17. 22 0
      custom/cicv_ICA/cicv_ica_lateral_control08_center_distance_min.json
  18. 160 0
      custom/cicv_ICA/cicv_ica_lateral_control08_center_distance_min.py
  19. 22 0
      custom/cicv_ICA/cicv_ica_lateral_control09_absolute_position_oscillation_frequency.json
  20. 179 0
      custom/cicv_ICA/cicv_ica_lateral_control09_absolute_position_oscillation_frequency.py
  21. 22 0
      custom/cicv_ICA/cicv_ica_lateral_control10_absolute_position_oscillation_difference.json
  22. 211 0
      custom/cicv_ICA/cicv_ica_lateral_control10_absolute_position_oscillation_difference.py
  23. 22 0
      custom/cicv_ICA/cicv_ica_lateral_control11_heading_deviation_max.json
  24. 164 0
      custom/cicv_ICA/cicv_ica_lateral_control11_heading_deviation_max.py
  25. 22 0
      custom/cicv_ICA/cicv_ica_lateral_control12_relative_position_oscillation_frequency.json
  26. 236 0
      custom/cicv_ICA/cicv_ica_lateral_control12_relative_position_oscillation_frequency.py
  27. 22 0
      custom/cicv_ICA/cicv_ica_lateral_control13_relative_position_oscillation_difference.json
  28. 186 0
      custom/cicv_ICA/cicv_ica_lateral_control13_relative_position_oscillation_difference.py
  29. 21 0
      custom/cicv_ICA/cicv_ica_longitudinal_control01_delay_time_cruise.json
  30. 192 0
      custom/cicv_ICA/cicv_ica_longitudinal_control01_delay_time_cruise.py
  31. 21 0
      custom/cicv_ICA/cicv_ica_longitudinal_control02_rise_time_cruise.json
  32. 222 0
      custom/cicv_ICA/cicv_ica_longitudinal_control02_rise_time_cruise.py
  33. 21 0
      custom/cicv_ICA/cicv_ica_longitudinal_control03_peak_time_cruise.json
  34. 149 0
      custom/cicv_ICA/cicv_ica_longitudinal_control03_peak_time_cruise.py
  35. 21 0
      custom/cicv_ICA/cicv_ica_longitudinal_control04_overshoot_cruise.json
  36. 205 0
      custom/cicv_ICA/cicv_ica_longitudinal_control04_overshoot_cruise.py
  37. 21 0
      custom/cicv_ICA/cicv_ica_longitudinal_control05_steady_error_cruise.json
  38. 202 0
      custom/cicv_ICA/cicv_ica_longitudinal_control05_steady_error_cruise.py
  39. 21 0
      custom/cicv_ICA/cicv_ica_longitudinal_control06_delay_time_THW.json
  40. 191 0
      custom/cicv_ICA/cicv_ica_longitudinal_control06_delay_time_THW.py
  41. 21 0
      custom/cicv_ICA/cicv_ica_longitudinal_control07_rise_time_THW.json
  42. 231 0
      custom/cicv_ICA/cicv_ica_longitudinal_control07_rise_time_THW.py
  43. 21 0
      custom/cicv_ICA/cicv_ica_longitudinal_control08_peak_time_THW.json
  44. 148 0
      custom/cicv_ICA/cicv_ica_longitudinal_control08_peak_time_THW.py
  45. 21 0
      custom/cicv_ICA/cicv_ica_longitudinal_control09_overshoot_THW.json
  46. 201 0
      custom/cicv_ICA/cicv_ica_longitudinal_control09_overshoot_THW.py
  47. 21 0
      custom/cicv_ICA/cicv_ica_longitudinal_control10_steady_error_THW.json
  48. 201 0
      custom/cicv_ICA/cicv_ica_longitudinal_control10_steady_error_THW.py
  49. 21 0
      custom/cicv_ICA/cicv_ica_longitudinal_control11_reasonable_acceleration_percentage.json
  50. 131 0
      custom/cicv_ICA/cicv_ica_longitudinal_control11_reasonable_acceleration_percentage.py
  51. 21 0
      custom/cicv_ICA/cicv_ica_longitudinal_control12_reasonable_acceleration_change_rate_percentage.json
  52. 132 0
      custom/cicv_ICA/cicv_ica_longitudinal_control12_reasonable_acceleration_change_rate_percentage.py
  53. 192 0
      demoICA/cicv_ica_longitudinal_control01_delay_time_cruise.py
  54. 222 0
      demoICA/cicv_ica_longitudinal_control02_rise_time_cruise.py
  55. 149 0
      demoICA/cicv_ica_longitudinal_control03_peak_time_cruise.py
  56. 205 0
      demoICA/cicv_ica_longitudinal_control04_overshoot_cruise.py
  57. 202 0
      demoICA/cicv_ica_longitudinal_control05_steady_error_cruise.py
  58. 191 0
      demoICA/cicv_ica_longitudinal_control06_delay_time_THW.py
  59. 231 0
      demoICA/cicv_ica_longitudinal_control07_rise_time_THW.py
  60. 148 0
      demoICA/cicv_ica_longitudinal_control08_peak_time_THW.py
  61. 201 0
      demoICA/cicv_ica_longitudinal_control09_overshoot_THW.py
  62. 201 0
      demoICA/cicv_ica_longitudinal_control10_steady_error_THW.py
  63. 131 0
      demoICA/cicv_ica_longitudinal_control11_reasonable_acceleration_percentage.py
  64. 132 0
      demoICA/cicv_ica_longitudinal_control12_reasonable_acceleration_change_rate_percentage.py
  65. BIN
      task_0515/case0530-ica-post/case0731-ica-post.zip

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
config/config_ica_0731-2.json


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
config/config_ica_0731.json


+ 22 - 0
custom/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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/cicv_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'] == "Only_Longitudinal_Control"].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_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/cicv_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/cicv_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'] == "Only_Longitudinal_Control"].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/cicv_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/cicv_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'] == "Only_Longitudinal_Control"].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/cicv_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/cicv_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'] == "Only_Longitudinal_Control"].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/cicv_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/cicv_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'] == "Only_Longitudinal_Control"].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/cicv_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/cicv_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'] == "Only_Longitudinal_Control"].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/cicv_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/cicv_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'] == "Only_Longitudinal_Control"].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/cicv_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/cicv_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'] == "Only_Longitudinal_Control"].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/cicv_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/cicv_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'] == "Only_Longitudinal_Control"].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/cicv_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/cicv_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'] == "Only_Longitudinal_Control"].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/cicv_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
+        }
+      ]
+    }
+  ]
+}

+ 131 - 0
custom/cicv_ICA/cicv_ica_longitudinal_control11_reasonable_acceleration_percentage.py

@@ -0,0 +1,131 @@
+#!/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
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "Only_Longitudinal_Control"].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_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/cicv_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
+        }
+      ]
+    }
+  ]
+}

+ 132 - 0
custom/cicv_ICA/cicv_ica_longitudinal_control12_reasonable_acceleration_change_rate_percentage.py

@@ -0,0 +1,132 @@
+#!/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
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "Only_Longitudinal_Control"].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

+ 192 - 0
demoICA/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'] == "Only_Longitudinal_Control"].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_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

+ 222 - 0
demoICA/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'] == "Only_Longitudinal_Control"].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

+ 149 - 0
demoICA/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'] == "Only_Longitudinal_Control"].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

+ 205 - 0
demoICA/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'] == "Only_Longitudinal_Control"].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

+ 202 - 0
demoICA/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'] == "Only_Longitudinal_Control"].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

+ 191 - 0
demoICA/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'] == "Only_Longitudinal_Control"].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

+ 231 - 0
demoICA/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'] == "Only_Longitudinal_Control"].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

+ 148 - 0
demoICA/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'] == "Only_Longitudinal_Control"].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

+ 201 - 0
demoICA/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'] == "Only_Longitudinal_Control"].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

+ 201 - 0
demoICA/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'] == "Only_Longitudinal_Control"].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

+ 131 - 0
demoICA/cicv_ica_longitudinal_control11_reasonable_acceleration_percentage.py

@@ -0,0 +1,131 @@
+#!/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
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "Only_Longitudinal_Control"].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_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

+ 132 - 0
demoICA/cicv_ica_longitudinal_control12_reasonable_acceleration_change_rate_percentage.py

@@ -0,0 +1,132 @@
+#!/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
+        self.df_ica = self.ego_df[self.ego_df['ICA_status'] == "Only_Longitudinal_Control"].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

BIN
task_0515/case0530-ica-post/case0731-ica-post.zip


Някои файлове не бяха показани, защото твърде много файлове са промени