data_analyse_origin.py 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692
  1. import os
  2. import json
  3. import pandas as pd
  4. import numpy as np
  5. import seaborn as sns
  6. import matplotlib.pyplot as plt
  7. from matplotlib.ticker import MaxNLocator
  8. from typing import Tuple, List
  9. import warnings
  10. import time
  11. import sys
  12. import frequency_filter as ff
  13. from datetime import datetime
  14. from scipy.optimize import least_squares, differential_evolution
  15. from scipy.signal import savgol_filter
  16. warnings.filterwarnings("ignore", category=FutureWarning) # 忽略特定警告
  17. plt.rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体
  18. plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
  19. # TODO: 将偏斜角度还原叶片数据的代码转移到最后的步骤
  20. def result_main():
  21. """
  22. 创建data目录,返回历史分析数据存放的文件路径
  23. """
  24. # 获取当前程序的绝对路径
  25. python_interpreter_path = sys.executable
  26. project_directory = os.path.dirname(python_interpreter_path)
  27. data_folder = os.path.join(project_directory, 'data')
  28. # 检查data文件夹是否存在,如果不存在则创建
  29. if not os.path.exists(data_folder):
  30. os.makedirs(data_folder)
  31. # CSV文件路径
  32. csv_file_path = os.path.join(data_folder, 'history_data.csv')
  33. # 检查CSV文件是否存在,如果不存在则创建一个空的CSV文件
  34. if not os.path.exists(csv_file_path):
  35. pd.DataFrame(columns=['时间', '场站', '风机编号', '采样频率',
  36. '叶片1角度偏差', '叶片2角度偏差', '叶片3角度偏差', '相对角度偏差',
  37. '叶片1净空值', '叶片2净空值', '叶片3净空值',
  38. '叶片1扭转', '叶片2扭转', '叶片3扭转', '平均扭转',
  39. '振动幅值', '振动主频']).to_csv(csv_file_path, index=False)
  40. return csv_file_path
  41. def delete_data(name):
  42. """
  43. 删除历史分析数据
  44. :param name: 删除条件
  45. :return: csv文件路径
  46. """
  47. # 获取当前程序的绝对路径
  48. python_interpreter_path = sys.executable
  49. project_directory = os.path.dirname(python_interpreter_path)
  50. data_folder = os.path.join(project_directory, 'data')
  51. # CSV文件路径
  52. csv_file_path = os.path.join(data_folder, 'history_data.csv')
  53. df = pd.read_csv(csv_file_path)
  54. condition = ((df['时间'].astype(str).str.contains(name[0])) &
  55. (df['场站'].astype(str).str.contains(name[1])) &
  56. (df['风机编号'].astype(str).str.contains(name[2])))
  57. # 删除满足条件的行
  58. df = df[~condition]
  59. # 如果需要,可以将修改后的 DataFrame 保存回 CSV 文件
  60. df.to_csv(csv_file_path, index=False)
  61. return csv_file_path
  62. def history_data(name):
  63. """
  64. 读取历史分析数据
  65. :param name: 接口返回列表
  66. :return:
  67. """
  68. time_code = name[0]
  69. wind_name = name[1]
  70. turbine_code = name[2]
  71. # 获取当前程序的绝对路径
  72. python_interpreter_path = sys.executable
  73. project_directory = os.path.dirname(python_interpreter_path)
  74. data_folder = os.path.join(project_directory, 'data')
  75. time_code_cleaned = time_code.replace("-", "").replace(":", "").replace(" ", "")
  76. json_filename = f"{wind_name}_{turbine_code}_{time_code_cleaned}.json"
  77. json_file_path = os.path.join(data_folder, json_filename)
  78. if not os.path.exists(json_file_path):
  79. raise ValueError("文件不存在")
  80. with open(json_file_path, 'r') as f:
  81. data = json.load(f)
  82. return data
  83. def data_analyse(path: List[str]):
  84. """
  85. 创建data目录,把分析数据保存到历史记录中,同时返回全量分析数据
  86. locate_file_0:非中心线数据,1通道下方,2通道上方
  87. locate_file:轮毂中心数据,1通道叶尖,2通道轮毂中心
  88. measure_file:测量数据,1通道法兰,2通道叶片最宽处
  89. """
  90. # 基础配置参数
  91. locate_file_0 = path[0]
  92. locate_file = path[1]
  93. measure_file = path[2]
  94. angle_cone = float(path[3]) # 锥角
  95. axial_inclination = float(path[4]) # 轴向倾角
  96. rotate_angle = float(path[5]) # 偏航角
  97. skew_angle_meas = 0 # 偏航角
  98. noise_reduction = 0.000001 # 如果一个距离值的所有样本量小于总样本量的noise_reduction,则被去掉
  99. min_difference = 1.5 # 如果相邻2个点的距离差大于min_difference,则被注意是否是周期节点
  100. group_length = [10000, 10000, 5000, 10000] # 计算叶片轮廓时每个小切片的长度,4个数分别为叶中、叶根、叶尖、轴线计算切片长度
  101. lift_up_limit = 0 # 如果法兰中的数据大于这个值,则提升5m
  102. return_list = []
  103. # 读取文件信息,包括风场名、风机编号、时间、采样频率、2个通道俯仰角
  104. wind_name_0, turbine_code_0, time_code_0, sampling_fq_0, angle_locate_1, angle_locate_2 = find_param(locate_file_0)
  105. wind_name, turbine_code, time_code, sampling_fq, angle_nan, angle_cen = find_param(locate_file)
  106. wind_name_1, turbine_code_1, time_code_1, sampling_fq_1, angle_flange, angle_root = find_param(measure_file)
  107. sampling_fq_0 = sampling_fq_0 * 1000
  108. sampling_fq_1 = sampling_fq_1 * 1000
  109. sampling_fq = sampling_fq * 1000
  110. print(wind_name_0, turbine_code_0, time_code_0, sampling_fq_0, angle_locate_1, angle_locate_2)
  111. print(wind_name, turbine_code, time_code, sampling_fq, angle_nan, angle_cen)
  112. print(wind_name_1, turbine_code_1, time_code, sampling_fq_1, angle_flange, angle_root)
  113. # 读取数据,并检查是否有时间序列异常,分离2通道数据
  114. data_locate1, data_locate2 = process_data(locate_file_0, 0) # 定位数据,1是下方,2是上方
  115. data_locate0, data_locate_nan = process_data(locate_file, 0)
  116. data_nan, data_cen = process_data(locate_file, skew_angle_meas)
  117. data_flange, data_root = process_data(measure_file, skew_angle_meas)
  118. # 偏斜计算部分
  119. locate1, data_loc1 = locate_filter(data_locate1)
  120. locate2, data_loc2 = locate_filter(data_locate2)
  121. locate0_filter = tower_filter(data_locate0, noise_reduction)
  122. locate0 = locate0_filter['distance'].mean()
  123. start_loc1, end_loc1, filtered_data_loc1 = cycle_calculate(data_loc1, noise_reduction, min_difference)
  124. start_loc2, end_loc2, filtered_data_loc2 = cycle_calculate(data_loc2, noise_reduction, min_difference)
  125. if np.abs(start_loc2.iloc[1, 1] - start_loc1.iloc[0, 1]) < np.abs(start_loc2.iloc[0, 1] - start_loc1.iloc[0, 1]):
  126. start_loc2 = start_loc2.iloc[1:]
  127. end_loc2 = end_loc2.iloc[1:]
  128. angle_list = []
  129. for i in range(2, min(len(start_loc2), len(start_loc1)) - 2):
  130. time_period = start_loc2.iloc[i, 0] - start_loc1.iloc[i, 0] # 2个测量点的时间差
  131. time_angle1 = start_loc1.iloc[i + 1, 0] - start_loc1.iloc[i, 0] # 以下方测量点计一个周期
  132. time_angle2 = start_loc2.iloc[i, 0] - start_loc2.iloc[i - 1, 0] # 以上方测量点计一个周期
  133. if np.abs(time_angle1 - time_angle2) < 50000:
  134. angle_diff = 360 * time_period / (time_angle1 * 3)
  135. angle_list.append(angle_diff)
  136. else:
  137. pass
  138. angle_avg = np.mean(angle_list) # 计算2个测量点的平均相位角度
  139. locate0_coordinate = [locate0 * np.cos(np.deg2rad(angle_cen)), 0, locate0 * np.sin(np.deg2rad(angle_cen))]
  140. locate1_coordinate = [locate1 * np.cos(np.deg2rad(angle_locate_1)) * np.cos(np.deg2rad(rotate_angle)),
  141. locate1 * np.cos(np.deg2rad(angle_locate_1)) * np.sin(np.deg2rad(rotate_angle)),
  142. locate1 * np.sin(np.deg2rad(angle_locate_1))]
  143. locate2_coordinate = [locate2 * np.cos(np.deg2rad(angle_locate_2)) * np.cos(np.deg2rad(rotate_angle)),
  144. locate2 * np.cos(np.deg2rad(angle_locate_2)) * np.sin(np.deg2rad(rotate_angle)),
  145. locate2 * np.sin(np.deg2rad(angle_locate_2))]
  146. equation_list = [locate0_coordinate, locate1_coordinate, locate2_coordinate,
  147. axial_inclination, angle_avg, angle_cone]
  148. print("\033[34m偏斜方程计算输入:\033[0m", equation_list)
  149. # skew_angle = ff.process_equations(equation_list)
  150. if lift_up_limit >= 0.1:
  151. discrete_values = np.arange(0, 0.101, 0.001)
  152. condition = data_flange['distance'] > lift_up_limit
  153. n = condition.sum()
  154. random_discrete = np.random.choice(discrete_values, size=n)
  155. data_flange.loc[condition, 'distance'] = lift_up_limit + 3 + random_discrete
  156. elif np.abs(lift_up_limit) < 0.1:
  157. pass
  158. else:
  159. raise ValueError("lift_up_limit error.")
  160. # 全部数据进行降噪、去除异常点处理,叶根叶尖数据计算叶片扫掠起始、结束点,轮毂中心数据计算距离均值
  161. start_flange, end_flange, filtered_data_flange = cycle_calculate(data_flange, noise_reduction, min_difference)
  162. start_root, end_root, filtered_data_root = cycle_calculate(data_root, noise_reduction, min_difference)
  163. start_nan, end_nan, filtered_data_nan = cycle_calculate(data_nan, noise_reduction, min_difference)
  164. # 轮毂中心数据降噪,并求均值得到塔筒中心距离,再把降噪后的数据根据俯仰角转换为水平方向的振动
  165. filtered_data_cen = tower_filter(data_cen, noise_reduction)
  166. dist_cen = np.mean(filtered_data_cen.iloc[:, 1].tolist())
  167. filtered_data_cen.iloc[:, 1] = filtered_data_cen.iloc[:, 1] * np.cos(np.deg2rad(angle_cen + axial_inclination))
  168. # 计算叶根、叶中、叶尖处的塔筒距离,对轮毂中心做FFT分析
  169. tower_dist_flange = ff.tower_cal(filtered_data_flange, start_flange, end_flange, sampling_fq_1)
  170. tower_dist_root = ff.tower_cal(filtered_data_root, start_root, end_root, sampling_fq_1)
  171. tower_dist_nan = ff.tower_cal(filtered_data_nan, start_nan, end_nan, sampling_fq)
  172. lowpass_data, fft_x, fft_y, tower_freq, tower_max= ff.process_fft(filtered_data_cen, sampling_fq)
  173. # 根据起始结束点,对叶根、对叶片数据进行归一化处理,计算每个叶片的散点表、线表、边界点表、标准循环周期长度、每个叶片平均最小值
  174. result_line_flange, result_scatter_flange, border_rows_flange, cycle_len_flange, min_flange \
  175. = data_normalize(filtered_data_flange, start_flange, end_flange, group_length[0])
  176. result_line_root, result_scatter_root, border_rows_root, cycle_len_root, min_root \
  177. = data_normalize(filtered_data_root, start_root, end_root, group_length[1])
  178. result_line_nan, result_scatter_nan, border_rows_nan, cycle_len_nan, min_nan \
  179. = data_normalize(filtered_data_nan, start_nan, end_nan, group_length[2])
  180. # 计算3个叶片的平均轮廓,3个叶片的形状差
  181. result_avg_flange, result_diff_flange = blade_shape(result_line_flange)
  182. result_avg_root, result_diff_root = blade_shape(result_line_root)
  183. # 对叶尖的边界点表和俯仰角做坐标归一化处理
  184. border_rows_flange_new, angle_flange_new = coordinate_normalize(border_rows_flange, angle_flange)
  185. border_rows_nan_new, angle_nan_new = coordinate_normalize(border_rows_nan, angle_nan)
  186. # 将所有 DataFrame 合并成一个并求平均
  187. flange_ava = pd.concat([df['distance'] for df in border_rows_flange_new]).mean(numeric_only=True).mean()
  188. root_ava = pd.concat([df['distance'] for df in border_rows_root]).mean(numeric_only=True).mean()
  189. d_radius = np.abs((flange_ava * np.cos(np.deg2rad(angle_flange_new))
  190. - root_ava * np.cos(np.deg2rad(angle_root))) * np.sin(np.deg2rad(axial_inclination))
  191. + (flange_ava * np.sin(np.deg2rad(angle_flange_new))
  192. - root_ava * np.sin(np.deg2rad(angle_root))) * np.cos(np.deg2rad(axial_inclination)))
  193. flange_root_dist = np.sqrt(flange_ava ** 2 + root_ava ** 2 - 2 * flange_ava * root_ava * np.cos(np.deg2rad(angle_flange_new - angle_root)))
  194. # 对叶片的边界点表做半径计算
  195. flange_r = radius_cal(border_rows_flange_new, angle_flange_new, dist_cen, angle_cen, axial_inclination, angle_cone)
  196. root_r = radius_cal(border_rows_root, angle_root, dist_cen, angle_cen, axial_inclination, angle_cone)
  197. nan_r = radius_cal(border_rows_nan_new, angle_nan_new, dist_cen, angle_cen, axial_inclination, angle_cone)
  198. blade_axis, blade_axis_tuple, result_line_flange = blade_axis_cal(filtered_data_flange, start_flange, end_flange,
  199. angle_flange + angle_cone + axial_inclination, group_length[3], flange_r)
  200. blade_axis_new, angle_flange_new = flange_coordinate_normalize(blade_axis, angle_flange)
  201. blade_axis_tuple_new, angle_flange_new = flange_coordinate_normalize(blade_axis_tuple, angle_flange)
  202. if np.abs((root_r - flange_r) - d_radius) > 0.5:
  203. print(str(root_r - flange_r) + "对比" + str(d_radius))
  204. root_r = flange_r + d_radius
  205. if np.abs(flange_root_dist - d_radius) > 0.5:
  206. print(str(flange_root_dist) + "对比" + str(d_radius))
  207. root_r = flange_r + flange_root_dist
  208. # root_r = d_radius + blade_axis_new["旋转半径"].iloc[0]
  209. # root_flange_dist = d_radius / np.cos(np.deg2rad(angle_cone))
  210. # blade_axis_new["中心y"] = np.sqrt(blade_axis_new["中心y"] ** 2 + root_flange_dist ** 2 - 2 *
  211. # root_flange_dist * blade_axis_new["中心y"]
  212. # * np.cos(np.deg2rad(90 - angle_cone - axial_inclination - angle_flange_new)))
  213. blade_axis_new["中心y"] = blade_axis_new["中心y"] - (flange_ava - root_ava)
  214. blade_axis_tuple_new["中心y"] = blade_axis_tuple_new["中心y"] - (flange_ava - root_ava)
  215. # 计算叶片测量位置处的绝对桨距角、相对桨距角、线速度、叶片内部中心点距离
  216. aero_dist_flange, v_speed_flange, cen_blade_flange = (
  217. blade_angle_aero_dist(border_rows_flange, flange_r, cycle_len_flange, tower_dist_flange, angle_flange_new))
  218. aero_dist_nan, v_speed_nan, cen_blade_nan = (
  219. blade_angle_aero_dist(border_rows_nan_new, nan_r, cycle_len_nan, tower_dist_nan, angle_nan_new))
  220. pitch_angle_root, v_speed_root, blade_axis_tuple_new = (
  221. blade_angle(border_rows_root, blade_axis_tuple_new, root_r, cycle_len_root, angle_root + angle_cone + axial_inclination))
  222. blade_axis_tuple_new["中心y"] = blade_axis_tuple_new["中心y"]*np.cos(np.deg2rad(angle_root + angle_cone + axial_inclination))
  223. # 将列表转换为 numpy 数组
  224. cen_blade_nan_array = np.array(cen_blade_nan)
  225. min_nan_array = np.array(min_nan)
  226. # 计算叶片内部中心点距离与叶片最小值之间的差值
  227. abs_diff_nan = np.abs(cen_blade_nan_array - min_nan_array)
  228. blade_dist_nan = abs_diff_nan * np.cos(np.deg2rad(angle_nan_new))
  229. blade_dist_nan.tolist()
  230. # 计算叶片转速-净空散点表
  231. dist_distribute_nan = blade_dist_distribute_cal(filtered_data_nan, start_nan, end_nan,
  232. tower_dist_nan, angle_nan_new + angle_cone + axial_inclination, blade_dist_nan)
  233. # dist_distribute = [df.round(5) for df in dist_distribute]
  234. dist_distribute = [df.round(5) for df in dist_distribute_nan]
  235. # 获取净空距离的最小值和最大值,以及它们对应的转速值,并分别保存在列表中
  236. min_values = []
  237. min_keys = []
  238. max_values = []
  239. max_keys = []
  240. mean_values = []
  241. for df in dist_distribute:
  242. second_col_min = df[df.columns[1]].min()
  243. second_col_max = df[df.columns[1]].max()
  244. min_row = df[df[df.columns[1]] == second_col_min]
  245. max_row = df[df[df.columns[1]] == second_col_max]
  246. min_values.append(round(second_col_min, 2))
  247. min_keys.append(round(min_row.iloc[0][df.columns[0]], 2))
  248. max_values.append(round(second_col_max, 2))
  249. max_keys.append(round(max_row.iloc[0][df.columns[0]], 2))
  250. for i in range(3):
  251. mean_values.append(round((max_values[i] + min_values[i]) / 2, 2))
  252. # 将叶片线表数据乘以线速度,和俯仰角,得到叶片横截面的真实轮廓
  253. # for i in range(3):
  254. # df = result_line_flange[i]
  255. # first_column = df.iloc[:, 0]
  256. # sec_column = df.iloc[:, 1]
  257. # df.iloc[:, 0] = first_column * v_speed_flange
  258. # blade_axis_tuple.iloc[i, 1] = blade_axis_tuple.iloc[i, 1] * v_speed_flange * 5000000
  259. # min_time = df.iloc[:, 0].min()
  260. # df.iloc[:, 0] -= min_time
  261. # blade_axis_tuple.iloc[i, 1] -= min_time
  262. for i in range(3):
  263. df = result_line_root[i]
  264. first_column = df.iloc[:, 0]
  265. sec_column = df.iloc[:, 1]
  266. df.iloc[:, 0] = first_column * v_speed_root
  267. min_time = df.iloc[:, 0].min()
  268. df.iloc[:, 0] -= min_time
  269. blade_axis_tuple_new.iloc[i, 1] -= min_time
  270. df.iloc[:, 1] = sec_column * np.cos(np.deg2rad(angle_root + angle_cone + axial_inclination))
  271. for df in result_scatter_flange:
  272. first_column = df.iloc[:, 0]
  273. sec_column = df.iloc[:, 1]
  274. df.iloc[:, 0] = first_column * v_speed_flange
  275. df.iloc[:, 1] = sec_column * np.cos(np.deg2rad(angle_flange_new + angle_cone + axial_inclination))
  276. for df in result_scatter_root:
  277. first_column = df.iloc[:, 0]
  278. sec_column = df.iloc[:, 1]
  279. df.iloc[:, 0] = first_column * v_speed_root
  280. df.iloc[:, 1] = sec_column * np.cos(np.deg2rad(angle_root + angle_cone + axial_inclination))
  281. # 将叶片平均轮廓数据乘以线速度,得到实际叶片长度
  282. avg_flange = result_avg_flange.iloc[:, 0]
  283. result_avg_flange.iloc[:, 0] = avg_flange * v_speed_flange
  284. avg_root = result_avg_root.iloc[:, 0]
  285. result_avg_root.iloc[:, 0] = avg_root * v_speed_root
  286. # 计算叶片扭转角度
  287. pitch_angle_flange = [0, 0, 0]
  288. twist_1 = round(np.abs(pitch_angle_root[0] - pitch_angle_flange[0]), 2)
  289. twist_2 = round(np.abs(pitch_angle_root[1] - pitch_angle_flange[1]), 2)
  290. twist_3 = round(np.abs(pitch_angle_root[2] - pitch_angle_flange[2]), 2)
  291. twist_avg = round((twist_1 + twist_2 + twist_3) / 3, 2)
  292. # 降低给数据采样频率,降低接口负担
  293. sampling_num = int(0.015 * sampling_fq_1)
  294. # 将原始数据的时间列由计时时钟转换为实际时间
  295. data_flange.iloc[:, 0] = data_flange.iloc[:, 0] / 5000000
  296. data_root.iloc[:, 0] = data_root.iloc[:, 0] / 5000000
  297. lowpass_data.iloc[:, 0] = lowpass_data.iloc[:, 0] / 5000000
  298. # 将叶片轮廓旋转重合
  299. rotated_root = [pd.DataFrame() for _ in range(3)]
  300. for i in range(3):
  301. angle_rad = np.deg2rad(-pitch_angle_root[i])
  302. rotation_matrix = np.array([
  303. [np.cos(angle_rad), -np.sin(angle_rad)],
  304. [np.sin(angle_rad), np.cos(angle_rad)]
  305. ])
  306. # 获取中心点坐标
  307. center_x = blade_axis_tuple_new.iloc[i, 1]
  308. center_y = blade_axis_tuple_new.iloc[i, 2]
  309. # 创建结果DataFrame
  310. rotated_points = result_line_root[i].copy()
  311. # 对每个点进行旋转
  312. for idx, row in result_line_root[i].iterrows():
  313. # 获取当前点坐标
  314. x = row.iloc[0] # 获取第一列作为x坐标
  315. y = row.iloc[1] # 获取第二列作为y坐标
  316. # 将点平移到原点
  317. translated_x = x - center_x
  318. translated_y = y - center_y
  319. # 应用旋转矩阵
  320. rotated = rotation_matrix @ np.array([translated_x, translated_y])
  321. # 将点平移回原位置
  322. final_x = rotated[0] + center_x
  323. final_y = rotated[1] + center_y
  324. # 更新结果DataFrame
  325. rotated_points.iloc[idx, 0] = final_x
  326. rotated_points.iloc[idx, 1] = final_y
  327. rotated_root[i % 3] = pd.concat([rotated_root[i % 3], rotated_points])
  328. # 将需要保存到CSV的数据添加到return_list中
  329. return_list.append(str(time_code))
  330. return_list.append(str(wind_name))
  331. return_list.append(str(turbine_code))
  332. return_list.append(sampling_fq_1)
  333. return_list.append(pitch_angle_root[0])
  334. return_list.append(pitch_angle_root[1])
  335. return_list.append(pitch_angle_root[2])
  336. return_list.append(pitch_angle_root[3])
  337. return_list.append(mean_values[0])
  338. return_list.append(mean_values[1])
  339. return_list.append(mean_values[2])
  340. return_list.append(twist_1)
  341. return_list.append(twist_2)
  342. return_list.append(twist_3)
  343. return_list.append(twist_avg)
  344. return_list.append(tower_max)
  345. return_list.append(tower_freq)
  346. # 将return_list转换为DataFrame并追加到CSV文件
  347. df_new_row = pd.DataFrame([return_list],
  348. columns=['时间', '场站', '风机编号', '采样频率',
  349. '叶片1角度偏差', '叶片2角度偏差', '叶片3角度偏差', '相对角度偏差',
  350. '叶片1净空值', '叶片2净空值', '叶片3净空值',
  351. '叶片1扭转', '叶片2扭转', '叶片3扭转', '平均扭转',
  352. '振动幅值', '振动主频'])
  353. json_output = {
  354. 'original_plot': {
  355. 'blade_tip': {
  356. 'xdata': data_flange.iloc[:, 0].tolist()[::sampling_num],
  357. 'ydata': data_flange.iloc[:, 1].tolist()[::sampling_num]
  358. },
  359. 'blade_root': {
  360. 'xdata': data_root.iloc[:, 0].tolist()[::sampling_num],
  361. 'ydata': data_root.iloc[:, 1].tolist()[::sampling_num]
  362. }
  363. },
  364. 'fft_plot': {
  365. 'lowpass': {
  366. 'xdata': lowpass_data['time'].tolist()[::sampling_num],
  367. 'ydata': lowpass_data['distance_filtered'].tolist()[::sampling_num],
  368. 'xmax': max(lowpass_data['time'].tolist()),
  369. 'xmin': min(lowpass_data['time'].tolist()),
  370. 'ymax': max(lowpass_data['distance_filtered'].tolist()) + 0.02,
  371. 'ymin': min(lowpass_data['distance_filtered'].tolist()) - 0.02
  372. },
  373. 'fft': {
  374. 'xdata': fft_x,
  375. 'ydata': fft_y,
  376. 'xmax': max(fft_x),
  377. 'xmin': min(fft_x),
  378. 'ymax': max(fft_y) + 0.02,
  379. 'ymin': 0
  380. }
  381. },
  382. 'blade_tip': {
  383. 'first_blade': {
  384. 'xdata': result_line_flange[0].iloc[:, 0].tolist(),
  385. 'ydata': result_line_flange[0].iloc[:, 1].tolist()
  386. },
  387. 'second_blade': {
  388. 'xdata': result_line_flange[1].iloc[:, 0].tolist(),
  389. 'ydata': result_line_flange[1].iloc[:, 1].tolist()
  390. },
  391. 'third_blade': {
  392. 'xdata': result_line_flange[2].iloc[:, 0].tolist(),
  393. 'ydata': result_line_flange[2].iloc[:, 1].tolist()
  394. },
  395. 'avg_blade': {
  396. 'xdata': result_avg_flange.iloc[:, 0].tolist(),
  397. 'ydata': result_avg_flange.iloc[:, 1].tolist()
  398. },
  399. 'blade_center': {
  400. 'xdata': blade_axis_tuple.iloc[:, 1].tolist(),
  401. 'ydata': blade_axis_tuple.iloc[:, 2].tolist()
  402. }
  403. },
  404. 'blade_root': {
  405. 'first_blade': {
  406. 'xdata': result_line_root[0].iloc[:, 0].tolist(),
  407. 'ydata': result_line_root[0].iloc[:, 1].tolist()
  408. },
  409. 'second_blade': {
  410. 'xdata': result_line_root[1].iloc[:, 0].tolist(),
  411. 'ydata': result_line_root[1].iloc[:, 1].tolist()
  412. },
  413. 'third_blade': {
  414. 'xdata': result_line_root[2].iloc[:, 0].tolist(),
  415. 'ydata': result_line_root[2].iloc[:, 1].tolist()
  416. },
  417. 'avg_blade': {
  418. 'xdata': result_avg_root.iloc[:, 0].tolist(),
  419. 'ydata': result_avg_root.iloc[:, 1].tolist()
  420. },
  421. 'first_rotate_blade': {
  422. 'xdata': rotated_root[0].iloc[:, 0].tolist(),
  423. 'ydata': rotated_root[0].iloc[:, 1].tolist()
  424. },
  425. 'second_rotate_blade': {
  426. 'xdata': rotated_root[1].iloc[:, 0].tolist(),
  427. 'ydata': rotated_root[1].iloc[:, 1].tolist()
  428. },
  429. 'third_rotate_blade': {
  430. 'xdata': rotated_root[2].iloc[:, 0].tolist(),
  431. 'ydata': rotated_root[2].iloc[:, 1].tolist()
  432. },
  433. 'blade_center': {
  434. 'xdata': blade_axis_tuple_new.iloc[:, 1].tolist(),
  435. 'ydata': blade_axis_tuple_new.iloc[:, 2].tolist()
  436. }
  437. },
  438. 'dist_distribution': {
  439. 'first_blade': {
  440. 'xdata': dist_distribute[0].iloc[:, 0].tolist(),
  441. 'ydata': dist_distribute[0].iloc[:, 1].tolist()
  442. },
  443. 'second_blade': {
  444. 'xdata': dist_distribute[1].iloc[:, 0].tolist(),
  445. 'ydata': dist_distribute[1].iloc[:, 1].tolist()
  446. },
  447. 'third_blade': {
  448. 'xdata': dist_distribute[2].iloc[:, 0].tolist(),
  449. 'ydata': dist_distribute[2].iloc[:, 1].tolist()
  450. }
  451. },
  452. 'analyse_table': {
  453. 'pitch_angle_diff': {
  454. 'blade_1': pitch_angle_root[0],
  455. 'blade_2': pitch_angle_root[1],
  456. 'blade_3': pitch_angle_root[2],
  457. 'blade_relate': pitch_angle_root[3]
  458. },
  459. 'aero_dist': {
  460. 'first_blade': {
  461. 'x_min': min_keys[0],
  462. 'y_min': min_values[0],
  463. 'x_max': max_keys[0],
  464. 'y_max': max_values[0],
  465. 'y_diff': np.abs(max_values[0] - min_values[0]),
  466. 'y_ava': mean_values[0]
  467. },
  468. 'second_blade': {
  469. 'x_min': min_keys[1],
  470. 'y_min': min_values[1],
  471. 'x_max': max_keys[1],
  472. 'y_max': max_values[1],
  473. 'y_diff': np.abs(max_values[1] - min_values[1]),
  474. 'y_ava': mean_values[1]
  475. },
  476. 'third_blade': {
  477. 'x_min': min_keys[2],
  478. 'y_min': min_values[2],
  479. 'x_max': max_keys[2],
  480. 'y_max': max_values[2],
  481. 'y_diff': np.abs(max_values[2] - min_values[2]),
  482. 'y_ava': mean_values[2]
  483. }
  484. },
  485. 'blade_twist': {
  486. 'blade_1': twist_1,
  487. 'blade_2': twist_2,
  488. 'blade_3': twist_3,
  489. 'blade_avg': twist_avg
  490. },
  491. 'tower_vibration': {
  492. 'max_vibration': tower_max,
  493. 'main_vibration_freq': tower_freq
  494. }
  495. }
  496. }
  497. # 获取当前程序的绝对路径
  498. python_interpreter_path = sys.executable
  499. project_directory = os.path.dirname(python_interpreter_path)
  500. data_folder = os.path.join(project_directory, 'data')
  501. # 检查data文件夹是否存在,如果不存在则创建
  502. if not os.path.exists(data_folder):
  503. os.makedirs(data_folder)
  504. # CSV文件路径
  505. csv_file_path = os.path.join(data_folder, 'history_data.csv')
  506. # 检查CSV文件是否存在,如果不存在则创建一个空的CSV文件
  507. if not os.path.exists(csv_file_path):
  508. pd.DataFrame(columns=['时间', '场站', '风机编号', '采样频率',
  509. '叶片1角度偏差', '叶片2角度偏差', '叶片3角度偏差', '相对角度偏差',
  510. '叶片1净空值', '叶片2净空值', '叶片3净空值',
  511. '叶片1扭转', '叶片2扭转', '叶片3扭转', '平均扭转',
  512. '振动幅值', '振动主频']).to_csv(csv_file_path, index=False)
  513. df_new_row.to_csv(csv_file_path, mode='a', header=False, index=False)
  514. time_code_cleaned = time_code.replace("-", "").replace(":", "").replace(" ", "")
  515. json_filename = f"{wind_name}_{turbine_code}_{time_code_cleaned}.json"
  516. json_file_path = os.path.join(data_folder, json_filename)
  517. with open(json_file_path, 'w') as json_file:
  518. json.dump(json_output, json_file, indent=4)
  519. print('-' * 50)
  520. print('csv文件路径' + str(csv_file_path))
  521. # print(result_line_flange[0].iloc[:, 0])
  522. print('振动主频' + str(tower_freq))
  523. print('振动幅值' + str(tower_max))
  524. print('净空最小值', min_values)
  525. print('最小值对应的键', min_keys)
  526. print('净空最大值', max_values)
  527. print('最大值对应的键', max_keys)
  528. print('叶尖速度' + str(v_speed_flange), '叶根速度' + str(v_speed_root))
  529. print('新俯仰角' + str(angle_flange_new))
  530. print('轮毂中心距离' + str(dist_cen))
  531. print('叶根原始数据采样时间长度' + str(data_root.iloc[-1, 0]))
  532. print('-' * 50)
  533. plot_data(result_line_flange, blade_axis_tuple, 'line', 'data1')
  534. # plot_data(result_diff_flange, 'line', 'data_diff_1')
  535. # plot_data(result_scatter_flange, 'scatter', 'data1')
  536. plot_data(result_line_root, blade_axis_tuple_new, 'line', 'data2')
  537. plot_data(rotated_root, blade_axis_tuple_new, 'line', 'data3')
  538. # plot_data(result_diff_root, 'line', 'data_diff_2')
  539. # plot_data(result_scatter_root, blade_axis_tuple_new, 'scatter', 'data2')
  540. # plot_data(dist_distribute, 'scatter', 'dist_distribute')
  541. return json_output
  542. def process_data(file_path, skew_angle):
  543. """
  544. 打开、解决时间重置、按时间清洗异常值、分列数据
  545. """
  546. skew_angle = 90 - skew_angle
  547. # 读取第2、4、9列的数据
  548. data = pd.read_csv(file_path, usecols=[1, 3, 4, 8, 9], header=None, engine='c')
  549. data = data.head(int(len(data) * 0.95))
  550. # n = len(data)
  551. # start = int(n * 0.35) # 开始位置在35%处,这样可以取中间30%
  552. # end = int(n * 0.65) # 结束位置在65%处,这样总共是30%的数据
  553. # data = data.iloc[start:end]
  554. print('原始数据长度' + str(len(data)))
  555. '''
  556. # 绘制原始数据图
  557. # 只取前1%的数据
  558. # data = data.head(int(len(data)* 0.01))
  559. data.columns = ['time', 'distance1', 'distance2']
  560. plt.figure(figsize=(300, 150))
  561. sns.scatterplot(data=data, x='time', y='distance1', s=50, color='green')
  562. sns.scatterplot(data=data, x='time', y='distance2', s=50, color='red')
  563. abxy = plt.gca() # 获取当前坐标轴对象
  564. plt.grid(linewidth=2) # 设置网格线宽度为2
  565. abxy.xaxis.set_major_locator(MaxNLocator(nbins=100)) # 设置x轴主刻度的最大数量为10
  566. plt.xlabel('时间', fontsize=16, fontweight='bold') # 添加x轴标签
  567. plt.ylabel('距离(m)', fontsize=16, fontweight='bold') # 添加y轴标签
  568. abxy.tick_params(axis='x', labelsize=14, labelcolor='black', width=2) # 设置x轴刻度标签
  569. abxy.tick_params(axis='y', labelsize=14, labelcolor='black', width=2) # 设置y轴刻度标签
  570. plt.savefig(f"{"original"}.png", dpi=100, pil_kwargs={"icc_profile": False})
  571. plt.close()
  572. '''
  573. # 找到第一列中最大值和最小值的位置
  574. max_value = data.iloc[:, 0].max()
  575. max_index = data.iloc[:, 0].idxmax()
  576. min_index = data.iloc[:, 0].idxmin()
  577. # 检查最小值的位置是否是最大值位置的下一个
  578. if min_index == max_index + 1:
  579. # 将最小值及其之后的所有值都加上最大值
  580. data.iloc[min_index:, 0] += max_value
  581. # 按时间列筛选清洗异常值
  582. last_time = data.iloc[-1, 0]
  583. first_time = data.iloc[0, 0]
  584. filtered_data = data[(data.iloc[:, 0] > last_time) & (data.iloc[:, 0] < first_time)]
  585. print(f'时间列异常数据: {filtered_data}')
  586. print(f'起止时间: {first_time}, {last_time}')
  587. data = data[data.iloc[:, 0] >= first_time]
  588. data = data[data.iloc[:, 0] <= last_time]
  589. data.reset_index(drop=True, inplace=True)
  590. # 计算最小值
  591. min_time = data.iloc[:, 0].min()
  592. data.iloc[:, 0] -= min_time
  593. # 分为两组数据
  594. data_1 = data.iloc[:, [0, 1, 2]]
  595. data_2 = data.iloc[:, [0, 3, 4]]
  596. # 分别命名列
  597. data_1.columns = ['time', 'distance', 'grey']
  598. data_2.columns = ['time', 'distance', 'grey']
  599. data_1['time'] = data_1['time'] + data_1['distance'] * np.cos(np.deg2rad(skew_angle))
  600. data_1['distance'] = data_1['distance'] * np.sin(np.deg2rad(skew_angle))
  601. data_2['time'] = data_2['time'] + data_2['distance'] * np.cos(np.deg2rad(skew_angle))
  602. data_2['distance'] = data_2['distance'] * np.sin(np.deg2rad(skew_angle))
  603. data_1['time'] = data_1['time'].round().astype(int)
  604. data_2['time'] = data_2['time'].round().astype(int)
  605. return data_1, data_2
  606. def locate_filter(data_group: pd.DataFrame):
  607. """
  608. 求定位数据的叶片距离均值,并将数据标准化,以便放入cycle_calculate,用作求2组定位点的旋转角度
  609. :param data_group: process_data计算完成后的数据。
  610. :return: distance_mean:测量距离值
  611. :return: filtered_data:w后的数据
  612. """
  613. # 创建新的DataFrame以避免修改原始数据
  614. filtered_data = data_group.copy()
  615. # 去掉distance为0的行
  616. data_group = data_group[data_group['distance'] != 0]
  617. # 计算grey列20%-80%分位数的均值
  618. grey_mean = data_group['grey'].quantile(0.2)
  619. grey_upper = data_group['grey'].quantile(0.8)
  620. grey_threshold = data_group[(data_group['grey'] >= grey_mean) & (data_group['grey'] <= grey_upper)]['grey'].mean()
  621. # 去掉grey小于均值*0.8的行
  622. data_group = data_group[data_group['grey'] >= grey_threshold * 0.8]
  623. # 计算剩余行distance列的均值
  624. distance_mean = data_group['distance'].mean()
  625. # 将distance为0的值替换为distance_mean+10
  626. filtered_data.loc[filtered_data['distance'] == 0, 'distance'] = distance_mean + 10
  627. return distance_mean, filtered_data
  628. def tower_filter(data_group: pd.DataFrame, noise_threshold: float):
  629. """
  630. 对轮毂中心数据进行降噪,和前项填充
  631. :param data_group: process_data计算完成后轮毂中心的数据。
  632. :param noise_threshold: 去掉占比小于noise_threshold的数据。
  633. :return: filtered_data:降噪后的数据
  634. """
  635. print('正在进行数据清洗......')
  636. time.sleep(1)
  637. # 计算distance的分布
  638. distance_counts = data_group['distance'].value_counts(normalize=True)
  639. noise_distance_threshold = distance_counts[distance_counts < noise_threshold].index
  640. noise_indices = data_group[data_group['distance'].isin(noise_distance_threshold)].index
  641. data_group.loc[noise_indices, 'distance'] = np.nan
  642. # 选择频率最大的5个值
  643. top_5_distances = distance_counts.head(5).index
  644. mean_values = data_group[data_group['distance'].isin(top_5_distances)]['distance'].mean()
  645. data_group.loc[(data_group['distance'] < mean_values*0.9) | (
  646. data_group['distance'] > mean_values*1.1), 'distance'] = np.nan
  647. nan_count = data_group['distance'].isna().sum()
  648. all_count = data_group.shape[0]
  649. print(f"中值是:{mean_values},替换为NaN的异常distance值的数量是: {nan_count}, 总数量是: {all_count},"
  650. f"占比: {nan_count / all_count * 100:.2f}%")
  651. # 前向填充
  652. data_group['distance'] = data_group['distance'].fillna(method='ffill')
  653. filtered_data = data_group
  654. return filtered_data
  655. def cycle_calculate(data_group: pd.DataFrame, noise_threshold: float, min_distance: float):
  656. """
  657. 对数据进行降噪,和前项填充;计算数据的周期节点,叶片前缘突变点、后缘突变点
  658. :param data_group: process_data计算完成后的数据。
  659. :param noise_threshold: 去掉占比小于noise_threshold的数据。
  660. :param min_distance: 区分叶片和塔筒的距离差值。
  661. :return: start_points:周期开始点, end_points:周期结束点, filtered_data:降噪后的数据
  662. """
  663. print('正在计算周期节点......')
  664. time.sleep(1)
  665. # 计算distance的分布
  666. distance_counts = data_group['distance'].value_counts(normalize=True)
  667. noise_distance_threshold = distance_counts[distance_counts < noise_threshold].index
  668. noise_indices = data_group[data_group['distance'].isin(noise_distance_threshold)].index
  669. data_group.loc[noise_indices, 'distance'] = np.nan
  670. print(distance_counts.get(0, 0))
  671. if distance_counts.get(0, 0) >= 0.1:
  672. top_5_distances = distance_counts[distance_counts.index != 0].head(5).index
  673. mean_values = data_group[data_group['distance'].isin(top_5_distances)]['distance'].mean()
  674. data_group.loc[((data_group['distance'] > 0) & (data_group['distance'] < mean_values - 30)) |
  675. (data_group['distance'] > mean_values * 1.1), 'distance'] = np.nan
  676. else:
  677. # 选择频率最大的5个值
  678. top_5_distances = distance_counts[distance_counts.index != 0].head(5).index
  679. mean_values = data_group[data_group['distance'].isin(top_5_distances)]['distance'].mean()
  680. data_group.loc[(data_group['distance'] < mean_values-30) | (
  681. data_group['distance'] > mean_values*1.1), 'distance'] = np.nan
  682. print(f"中值是:{mean_values}")
  683. nan_count = data_group['distance'].isna().sum()
  684. all_count = data_group.shape[0]
  685. print(f"替换为NaN的distance异常值的数量是: {nan_count}, 总数量是: {all_count},"
  686. f"占比: {nan_count / all_count * 100:.2f}%")
  687. # 前向填充
  688. data_group['distance'] = data_group['distance'].fillna(method='ffill')
  689. filtered_data = data_group
  690. # 计算相邻两行distance的差值
  691. filtered_data['distance_diff'] = filtered_data['distance'].diff()
  692. large_diff_indices = filtered_data[filtered_data['distance_diff'] > min_distance].index
  693. small_diff_indices = filtered_data[filtered_data['distance_diff'] < -min_distance].index
  694. filtered_data = filtered_data.drop(columns=['distance_diff'])
  695. start_points = pd.DataFrame()
  696. end_points = pd.DataFrame()
  697. # 遍历所有差值大于的行
  698. for idx in large_diff_indices:
  699. # 获取当前行的 distance 值
  700. current_distance = filtered_data.loc[idx, 'distance']
  701. next_rows_large = filtered_data.loc[idx - 500: idx - 1]
  702. # 检查是否任意 distance 的值小于 current_distance - 2
  703. if next_rows_large['distance'].le(current_distance - min_distance).all():
  704. # 如果都小于,则将当前行和下一行添加到 special_points 中
  705. end_points = pd.concat([end_points, filtered_data.loc[[idx - 1]]])
  706. for idx in small_diff_indices:
  707. # 获取当前行的 distance 值
  708. current_distance = filtered_data.loc[idx - 1, 'distance']
  709. next_rows_small = filtered_data.iloc[idx: idx + 500]
  710. # 检查是否任意 distance 的值小于 current_distance - 2
  711. if next_rows_small['distance'].le(current_distance - min_distance).all():
  712. # 如果都小于,则将当前行和下一行添加到 special_points 中
  713. start_points = pd.concat([start_points, filtered_data.loc[[idx]]])
  714. if 0 in distance_counts.nlargest(3).index:
  715. end_points, start_points = start_points, end_points # 互换
  716. if end_points.iloc[0, 0] < start_points.iloc[0, 0]:
  717. end_points = end_points.drop(end_points.index[0])
  718. if end_points.iloc[-1, 0] < start_points.iloc[-1, 0]:
  719. start_points = start_points.drop(start_points.index[-1])
  720. else:
  721. pass
  722. return start_points, end_points, filtered_data
  723. def data_normalize(data_group: pd.DataFrame, start_points: pd.DataFrame, end_points: pd.DataFrame, group_len: int) \
  724. -> Tuple[List[pd.DataFrame], List[pd.DataFrame], List[pd.DataFrame], int, list]:
  725. """
  726. 提取每个叶片的数据并归一化,输出散点图和拟合图
  727. :param data_group: cycle_calculate计算完成后的数据。
  728. :param start_points: 所有每个周期开始点,叶片前缘突变点。
  729. :param end_points: 叶片后缘突变点。
  730. :param group_len: 每个分组的长度。
  731. :return: turbines_processed: 每个叶片的拟合数据,
  732. turbines_scattered: 每个叶片的散点数据,
  733. border_rows: 每个叶片的2个边缘数据,
  734. normalize_cycle: 周期长度
  735. """
  736. print('正在进行各周期归一化......')
  737. time.sleep(1)
  738. combined_df_sorted = pd.concat([start_points, end_points]).sort_values(by='time')
  739. # 检查排序后的数据从start开始,end结束
  740. if combined_df_sorted.iloc[0].equals(end_points.iloc[0]):
  741. combined_df_sorted = combined_df_sorted.iloc[1:]
  742. if combined_df_sorted.iloc[-1].equals(start_points.iloc[-1]):
  743. combined_df_sorted = combined_df_sorted.iloc[:-1]
  744. combined_df_sorted.reset_index(drop=True, inplace=True)
  745. # 将 start_points 中的时间点转换为列表
  746. start_times = combined_df_sorted['time'].tolist()
  747. print('本次测量风机完整旋转圈数:'+ str(int(len(start_times) / 6)))
  748. time.sleep(1)
  749. normalize_cycle = int(start_times[1] - start_times[0])
  750. full_cycle = int((start_times[2] - start_times[0]) * 3)
  751. turbines = [pd.DataFrame() for _ in range(3)]
  752. # 遍历所有起始时间点
  753. for i in range(0, len(start_times), 2):
  754. # 获取当前起始和结束时间点
  755. start_time = start_times[i]
  756. end_time = start_times[i + 1]
  757. # 根据当前起始时间点和结束时间点对数据进行分段
  758. segment = data_group[(data_group['time'] > start_time) & (data_group['time'] <= end_time)]
  759. if not segment.empty:
  760. # 周期归一化
  761. ratio = (end_time - start_time) / normalize_cycle
  762. segment.loc[:, 'time'] = (segment['time'] - start_time) / ratio
  763. # segment.loc[:, 'distance'] = ff.butter_lowpass_filter(segment['distance'], cutoff_low, fs)
  764. # 将结果添加到相应的 turbine 数据框中
  765. turbines[int(i / 2) % 3] = pd.concat([turbines[int(i / 2) % 3], segment])
  766. # 数据分组清洗、求平均
  767. turbines_processed = []
  768. turbines_scattered = []
  769. min_list = []
  770. plot_points = []
  771. diff_line = []
  772. sd_time = [-1, -1]
  773. time_list = list(range(0, normalize_cycle, group_len))
  774. # time_list = [(i + 1) * normalize_cycle / fs * 100 for i in range(fs * 100)] # 生成时间序列
  775. for turbine in turbines:
  776. # 按时间排序
  777. turbine_sorted = turbine.sort_values(by='time').reset_index(drop=True)
  778. grey_start_index = int(len(turbine_sorted) * 0.1)
  779. grey_end_index = int(len(turbine_sorted) * 0.9)
  780. subset_grey = turbine_sorted[grey_start_index:grey_end_index]
  781. turbine_sorted = ff.median_filter_correction(turbine_sorted, 2, 10)
  782. mean_grey = subset_grey['grey'].mean() * 0.8 # 0.8
  783. # 计算分界点
  784. n = len(turbine_sorted)
  785. n_10 = int(0.1 * n)
  786. # 创建筛选掩码
  787. is_extreme = (turbine_sorted.index < n_10) | (turbine_sorted.index >= len(turbine_sorted) - n_10)
  788. meets_condition = turbine_sorted['grey'] > mean_grey
  789. # 筛选:如果是前10%或后10%,需要满足条件;如果是中间80%,直接保留
  790. turbine_sorted = turbine_sorted[~is_extreme | (is_extreme & meets_condition)]
  791. # 找到time列的第一个值
  792. first_time = turbine_sorted['time'].iloc[0]
  793. # 分组,时间列每1000为一组(每40个时间点一组)
  794. bins = list(range(int(first_time), int(turbine_sorted['time'].max()), group_len))
  795. grouped = turbine_sorted.groupby(pd.cut(turbine_sorted['time'], bins=bins, right=False))
  796. # 初始化一个空的 DataFrame 用于存储处理后的数据
  797. processed_df = pd.DataFrame()
  798. scattered_df = pd.DataFrame()
  799. mean_points = []
  800. diff_points = []
  801. # 对每个组进行处理
  802. for _, group in grouped:
  803. # 去除 distance 最大和最小的前5%
  804. quantile_5 = group['distance'].quantile(0.05)
  805. quantile_95 = group['distance'].quantile(0.95)
  806. filtered_group = group[(group['distance'] > quantile_5) & (group['distance'] < quantile_95)]
  807. # 计算均值
  808. mean_point = filtered_group['distance'].mean()
  809. mean_points.append(mean_point)
  810. # 遍历 mean_points 列表,计算每个元素与其下一个元素的差值
  811. for i in range(len(mean_points) - 1):
  812. diff = abs(mean_points[i + 1] - mean_points[i])
  813. diff_points.append(diff)
  814. start_index = int(len(diff_points) * 0.05)
  815. end_index = int(len(diff_points) * 0.95)
  816. subset1 = diff_points[start_index:end_index]
  817. sdr_diff = np.max(subset1) * 1.1 # 1.1
  818. min_list.append(min(mean_points))
  819. # 找到第一个和最后一个小于 sdr_diff 的序号
  820. first_index = np.where(diff_points < sdr_diff)[0][0]
  821. last_index = np.where(diff_points < sdr_diff)[0][-1]
  822. plot_points.append(diff_points)
  823. diff_line.append(sdr_diff)
  824. for index, (bin, group) in enumerate(grouped):
  825. # 去除 distance 最大和最小的前5%
  826. quantile_5 = group['distance'].quantile(0.05)
  827. quantile_95 = group['distance'].quantile(0.95)
  828. filtered_group = group[(group['distance'] > quantile_5) & (group['distance'] < quantile_95)]
  829. if first_index <= index < last_index: # 如果斜率小于,则认为该组数据不是突变点
  830. # 计算中点
  831. mid_point = filtered_group.mean()
  832. # 将中点转换为 DataFrame 并添加到处理后的 DataFrame 中
  833. mid_point_df = pd.DataFrame([mid_point])
  834. mid_point_df.iloc[0, 0] = time_list[index]
  835. processed_df = pd.concat([processed_df, mid_point_df], ignore_index=True)
  836. scattered_df = pd.concat([scattered_df, filtered_group], ignore_index=True)
  837. else: pass
  838. # 找到time列的最小值和最大值
  839. min_time = processed_df['time'].min()
  840. max_time = processed_df['time'].max()
  841. if sd_time == [-1, -1]:
  842. sd_time = [min_time, max_time]
  843. elif sd_time[0] < min_time:
  844. sd_time[0] = min_time
  845. elif sd_time[1] > max_time:
  846. sd_time[1] = max_time
  847. # 将处理后的 DataFrame 添加到列表中
  848. turbines_processed.append(processed_df)
  849. turbines_scattered.append(scattered_df)
  850. """# 创建一个总图中有3个分图的形式
  851. fig, axs = plt.subplots(1, 3, figsize=(15, 9))
  852. plt.subplots_adjust(wspace=0.3) # 调整子图之间的水平间距
  853. # 绘制第一张图
  854. axs[0].plot(plot_points[0], label='Diff Points', color='blue', marker='x', markersize=5)
  855. axs[0].axhline(y=diff_line[0], color='red', linestyle='--')
  856. axs[0].legend()
  857. axs[0].set_title('Diff Points (Index 1)')
  858. axs[0].set_xlabel('Index')
  859. axs[0].set_ylabel('Value')
  860. # 绘制第二张图
  861. axs[1].plot(plot_points[1], label='Diff Points', color='blue', marker='x', markersize=5)
  862. axs[1].axhline(y=diff_line[1], color='red', linestyle='--')
  863. axs[1].legend()
  864. axs[1].set_title('Diff Points (Index 2)')
  865. axs[1].set_xlabel('Index')
  866. axs[1].set_ylabel('Value')
  867. # 绘制第三张图
  868. axs[2].plot(plot_points[2], label='Diff Points', color='blue', marker='x', markersize=5)
  869. axs[2].axhline(y=diff_line[2], color='red', linestyle='--')
  870. axs[2].legend()
  871. axs[2].set_title('Diff Points (Index 3)')
  872. axs[2].set_xlabel('Index')
  873. axs[2].set_ylabel('Value')
  874. # 显示图形
  875. plt.tight_layout()
  876. plt.show()"""
  877. # 把三组叶片数据按sd_time进行筛选,并把每个的边界数据保存
  878. border_rows = []
  879. for i, turbine in enumerate(turbines_processed):
  880. # 找到离 sd_time[0] 最近的行的索引
  881. closest_index_0 = (turbine['time'] - sd_time[0]).abs().idxmin()
  882. turbine.at[closest_index_0, 'time'] = sd_time[0]
  883. sd_time_row_0 = turbine.loc[closest_index_0]
  884. # 找到离 sd_time[1] 最近的行的索引
  885. closest_index_1 = (turbine['time'] - sd_time[1]).abs().idxmin()
  886. turbine.at[closest_index_1, 'time'] = sd_time[1]
  887. sd_time_row_1 = turbine.loc[closest_index_1]
  888. # 切片 turbine,从 closest_index_0 到 closest_index_1
  889. turbines_processed[i] = turbine.iloc[closest_index_0:closest_index_1 + 1].reset_index(drop=True)
  890. sd_time_rows_turbine = pd.concat([pd.DataFrame([sd_time_row_0]), pd.DataFrame([sd_time_row_1])]
  891. , ignore_index=True)
  892. border_rows.append(sd_time_rows_turbine)
  893. return turbines_processed, turbines_scattered, border_rows, full_cycle, min_list
  894. def blade_shape(turbines_processed: List[pd.DataFrame]):
  895. """
  896. 计算叶片平均形状、叶片形状偏差。
  897. :param turbines_processed:叶片拟合曲线数据,来自data_normalize
  898. :return: 叶片平均形状、叶片形状偏差
  899. """
  900. print('正在进行叶片外形偏差计算......')
  901. row_counts = [df.shape[0] for df in turbines_processed]
  902. num_rows = min(row_counts)
  903. # 创建一个新的data.frame用于保存结果
  904. turbine_avg = pd.DataFrame(index=range(num_rows), columns=['time', 'distance'])
  905. turbine_diff = [pd.DataFrame(index=range(num_rows), columns=['time', 'distance']) for _ in turbines_processed]
  906. # 遍历每一行
  907. for i in range(num_rows):
  908. distances = [df.loc[i, 'distance'] for df in turbines_processed] # 获取每个data.frame的distance列的值
  909. avg_distance = sum(distances) / len(distances) # 计算distance列的平均值
  910. time_value = turbines_processed[0].loc[i, 'time'] # 获取time列的值
  911. turbine_avg.loc[i, 'time'] = time_value
  912. turbine_avg.loc[i, 'distance'] = avg_distance
  913. for j in range(len(distances)):
  914. distances[j] = distances[j] - avg_distance
  915. turbine_diff[j].loc[i, 'time'] = time_value
  916. turbine_diff[j].loc[i, 'distance'] = distances[j]
  917. return turbine_avg, turbine_diff
  918. def coordinate_normalize(tip_border_rows: List[pd.DataFrame], tip_angle):
  919. """
  920. 将叶尖、法兰测量数据和叶根、轮毂中心的测量原点归一化。
  921. :param tip_border_rows: 3个叶尖边缘数据
  922. :param tip_angle: 叶尖测量俯仰角
  923. :return: 归一化后叶尖数据,新俯仰角
  924. """
  925. tip_angle1 = np.deg2rad(tip_angle)
  926. tip_angle_list = []
  927. for turbine in tip_border_rows:
  928. tip_angle_cal0 = ((np.sin(tip_angle1) * turbine['distance'] - 0.07608) /
  929. (np.cos(tip_angle1) * turbine['distance']))
  930. tip_angle_cal = np.arctan(tip_angle_cal0)
  931. turbine['distance'] = (turbine['distance']**2 + 0.0057881664 -
  932. 0.15216*turbine['distance']*np.sin(tip_angle1)) ** 0.5
  933. tip_angle_list.append(tip_angle_cal)
  934. tip_angle_new = float(np.mean(tip_angle_list))
  935. tip_angle_new1 = np.rad2deg(tip_angle_new)
  936. print('叶尖俯仰角: ' + str(tip_angle) + '坐标转换后的新叶尖俯仰角: ' + str(tip_angle_new1))
  937. return tip_border_rows, tip_angle_new1
  938. def flange_coordinate_normalize(flange_cen_row: pd.DataFrame, flange_angle):
  939. """
  940. 将法兰中心计算数据测量原点归一化。
  941. :param flange_cen_row: 法兰数据
  942. :param flange_angle: 法兰测量俯仰角
  943. :return: 归一化后法兰数据,新俯仰角
  944. """
  945. flange_angle1 = np.deg2rad(flange_angle)
  946. center_y_mean = flange_cen_row['中心y'].mean()
  947. # 计算新的俯仰角
  948. flange_angle_cal0 = ((np.sin(flange_angle1) * center_y_mean - 0.07608) /
  949. (np.cos(flange_angle1) * center_y_mean))
  950. flange_angle_cal = np.arctan(flange_angle_cal0)
  951. # 更新中心y列
  952. flange_cen_row['中心y'] = (flange_cen_row['中心y'] ** 2 + 0.0057881664 -
  953. 0.15216 * flange_cen_row['中心y'] * np.sin(flange_angle1)) ** 0.5
  954. # 计算新的俯仰角(由于现在只有一个值,直接使用计算出的值)
  955. flange_angle_new = float(flange_angle_cal)
  956. flange_angle_new1 = np.rad2deg(flange_angle_new)
  957. print('坐标转换后的新法兰俯仰角: ' + str(flange_angle_new1))
  958. return flange_cen_row, flange_angle_new1
  959. def blade_axis_cal(data_group: pd.DataFrame, start_points: pd.DataFrame, end_points: pd.DataFrame, horizon_angle: float,
  960. group_len: int, radius_blade: float):
  961. """
  962. 每个叶片周期计算旋转中心及半径
  963. :param data_group: cycle_calculate计算完成后的叶根法兰数据。
  964. :param start_points: 所有每个周期开始点,叶片前缘突变点。
  965. :param end_points: 叶片后缘突变点。
  966. :param horizon_angle: 叶根法兰水平角度。
  967. :param group_len: 每数据长度。
  968. :param radius_blade: 半径。
  969. :return: 叶根法兰圆轮廓的圆心、测量点的旋转半径
  970. """
  971. print('正在进行叶片轴线计算......')
  972. def fit_circle(df, v_fixed, top_k=5, prefilter=True):
  973. """
  974. 修改版:速度v作为已知量,只优化圆心和半径
  975. Savitzky–Golay 平滑 + 差分进化 + least_squares 精修
  976. """
  977. # ========== Savitzky–Golay 平滑 ==========
  978. def smooth_savgol(y, window_length=101, polyorder=3):
  979. wl = min(window_length, len(y) if len(y) % 2 == 1 else len(y) - 1)
  980. if wl < 3:
  981. return y
  982. if wl % 2 == 0:
  983. wl -= 1
  984. return savgol_filter(y, wl, polyorder)
  985. t = np.asarray(df['time'])
  986. d_raw = np.asarray(df['distance'])
  987. # ---------- 平滑 ----------
  988. d_smooth = smooth_savgol(d_raw, window_length=101, polyorder=3) if prefilter else d_raw
  989. # 计算x坐标(已知速度v)
  990. x = v_fixed * t
  991. # 新的参数边界:只优化圆心坐标(xc, yc)和半径R
  992. bounds = [(min(x) - 5, max(x) + 5), # xc范围:x值范围附近
  993. (min(d_smooth), max(d_smooth) + 10), # yc范围:y值范围附近
  994. (0.5, 10)] # R范围
  995. def residuals_sq(params):
  996. # 参数现在只有三个:xc, yc, R
  997. xc, yc, R = params
  998. if R <= 0:
  999. return 1e6 * np.ones_like(t)
  1000. return (x - xc) ** 2 + (d_smooth - yc) ** 2 - R ** 2
  1001. def objective_mean_sq(params):
  1002. res = residuals_sq(params)
  1003. return np.mean(res ** 2)
  1004. # ---------- 差分进化 ----------
  1005. result = differential_evolution(
  1006. objective_mean_sq,
  1007. bounds,
  1008. strategy='rand2bin',
  1009. mutation=(0.8, 1.2),
  1010. recombination=0.8,
  1011. popsize=30,
  1012. maxiter=1000,
  1013. polish=False,
  1014. seed=42,
  1015. workers=1
  1016. )
  1017. # 多候选点精修
  1018. pop = result.population
  1019. energies = result.population_energies
  1020. idx = np.argsort(energies)[:top_k]
  1021. candidates = pop[idx]
  1022. best_rmse = np.inf
  1023. best_result = None
  1024. for cand in candidates:
  1025. res = least_squares(
  1026. residuals_sq,
  1027. x0=cand,
  1028. bounds=([-np.inf, -np.inf, 1e-6], # 下界
  1029. [np.inf, np.inf, np.inf]), # 上界
  1030. method='trf',
  1031. loss='linear',
  1032. max_nfev=50000,
  1033. xtol=1e-12,
  1034. ftol=1e-12,
  1035. gtol=1e-12
  1036. )
  1037. xc_opt, yc_opt, R_opt = res.x
  1038. Ri_all = np.sqrt((x - xc_opt) ** 2 + (d_smooth - yc_opt) ** 2)
  1039. geo_rmse = np.sqrt(np.mean((Ri_all - R_opt) ** 2))
  1040. if geo_rmse < best_rmse:
  1041. best_rmse = geo_rmse
  1042. best_result = [v_fixed, xc_opt, yc_opt, R_opt, geo_rmse]
  1043. result_df = pd.DataFrame([best_result],
  1044. columns=['旋转半径', '中心x', '中心y', '圆半径', '几何RMSE'])
  1045. return result_df
  1046. def plot_circle_fit(df, fit_result):
  1047. """
  1048. df: 原始数据,包含 'time' 和 'distance'
  1049. fit_result: fit_circle 输出的 DataFrame
  1050. """
  1051. t = np.asarray(df['time'])
  1052. d_raw = np.asarray(df['distance'])
  1053. # 最优参数
  1054. v, xc, yc, R = fit_result[['旋转半径', '中心x', '中心y', '圆半径']].values[0]
  1055. # x 轴乘速度后的原始散点
  1056. x_all = v * t
  1057. plt.figure(figsize=(8, 8))
  1058. # 原始散点
  1059. plt.scatter(x_all, d_raw, s=10, color='blue', alpha=0.5, label='原始散点 (v*t)')
  1060. # 拟合圆
  1061. theta = np.linspace(0, 2 * np.pi, 500)
  1062. x_circle = xc + R * np.cos(theta)
  1063. y_circle = yc + R * np.sin(theta)
  1064. plt.plot(x_circle, y_circle, color='red', lw=2, label='拟合圆')
  1065. # 圆心
  1066. plt.scatter([xc], [yc], color='green', s=50, label='圆心')
  1067. plt.xlabel('x = v * t')
  1068. plt.ylabel('distance')
  1069. plt.axis('equal')
  1070. plt.legend()
  1071. plt.title('速度乘时间后的散点与拟合圆')
  1072. plt.show()
  1073. combined_df_sorted = pd.concat([start_points, end_points]).sort_values(by='time')
  1074. # 检查排序后的数据从start开始,end结束
  1075. if combined_df_sorted.iloc[0].equals(end_points.iloc[0]):
  1076. combined_df_sorted = combined_df_sorted.iloc[1:]
  1077. if combined_df_sorted.iloc[-1].equals(start_points.iloc[-1]):
  1078. combined_df_sorted = combined_df_sorted.iloc[:-1]
  1079. combined_df_sorted.reset_index(drop=True, inplace=True)
  1080. # 将 start_points 中的时间点转换为列表
  1081. start_times = combined_df_sorted['time'].tolist()
  1082. data_group['distance'] = data_group['distance'] * np.cos(np.deg2rad(horizon_angle))
  1083. normalize_cycle = start_times[1] - start_times[0]
  1084. full_cycle = int((start_times[2] - start_times[0]) * 3)
  1085. v_blade = 10000000 * np.pi * radius_blade / full_cycle
  1086. angle_speed = (np.pi / full_cycle) * 5000000
  1087. turbines = [pd.DataFrame() for _ in range(3)]
  1088. for i in range(0, len(start_times), 2):
  1089. start_time = start_times[i]
  1090. end_time = start_times[i + 1]
  1091. segment = data_group[(data_group['time'] > start_time) & (data_group['time'] <= end_time)]
  1092. if segment is None or segment.empty:
  1093. raise ValueError("Segment is empty")
  1094. segment = segment.copy()
  1095. ratio = (end_time - start_time) / normalize_cycle
  1096. segment.loc[:, 'time'] = (segment['time'] - start_time) / ratio
  1097. turbines[int(i / 2) % 3] = pd.concat([turbines[int(i / 2) % 3], segment])
  1098. turbines_processed = []
  1099. turbines_scattered = []
  1100. result_df = pd.DataFrame()
  1101. time_list = list(range(0, normalize_cycle, group_len))
  1102. for turbine in turbines:
  1103. # 按时间排序
  1104. turbine_sorted = turbine.sort_values(by='time').reset_index(drop=True)
  1105. # 找到time列的第一个值
  1106. first_time = turbine_sorted['time'].iloc[0]
  1107. # 分组,时间列每1000为一组(每40个时间点一组)
  1108. bins = list(range(int(first_time), int(turbine_sorted['time'].max()), group_len))
  1109. grouped = turbine_sorted.groupby(pd.cut(turbine_sorted['time'], bins=bins, right=False))
  1110. process_df = pd.DataFrame()
  1111. for index, (bin, group) in enumerate(grouped):
  1112. mid_point = group.mean()
  1113. # 将中点转换为 DataFrame 并添加到处理后的 DataFrame 中
  1114. mid_point_df = pd.DataFrame([mid_point])
  1115. mid_point_df.iloc[0, 0] = time_list[index]
  1116. process_df = pd.concat([process_df, mid_point_df], ignore_index=True)
  1117. process_df['time'] = process_df['time'] / 5000000
  1118. lower_bound = process_df['time'].quantile(0.2)
  1119. upper_bound = process_df['time'].quantile(0.8)
  1120. processed_df = process_df[(process_df['time'] >= lower_bound) & (process_df['time'] <= upper_bound)]
  1121. blade_cen_est = fit_circle(processed_df, v_blade)
  1122. plot_circle_fit(processed_df, blade_cen_est)
  1123. processed_df['time'] = processed_df['time'] * v_blade
  1124. turbines_processed.append(processed_df)
  1125. turbines_scattered.append(turbine)
  1126. result_df = pd.concat([result_df, blade_cen_est], ignore_index=True)
  1127. if blade_cen_est['几何RMSE'].iloc[0] >= 0.1:
  1128. raise ValueError("叶片几何误差过大")
  1129. result_df_mean = result_df.mean(numeric_only=True).to_frame().T
  1130. result_df_mean["中心y"] = result_df_mean["中心y"] / np.cos(np.deg2rad(horizon_angle))
  1131. result_df["中心y"] = result_df["中心y"] / np.cos(np.deg2rad(horizon_angle))
  1132. # turbines_mean["中心x"] = turbines_mean["中心x"] * 5000000
  1133. # turbines_mean["圆半径"] = turbines_mean["圆半径"] / np.cos(np.deg2rad(horizon_angle))
  1134. # turbines_mean['旋转半径'] = turbines_mean['旋转半径'] / angle_speed
  1135. print(result_df)
  1136. return result_df_mean, result_df, turbines_processed
  1137. def radius_cal(border_rows, meas_angle, cen_dist, cen_angle, angle_main, angle_rotate):
  1138. """
  1139. 计算测量点处的旋转半径。
  1140. :param border_rows: 三个叶片的边界
  1141. :param meas_angle: 回波俯仰角
  1142. :param cen_dist: 轮毂中心距离
  1143. :param cen_angle: 轮毂中心俯仰角
  1144. :param angle_main: 主轴倾角
  1145. :param angle_rotate: 锥角
  1146. :return: 旋转半径
  1147. """
  1148. aero_dist = (pd.concat([df['distance'] for df in border_rows]).mean())
  1149. radius = np.abs(aero_dist * np.sin(np.deg2rad(meas_angle - angle_main))
  1150. - cen_dist * np.sin(np.deg2rad(cen_angle - angle_main)))
  1151. return radius
  1152. def blade_angle_aero_dist(border_rows: List[pd.DataFrame], radius: float, full_cycle: int,
  1153. tower_dist: float, v_angle: float):
  1154. """
  1155. 计算叶片相对桨距角和叶片净空距离。
  1156. :param border_rows: 三个叶片的边界
  1157. :param radius: 旋转半径
  1158. :param full_cycle: 全周期
  1159. :param tower_dist: 塔筒距离
  1160. :param v_angle: 俯仰角度
  1161. :return: 净空距离,叶片线速度,叶片中心距离
  1162. """
  1163. print('正在进行叶片净空距离计算......')
  1164. v_speed = 2 * np.pi * radius / full_cycle # 叶片线速度m/(1计时器单位)
  1165. aero_dist_list = []
  1166. cen_blade = []
  1167. for turbine in border_rows:
  1168. mean_col2 = (turbine.iloc[1, 1] + turbine.iloc[0, 1]) / 2
  1169. aero_dist = abs(mean_col2 - tower_dist) * np.cos(np.deg2rad(v_angle))
  1170. aero_dist_list.append(aero_dist)
  1171. cen_blade.append(mean_col2)
  1172. aero_dist_list.append(np.mean(aero_dist_list))
  1173. aero_dist_list = [round(num, 2) for num in aero_dist_list]
  1174. return aero_dist_list, v_speed, cen_blade
  1175. def blade_angle(border_rows: List[pd.DataFrame], cen_data: pd.DataFrame, radius: float, full_cycle: int,
  1176. v_angle: float):
  1177. """
  1178. 计算叶片相对桨距角。
  1179. :param border_rows: 三个叶片的边界
  1180. :param cen_data: 旋转中心数据
  1181. :param radius: 旋转半径
  1182. :param full_cycle: 全周期
  1183. :param v_angle: 俯仰角度
  1184. :return: 绝对桨距角,叶片线速度
  1185. """
  1186. print('正在进行相对桨距角计算......')
  1187. print('叶根半径:' + str(radius))
  1188. v_speed = 2 * np.pi * radius / full_cycle # 叶片线速度m/(1计时器单位)
  1189. values = []
  1190. for df in border_rows:
  1191. if df.shape[0] >= 2 and df.shape[1] >= 2:
  1192. values.append(df.iloc[0, 1])
  1193. values.append(df.iloc[1, 1])
  1194. mean_value = sum(values) / len(values) if values else float('nan')
  1195. for i in [0, 1, 2]:
  1196. if np.abs(cen_data['中心y'].iloc[i] - mean_value) > 0.5:
  1197. print('原本:' + str(cen_data['中心y'].iloc[i]) + '标准:' + str(mean_value))
  1198. cen_data['中心y'].iloc[i] = mean_value
  1199. print('y_change')
  1200. if cen_data['中心x'].iloc[i] > 1.5:
  1201. cen_data['中心x'].iloc[i] = 1.5
  1202. print('x_change')
  1203. if cen_data['中心x'].iloc[i] < 0.75:
  1204. cen_data['中心x'].iloc[i] = 0.75
  1205. print('x_change')
  1206. print(cen_data['中心x'].iloc[i])
  1207. pitch_angle_list = []
  1208. for idx, turbine in enumerate(border_rows, start=1):
  1209. # 使用圆拟合的情况(注释掉的是备选方案)
  1210. diff_time = np.abs(cen_data['中心x'].iloc[idx - 1] - turbine.iloc[1, 0] * v_speed)
  1211. # diff_time = np.abs((turbine.iloc[0, 0] - turbine.iloc[1, 0]) * 0.66 * v_speed)
  1212. # 计算长度差,使用对应的cen_data行
  1213. diff_len = np.abs((cen_data['中心y'].iloc[idx - 1] - turbine.iloc[1, 1]) * np.cos(np.deg2rad(v_angle)))
  1214. # 计算桨距角
  1215. pitch_angle = np.degrees(np.arctan(diff_len / diff_time))
  1216. print(f'第{idx}个叶片绝对桨距角: {pitch_angle}')
  1217. pitch_angle_list.append(pitch_angle)
  1218. pitch_mean = np.mean(pitch_angle_list)
  1219. pitch_angle_list = [angle - pitch_mean for angle in pitch_angle_list]
  1220. pitch_angle_list.append(max(pitch_angle_list) - min(pitch_angle_list))
  1221. pitch_angle_list = [round(num, 2) for num in pitch_angle_list]
  1222. print("\033[34m叶片相对角度偏差:\033[0m", pitch_angle_list)
  1223. return pitch_angle_list, v_speed, cen_data
  1224. def plot_data(data, point, plot_type: str, data_name: str):
  1225. """
  1226. 绘制数据图表并保存为文件。
  1227. :param data: 数据列表,每个元素是一个 DataFrame。
  1228. :param point: 测量点。
  1229. :param plot_type: 图表类型,'line' 或 'scatter'。
  1230. :param data_name: 数据名称,用于生成文件名。
  1231. """
  1232. print('正在画图......')
  1233. time.sleep(1)
  1234. save_path = "C:/Users/laiwe/Desktop/"
  1235. save_name = fr"{data_name}_{plot_type}.png" # 生成文件名
  1236. plt.figure(figsize=(300, 150))
  1237. if plot_type == 'line':
  1238. for df, color in zip(data, ['blue', 'green', 'red']):
  1239. sns.lineplot(data=df, x=df.iloc[:, 0], y=df.iloc[:, 1], color=color, linewidth=8)
  1240. elif plot_type == 'scatter':
  1241. for df, (size, color) in zip(data, [(50, 'blue'), (25, 'green'), (10, 'red')]):
  1242. sns.scatterplot(data=df, x=df.iloc[:, 0], y=df.iloc[:, 1], s=size, color=color)
  1243. else:
  1244. raise ValueError("plot_type must be either 'line' or 'scatter'")
  1245. plt.scatter(point['中心x'], point['中心y'], color='black', s=1000, marker='o', label='测量点')
  1246. axy = plt.gca() # 获取当前坐标轴对象
  1247. plt.grid(which='both', linewidth=2) # 设置网格线宽度为2
  1248. axy.xaxis.set_major_locator(MaxNLocator(nbins=200)) # 设置x轴主刻度的最大数量为10
  1249. axy.yaxis.set_major_locator(MaxNLocator(nbins=100)) # 设置y轴主刻度的最大数量为10
  1250. plt.xlabel('时间', fontsize=100, fontweight='bold') # 添加x轴标签
  1251. plt.ylabel('距离(m)', fontsize=100, fontweight='bold') # 添加y轴标签
  1252. axy.tick_params(axis='x', labelsize=10, labelcolor='black', width=2) # 设置x轴刻度标签
  1253. axy.tick_params(axis='y', labelsize=60, labelcolor='black', width=10) # 设置y轴刻度标签
  1254. plt.savefig(save_path + save_name)
  1255. plt.close()
  1256. abs_path = os.path.abspath(save_name)
  1257. print(f" {save_name} 已完成")
  1258. return abs_path
  1259. def find_param(path: str):
  1260. """
  1261. 根据文件路径获取参数
  1262. """
  1263. path = path.replace('\\', '/')
  1264. last_slash_index = path.rfind('/')
  1265. result = path[last_slash_index + 1:]
  1266. underscore_indices = []
  1267. start = 0
  1268. while True:
  1269. index = result.find('_', start)
  1270. if index == -1:
  1271. break
  1272. underscore_indices.append(index)
  1273. start = index + 1
  1274. wind_name = result[: underscore_indices[0]]
  1275. turbine_code = result[underscore_indices[0] + 1: underscore_indices[1]]
  1276. time_code = result[underscore_indices[1] + 1: underscore_indices[2]]
  1277. sampling_fq = int(result[underscore_indices[2] + 1: underscore_indices[3]])
  1278. tunnel_1 = float(result[underscore_indices[3] + 1: underscore_indices[4]])
  1279. tunnel_2 = float(result[underscore_indices[4] + 1: -4])
  1280. dt = datetime.strptime(time_code, "%Y%m%d%H%M%S")
  1281. standard_time_str = dt.strftime("%Y-%m-%d %H:%M:%S")
  1282. return wind_name, turbine_code, standard_time_str, sampling_fq, tunnel_1, tunnel_2
  1283. def blade_dist_distribute_cal(data_group: pd.DataFrame, start_points: pd.DataFrame, end_points: pd.DataFrame,
  1284. tower_dist: float, v_angle: float, blade_cen_dist: list):
  1285. """
  1286. 计算每个叶片每个周期的转速和净空距离
  1287. :param data_group: cycle_calculate计算完成后的数据。
  1288. :param start_points: 所有每个周期开始点,叶片前缘突变点。
  1289. :param end_points: 叶片后缘突变点。
  1290. :param tower_dist: 塔筒距离。
  1291. :param v_angle: 测量俯仰角度。
  1292. :param blade_cen_dist: 叶片内部距离。
  1293. """
  1294. print('正在进行各周期净空距离分布计算......')
  1295. time.sleep(1)
  1296. combined_df_sorted = pd.concat([start_points, end_points]).sort_values(by='time')
  1297. # 检查排序后的数据从start开始,end结束
  1298. if combined_df_sorted.iloc[0].equals(end_points.iloc[0]):
  1299. combined_df_sorted = combined_df_sorted.iloc[1:]
  1300. if combined_df_sorted.iloc[-1].equals(start_points.iloc[-1]):
  1301. combined_df_sorted = combined_df_sorted.iloc[:-1]
  1302. combined_df_sorted.reset_index(drop=True, inplace=True)
  1303. # 将 start_points 中的时间点转换为列表
  1304. start_times = combined_df_sorted['time'].tolist()
  1305. normalize_cycle = start_times[1] - start_times[0]
  1306. tower_clearance = [pd.DataFrame() for _ in range(3)]
  1307. # 遍历所有起始时间点
  1308. for i in range(0, len(start_times) - 2, 2):
  1309. # 获取当前起始和结束时间点
  1310. start_time = start_times[i]
  1311. end_time = start_times[i + 1]
  1312. # 根据当前起始时间点和结束时间点对数据进行分段
  1313. segment = data_group[(data_group['time'] > start_time) & (data_group['time'] <= end_time)]
  1314. min_distance = segment['distance'].min()
  1315. clearance = np.abs(tower_dist - min_distance - blade_cen_dist[i % 3]) * np.cos(np.deg2rad(v_angle))
  1316. r_speed = round(60 / ((start_times[i + 2] - start_times[i]) * 3 / 5000000), 2)
  1317. new_df = pd.DataFrame({
  1318. 'r_speed': [r_speed],
  1319. 'clearance': [clearance]
  1320. })
  1321. # 将结果添加到相应的 turbine 数据框中
  1322. tower_clearance[i % 3] = pd.concat([tower_clearance[i % 3], new_df])
  1323. tower_clearance = [df.sort_values(by='r_speed') for df in tower_clearance]
  1324. return tower_clearance
  1325. if __name__ == "__main__":
  1326. # 左偏
  1327. # locate_path0 = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20250919怀来/1/hl_F14-L_20250819134200_50_11.25_13.42.csv"
  1328. # locate_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20250919怀来/1/hl_F14-L_20250819133535_50_7.55_12.10.csv"
  1329. # measure_path= "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20250919怀来/1/hl_F14-L_20250819132731_50_11.71_10.34.csv"
  1330. # 右偏
  1331. # locate_path0 = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20250919怀来/1/hl_F14-last_20250819144135_50_9.36_11.02.csv"
  1332. # locate_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20250919怀来/1/hl_F14-last_20250819143634_50_7.44_10.04.csv"
  1333. # measure_path= "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20250919怀来/1/hl_F14-last_20250819143011_50_9.81_8.57.csv"
  1334. # 左微偏
  1335. # locate_path0 = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20250919怀来/2/hl_F14-z_20250819124400_50_11.32_13.07.csv"
  1336. # locate_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20250919怀来/2/hl_F14-z_20250819124108_50_7.51_12.05.csv"
  1337. # measure_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20250919怀来/2/hl_F14-z_20250819123507_50_11.23_10.51.csv"
  1338. # 第一组正对
  1339. # locate_path0 = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-z_20251011100836_50_26.95_30.34.csv"
  1340. # locate_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-z_20251011095956_50_20.23_28.22.csv"
  1341. # measure_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-z_20251011095339_50_27.64_26.44.csv"
  1342. # 第二组正对
  1343. # locate_path0 = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-z_20251011100836_50_26.95_30.34.csv"
  1344. # locate_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-z_20251011102933_50_20.48_28.28.csv"
  1345. # measure_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-z_20251011102339_50_27.72_26.33.csv"
  1346. # 第三组左侧
  1347. # locate_path0 = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-l_20251011105053_50_25.67_27.83.csv"
  1348. # locate_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-l_20251011104654_50_18.84_26.34.csv"
  1349. # measure_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-l_20251011104100_50_25.84_24.66.csv"
  1350. # 第四组右侧
  1351. # locate_path0 = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-rr_20251011114639_50_28.64_31.72.csv"
  1352. # locate_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-rr_20251011114224_50_22.36_30.22.csv"
  1353. # measure_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-r_20251011111012_50_29.57_28.26.csv"
  1354. # 第五组左侧
  1355. # locate_path0 = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-rr_20251011114639_50_28.64_31.72.csv"
  1356. # locate_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-ll_20251011121305_50_25.22_34.48.csv"
  1357. # measure_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-ll_20251011120724_50_33.79_32.64.csv"
  1358. # 第六组正对
  1359. # locate_path0 = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-zz_20251011123416_50_31.48_33.93.csv"
  1360. # locate_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-zz_20251011123102_50_25.23_32.98.csv"
  1361. # measure_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-zz_20251011122457_50_32.24_31.02.csv"
  1362. # 第七组右侧
  1363. locate_path0 = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-r-z_20251011124902_50_31.19_34.81.csv"
  1364. locate_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-r-z_20251011124620_50_24.85_32.90.csv"
  1365. measure_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-r-z_20251011124036_50_32.24_31.14.csv"
  1366. # dq-8
  1367. # locate_path0 = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-r-z_20251011124902_50_31.19_34.81.csv"
  1368. # locate_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251023大庆/第三天/dh_8_20251109102949_50_17.22_27.45.csv"
  1369. # measure_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251023大庆/第三天/dh_8_20251109102255_50_26.18_24.06.csv"
  1370. # zb-9
  1371. # locate_path0 = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251011沽源/验证/gy_20-r-z_20251011124902_50_31.19_34.81.csv"
  1372. # locate_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251121淄博/数据/zb_9_20251122110132_50_14.19_19.83.csv"
  1373. # measure_path = "C:/Users/laiwe/Desktop/风电/激光测量/测试数据/20251121淄博/数据/zb_9_20251122105534_50_19.37_17.73.csv"
  1374. start_t = time.time() # 记录开始时间
  1375. data_path = [locate_path0, locate_path, measure_path, 3.5, 5, 1.54] # 偏斜测量数据、轮毂数据、叶根数据、锥角、轴向倾角、偏航角
  1376. list_1 = data_analyse(data_path)
  1377. print(f"耗时: {time.time() - start_t:.2f} 秒")