Compare commits
1 Commits
master
...
rough_for_
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a5f1a5a54 |
18
debug_script/Replace_missing.py
Normal file
18
debug_script/Replace_missing.py
Normal file
@ -0,0 +1,18 @@
|
||||
from pathlib import Path
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
samp_np = 10395
|
||||
missing_second = 11495+11463-6300
|
||||
missing_length = 17.29-2.81
|
||||
fs = 500
|
||||
missging_bcg = Path(rf"E:\code\DataCombine2023\ZD5Y2\OrgBCG_Text\{samp_np}\OrgBCG_Raw_500.txt")
|
||||
|
||||
bcg_data = pd.read_csv(missging_bcg, header=None).to_numpy().reshape(-1)
|
||||
miss_start = int(missing_second * fs)
|
||||
bcg_data_filled = np.concatenate([bcg_data[:miss_start],
|
||||
np.full(int(missing_length * fs), np.mean(bcg_data)),
|
||||
bcg_data[miss_start:]])
|
||||
|
||||
output_path = Path(rf"E:\code\DataCombine2023\ZD5Y2\OrgBCG_Text\{samp_np}\OrgBCG_Raw_500_filled.txt")
|
||||
np.savetxt(output_path, bcg_data_filled, fmt="%d")
|
||||
71
debug_script/show_orgBcg_psg_resp.py
Normal file
71
debug_script/show_orgBcg_psg_resp.py
Normal file
@ -0,0 +1,71 @@
|
||||
from pathlib import Path
|
||||
from matplotlib import pyplot as plt
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from func.Filters.Preprocessing import Butterworth_for_ECG_PreProcess, Butterworth_for_BCG_PreProcess
|
||||
|
||||
# # 原始
|
||||
# psg_dir = Path(r"E:\code\DataCombine2023\ZD5Y2\PSG_Text\10787")
|
||||
# org_bcg_dir = Path(r"E:\code\DataCombine2023\ZD5Y2\OrgBCG_Text\10787")
|
||||
# tho_file_path = psg_dir / "Effort Tho_Raw_100.txt"
|
||||
# abd_file_path = psg_dir / "Effort Abd_Raw_100.txt"
|
||||
# tho_fs = 100
|
||||
# abd_fs = 100
|
||||
# orgbcg_file_path = org_bcg_dir / "OrgBCG_Raw_500.txt"
|
||||
# orgbcg_fs = 500
|
||||
|
||||
# 对齐后
|
||||
psg_dir = Path(r"E:\code\DataCombine2023\ZD5Y2\PSG_Aligned\10787")
|
||||
org_bcg_dir = Path(r"E:\code\DataCombine2023\ZD5Y2\OrgBCG_Aligned\10787")
|
||||
tho_file_path = psg_dir / "Effort Tho_Sync_100.txt"
|
||||
abd_file_path = psg_dir / "Effort Abd_Sync_100.txt"
|
||||
tho_fs = 100
|
||||
abd_fs = 100
|
||||
orgbcg_file_path = org_bcg_dir / "OrgBCG_Sync_1000.txt"
|
||||
orgbcg_fs = 1000
|
||||
|
||||
|
||||
tho_raw = pd.read_csv(tho_file_path, header=None).to_numpy().reshape(-1)
|
||||
abd_raw = pd.read_csv(abd_file_path, header=None).to_numpy().reshape(-1)
|
||||
orgbcg_raw = pd.read_csv(orgbcg_file_path, header=None).to_numpy().reshape(-1)
|
||||
|
||||
tho_filtered = Butterworth_for_ECG_PreProcess(tho_raw, tho_fs, type="bandpass", low_cut=0.01, high_cut=15.0, order=4)
|
||||
abd_filtered = Butterworth_for_ECG_PreProcess(abd_raw, abd_fs, type="bandpass", low_cut=0.01, high_cut=15.0, order=4)
|
||||
orgbcg_filtered = Butterworth_for_BCG_PreProcess(orgbcg_raw, orgbcg_fs, type="bandpass", low_cut=0.01, high_cut=0.7, order=4)
|
||||
|
||||
|
||||
# 降采样
|
||||
orgbcg_filtered = orgbcg_filtered[::orgbcg_fs//10] # 从500Hz降采样到10Hz
|
||||
tho_filtered = tho_filtered[::tho_fs//10] # 从100Hz降采样到10Hz
|
||||
abd_filtered = abd_filtered[::abd_fs//10] # 从100Hz降采样到10Hz
|
||||
|
||||
plt.figure(figsize=(12, 8))
|
||||
psg_x = np.linspace(0, len(tho_filtered) / 10, len(tho_filtered))
|
||||
orgbcg_x = np.linspace(0, len(orgbcg_filtered) / 10, len(orgbcg_filtered))
|
||||
|
||||
plt.subplot(3, 1, 1)
|
||||
plt.plot(psg_x, tho_filtered, label='Thoracic Effort', color='blue')
|
||||
plt.title('Thoracic Effort Signal (Filtered)')
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Amplitude')
|
||||
plt.grid()
|
||||
plt.legend()
|
||||
|
||||
plt.subplot(3, 1, 2)
|
||||
plt.plot(psg_x, abd_filtered, label='Abdominal Effort', color='green')
|
||||
plt.title('Abdominal Effort Signal (Filtered)')
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Amplitude')
|
||||
plt.grid()
|
||||
plt.legend()
|
||||
|
||||
plt.subplot(3, 1, 3)
|
||||
plt.plot(orgbcg_x, orgbcg_filtered, label='Original BCG', color='red')
|
||||
plt.title('Original BCG Signal (Filtered)')
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Amplitude')
|
||||
plt.grid()
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
@ -48,6 +48,7 @@ ButtonState = {
|
||||
"radioButton_NTHO": False,
|
||||
"radioButton_NABD": False,
|
||||
"radioButton_custom": False,
|
||||
"radioButton_customFreq": False,
|
||||
"radioButton_freqTHO": False,
|
||||
"radioButton_freqABD": False,
|
||||
},
|
||||
@ -71,6 +72,7 @@ ButtonState = {
|
||||
"radioButton_NTHO": False,
|
||||
"radioButton_NABD": False,
|
||||
"radioButton_custom": False,
|
||||
"radioButton_customFreq": False,
|
||||
"radioButton_freqTHO": False,
|
||||
"radioButton_freqABD": False,
|
||||
}
|
||||
@ -303,6 +305,7 @@ class MainWindow_approximately_align(QMainWindow):
|
||||
self.ui.radioButton_custom.clicked.connect(self.__enableAlign__)
|
||||
self.ui.radioButton_freqTHO.clicked.connect(self.__EstimateFrequencySelect__)
|
||||
self.ui.radioButton_freqABD.clicked.connect(self.__EstimateFrequencySelect__)
|
||||
self.ui.radioButton_customFreq.clicked.connect(self.__EstimateFrequencySelect__)
|
||||
|
||||
@overrides
|
||||
def closeEvent(self, event):
|
||||
@ -353,6 +356,7 @@ class MainWindow_approximately_align(QMainWindow):
|
||||
self.ui.spinBox_SelectEpoch.setMinimum(0)
|
||||
self.ui.radioButton_freqABD.setChecked(False)
|
||||
self.ui.radioButton_freqTHO.setChecked(False)
|
||||
self.ui.radioButton_customFreq.setChecked(False)
|
||||
self.ui.radioButton_freqTHO.setText("备选1")
|
||||
self.ui.radioButton_freqABD.setText("备选2")
|
||||
|
||||
@ -664,22 +668,22 @@ class MainWindow_approximately_align(QMainWindow):
|
||||
|
||||
response = self.data.estimate_frequency(tho_bias_list)
|
||||
tho_y = response.data["estimate_y"]
|
||||
tho_frequency = response.data["frequency"]
|
||||
tho_frequency_ratio = response.data["frequency"]
|
||||
tho_slope = response.data["slope"]
|
||||
tho_intercept = response.data["intercept"]
|
||||
response = self.data.estimate_frequency(abd_bias_list)
|
||||
abd_y = response.data["estimate_y"]
|
||||
abd_frequency = response.data["frequency"]
|
||||
abd_frequency_ratio = response.data["frequency"]
|
||||
abd_slope = response.data["slope"]
|
||||
abd_intercept = response.data["intercept"]
|
||||
result = self.__plot__(epoch_min, epoch_max, tho_bias_list, abd_bias_list, tho_y, abd_y,
|
||||
tho_frequency, abd_frequency)
|
||||
tho_frequency_ratio, abd_frequency_ratio)
|
||||
Config["estimate"] = {}
|
||||
Config["estimate"]["tho_frequency"] = tho_frequency
|
||||
Config["estimate"]["tho_frequency"] = tho_frequency_ratio
|
||||
Config["estimate"]["tho_slope"] = tho_slope
|
||||
Config["estimate"]["tho_intercept"] = tho_intercept
|
||||
|
||||
Config["estimate"]["abd_frequency"] = abd_frequency
|
||||
Config["estimate"]["abd_frequency"] = abd_frequency_ratio
|
||||
Config["estimate"]["abd_slope"] = abd_slope
|
||||
Config["estimate"]["abd_intercept"] = abd_intercept
|
||||
|
||||
@ -687,6 +691,7 @@ class MainWindow_approximately_align(QMainWindow):
|
||||
self.ui.radioButton_freqABD.setText(str(Config["estimate"]["abd_frequency"] * Config["InputConfig"]["orgBcgFreq"]))
|
||||
ButtonState["Current"]["radioButton_freqTHO"] = True
|
||||
ButtonState["Current"]["radioButton_freqABD"] = True
|
||||
ButtonState["Current"]["radioButton_customFreq"] = True
|
||||
|
||||
if not result.status:
|
||||
PublicFunc.text_output(self.ui, "(1/1)" + result.info, Constants.TIPS_TYPE_ERROR)
|
||||
@ -719,15 +724,30 @@ class MainWindow_approximately_align(QMainWindow):
|
||||
def __EstimateFrequencySelect__(self):
|
||||
PublicFunc.__disableAllButton__(self, ButtonState)
|
||||
if self.ui.radioButton_freqTHO.isChecked():
|
||||
frequency = Config["estimate"]["tho_frequency"]
|
||||
Config["estimate_freq"] = frequency
|
||||
frequency_ratio = Config["estimate"]["tho_frequency"]
|
||||
Config["estimate_freq"] = frequency_ratio
|
||||
Config["estimate_slope"] = Config["estimate"]["tho_slope"]
|
||||
Config["estimate_intercept"] = Config["estimate"]["tho_intercept"]
|
||||
elif self.ui.radioButton_freqABD.isChecked():
|
||||
frequency = Config["estimate"]["abd_frequency"]
|
||||
Config["estimate_freq"] = frequency
|
||||
frequency_ratio = Config["estimate"]["abd_frequency"]
|
||||
Config["estimate_freq"] = frequency_ratio
|
||||
Config["estimate_slope"] = Config["estimate"]["abd_slope"]
|
||||
Config["estimate_intercept"] = Config["estimate"]["abd_intercept"]
|
||||
elif self.ui.radioButton_customFreq.isChecked():
|
||||
# self.ui.lineEdit_customFreq.text()
|
||||
try:
|
||||
float(self.ui.lineEdit_customFreq.text())
|
||||
except ValueError:
|
||||
PublicFunc.msgbox_output(self, "自定义频率输入不合法,请输入数字类型数据!", Constants.MSGBOX_TYPE_ERROR)
|
||||
PublicFunc.finish_operation(self, ButtonState)
|
||||
return
|
||||
|
||||
frequency_ratio = float(self.ui.lineEdit_customFreq.text()) / Config["InputConfig"]["orgBcgFreq"]
|
||||
Config["estimate_freq"] = frequency_ratio
|
||||
Config["estimate_slope"] = None
|
||||
Config["estimate_intercept"] = None
|
||||
|
||||
|
||||
else:
|
||||
return
|
||||
|
||||
@ -1046,12 +1066,12 @@ class MainWindow_approximately_align(QMainWindow):
|
||||
return Result().success(info=Constants.DRAW_FINISHED)
|
||||
|
||||
def DrawAlignScatter(self, epoch_min, epoch_max, tho_bias_list, abd_bias_list, tho_y, abd_y,
|
||||
tho_frequency, abd_frequency):
|
||||
tho_frequency_ratio, abd_frequency_ratio):
|
||||
try:
|
||||
ax1 = self.fig.add_subplot(211)
|
||||
ax1.scatter(linspace(epoch_min, epoch_max, len(tho_bias_list)), tho_bias_list, alpha=0.2)
|
||||
ax1.plot(linspace(epoch_min, epoch_max, len(tho_bias_list)), tho_y, color='orange',
|
||||
label=f"THO Frequency: {tho_frequency} Hz")
|
||||
label=f"THO Frequency_ratio: {tho_frequency_ratio}")
|
||||
ax1.axhline(y=0, color='red', linestyle='--', alpha=0.3)
|
||||
ax1.set_xlabel("Epoch")
|
||||
ax1.set_ylabel("Tho Bias / s")
|
||||
@ -1061,7 +1081,7 @@ class MainWindow_approximately_align(QMainWindow):
|
||||
ax2 = self.fig.add_subplot(212)
|
||||
ax2.scatter(linspace(epoch_min, epoch_max, len(abd_bias_list)), abd_bias_list, alpha=0.2)
|
||||
ax2.plot(linspace(epoch_min, epoch_max, len(abd_bias_list)), abd_y, color='orange',
|
||||
label=f"ABD Frequency: {abd_frequency} Hz")
|
||||
label=f"ABD Frequency_ratio: {abd_frequency_ratio}")
|
||||
ax2.axhline(y=0, color='red', linestyle='--', alpha=0.3)
|
||||
ax2.set_xlabel("Epoch")
|
||||
ax2.set_ylabel("Abd Bias / s")
|
||||
@ -1260,12 +1280,12 @@ class Data:
|
||||
if Config["PSGConfig"]["PSGDelBase"]:
|
||||
# 减去四秒钟平均滤波
|
||||
self.processed_Tho = self.processed_Tho - convolve(
|
||||
self.processed_Tho, ones(int(4 * temp_frequency)) / int(4 * temp_frequency), mode='same')
|
||||
self.processed_Tho, ones(int(10 * temp_frequency)) / int(10 * temp_frequency), mode='same')
|
||||
self.processed_Abd = self.processed_Abd - convolve(
|
||||
self.processed_Abd, ones(int(4 * temp_frequency)) / int(4 * temp_frequency), mode='same')
|
||||
self.processed_Abd, ones(int(10 * temp_frequency)) / int(10 * temp_frequency), mode='same')
|
||||
if Config["orgBcgConfig"]["orgBcgDelBase"]:
|
||||
self.processed_orgBcg = self.processed_orgBcg - convolve(
|
||||
self.processed_orgBcg, ones(int(4 * temp_frequency)) / int(4 * temp_frequency), mode='same')
|
||||
self.processed_orgBcg, ones(int(10 * temp_frequency)) / int(10 * temp_frequency), mode='same')
|
||||
except Exception as e:
|
||||
return Result().failure(
|
||||
info=Constants.APPROXIMATELY_DELETE_BASE_FAILURE + Constants.FAILURE_REASON[
|
||||
@ -1306,9 +1326,16 @@ class Data:
|
||||
try:
|
||||
# 用[::]完成
|
||||
temp_frequency = Config["TempFrequency"]
|
||||
self.processed_downsample_Tho = self.processed_Tho[::int(temp_frequency / Config["ApplyFrequency"])]
|
||||
self.processed_downsample_Abd = self.processed_Abd[::int(temp_frequency / Config["ApplyFrequency"])]
|
||||
self.processed_downsample_orgBcg = self.processed_orgBcg[::int(temp_frequency / Config["ApplyFrequency"])]
|
||||
# self.processed_downsample_Tho = self.processed_Tho[::int(temp_frequency / Config["ApplyFrequency"])]
|
||||
# self.processed_downsample_Abd = self.processed_Abd[::int(temp_frequency / Config["ApplyFrequency"])]
|
||||
# self.processed_downsample_orgBcg = self.processed_orgBcg[::int(temp_frequency / Config["ApplyFrequency"])]
|
||||
# 用resample完成
|
||||
self.processed_downsample_Tho = resample(
|
||||
self.processed_Tho, int(Config["PSG_seconds"] * Config["ApplyFrequency"]))
|
||||
self.processed_downsample_Abd = resample(
|
||||
self.processed_Abd, int(Config["PSG_seconds"] * Config["ApplyFrequency"]))
|
||||
self.processed_downsample_orgBcg = resample(
|
||||
self.processed_orgBcg, int(Config["orgBcg_seconds"] * Config["ApplyFrequency"]))
|
||||
|
||||
|
||||
except Exception as e:
|
||||
@ -1333,10 +1360,10 @@ class Data:
|
||||
v = self.processed_downsample_orgBcg[
|
||||
Config["orgBcgConfig"]["PreCut"]:len(self.processed_downsample_orgBcg) - Config["orgBcgConfig"][
|
||||
"PostCut"]].copy()
|
||||
a *= MULTIPLE_FACTOER
|
||||
v *= MULTIPLE_FACTOER
|
||||
a = a.astype(int64)
|
||||
v = v.astype(int64)
|
||||
# a *= MULTIPLE_FACTOER
|
||||
# v *= MULTIPLE_FACTOER
|
||||
# a = a.astype(int64)
|
||||
# v = v.astype(int64)
|
||||
tho_relate = correlate(a, v, mode='full')
|
||||
tho_relate = tho_relate / (MULTIPLE_FACTOER ** 2)
|
||||
tho_relate2 = - tho_relate
|
||||
@ -1358,12 +1385,12 @@ class Data:
|
||||
v = self.processed_downsample_orgBcg[
|
||||
Config["orgBcgConfig"]["PreCut"]:len(self.processed_downsample_orgBcg) - Config["orgBcgConfig"][
|
||||
"PostCut"]].copy()
|
||||
a *= 100
|
||||
v *= 100
|
||||
a = a.astype(int64)
|
||||
v = v.astype(int64)
|
||||
# a *= 100
|
||||
# v *= 100
|
||||
# a = a.astype(int64)
|
||||
# v = v.astype(int64)
|
||||
abd_relate = correlate(a, v, mode='full')
|
||||
abd_relate = abd_relate / 10000
|
||||
# abd_relate = abd_relate / 10000
|
||||
abd_relate2 = - abd_relate
|
||||
|
||||
result = {"abd_relate": abd_relate, "abd_relate2": abd_relate2}
|
||||
@ -1397,7 +1424,7 @@ class Data:
|
||||
# 获取epoch
|
||||
try:
|
||||
epoch_second = Params.APPROXIMATELY_ALIGN_CONFIG_NEW_CONTENT["Second_PerEpoch"]
|
||||
epoch_min = max(0, Config["pos"] // epoch_second // Config["ApplyFrequency"] + 1)
|
||||
epoch_min = max(0, Config["pos"] // (epoch_second * Config["ApplyFrequency"])) + 1
|
||||
epoch_max = min(len(self.processed_downsample_Tho) // epoch_second // Config["ApplyFrequency"] - 1,
|
||||
(len(self.processed_downsample_orgBcg) + Config["pos"]) // epoch_second // Config[
|
||||
"ApplyFrequency"] - 1)
|
||||
@ -1476,16 +1503,17 @@ class Data:
|
||||
if slope != 0:
|
||||
drift_rate = slope / epoch_second
|
||||
# frequency = temp_freq * (1 - drift_rate)
|
||||
frequency = 1 - drift_rate
|
||||
# frequency_ratio = 1 - drift_rate
|
||||
frequency_ratio = 1 / (1 + drift_rate)
|
||||
else:
|
||||
# frequency = float(temp_freq)
|
||||
frequency = 1
|
||||
frequency_ratio = 1
|
||||
|
||||
theilsen_y = theilsen.predict(X)
|
||||
|
||||
return Result().success(info=Constants.APPROXIMATELY_ESTIMATE_FREQUENCY_FINISHED,
|
||||
data={"estimate_y": theilsen_y,
|
||||
"frequency": frequency,
|
||||
"frequency": frequency_ratio,
|
||||
"slope": slope,
|
||||
"intercept": theilsen.intercept_},
|
||||
)
|
||||
|
||||
@ -420,7 +420,7 @@ class Data:
|
||||
self.alignInfo = read_csv(Path(Config["Path"]["InputAlignInfo"]),
|
||||
encoding=Params.UTF8_ENCODING,
|
||||
header=None).to_numpy().reshape(-1)
|
||||
cleaned_str = re.sub(r'np\.(int64|float64|float32|int32)\((.*?)\)', r'\2', self.alignInfo[0])
|
||||
cleaned_str = re.sub(r'np\.\w+\(\s*([^)]+?)\s*\)', r'\1', self.alignInfo[0])
|
||||
self.alignInfo = literal_eval(cleaned_str)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@ -183,7 +183,7 @@ class MainWindow(QMainWindow, Ui_Signal_Label):
|
||||
self.check_save_path_and_mkdir(root_path, sampID)
|
||||
|
||||
def __slot_btn_preprocess__(self):
|
||||
self.preprocess = MainWindow_preprocess(Config["Plot_Mode"])
|
||||
self.preprocess = MainWindow_preprocess()
|
||||
|
||||
sender = self.sender()
|
||||
root_path = self.ui.plainTextEdit_root_path.toPlainText()
|
||||
|
||||
@ -7,12 +7,9 @@ from PySide6.QtWidgets import QMessageBox, QMainWindow, QApplication
|
||||
from matplotlib import gridspec
|
||||
from matplotlib.backends.backend_qt import NavigationToolbar2QT
|
||||
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
|
||||
from numpy import column_stack, arange
|
||||
from overrides import overrides
|
||||
from pandas import read_csv, DataFrame
|
||||
from scipy.signal import resample
|
||||
from vispy import scene
|
||||
from vispy.scene import visuals
|
||||
from yaml import dump, load, FullLoader
|
||||
|
||||
from func.utils.ConfigParams import Filename, Params
|
||||
@ -20,7 +17,6 @@ from func.utils.PublicFunc import PublicFunc
|
||||
from func.utils.Constants import Constants
|
||||
from func.Filters.Preprocessing import Butterworth_for_BCG_PreProcess, Butterworth_for_ECG_PreProcess
|
||||
from func.utils.Result import Result
|
||||
from func.utils.FloatingImagePanel import add_floating_image_panel
|
||||
|
||||
from ui.MainWindow.MainWindow_preprocess import Ui_MainWindow_preprocess
|
||||
from ui.setting.preprocess_input_setting import Ui_MainWindow_preprocess_input_setting
|
||||
@ -171,7 +167,7 @@ class SettingWindow(QMainWindow):
|
||||
|
||||
class MainWindow_preprocess(QMainWindow):
|
||||
|
||||
def __init__(self, plot_mode):
|
||||
def __init__(self):
|
||||
super(MainWindow_preprocess, self).__init__()
|
||||
self.ui = Ui_MainWindow_preprocess()
|
||||
self.ui.setupUi(self)
|
||||
@ -188,30 +184,12 @@ class MainWindow_preprocess(QMainWindow):
|
||||
self.progressbar = None
|
||||
PublicFunc.add_progressbar(self)
|
||||
|
||||
self.image_overlay = add_floating_image_panel(
|
||||
self.centralWidget(),
|
||||
title="参考图例",
|
||||
img_path=r"image\legend_preprocess.png"
|
||||
)
|
||||
|
||||
self.plot_mode = plot_mode
|
||||
if self.plot_mode == "matplotlib":
|
||||
#初始化画框
|
||||
self.fig = None
|
||||
self.canvas = None
|
||||
self.figToolbar = None
|
||||
self.gs = None
|
||||
self.ax0 = None
|
||||
elif self.plot_mode == "vispy":
|
||||
# 初始化画框
|
||||
self.canvas = None
|
||||
self.view = None
|
||||
self.y_axis = None
|
||||
self.x_axis = None
|
||||
self.view = None
|
||||
self.grid_lines = None
|
||||
self.line_original = None
|
||||
self.line_processed = None
|
||||
#初始化画框
|
||||
self.fig = None
|
||||
self.canvas = None
|
||||
self.figToolbar = None
|
||||
self.gs = None
|
||||
self.ax0 = None
|
||||
|
||||
self.ui.textBrowser_info.setStyleSheet("QTextBrowser { background-color: rgb(255, 255, 200); }")
|
||||
PublicFunc.__styleAllButton__(self, ButtonState)
|
||||
@ -228,50 +206,20 @@ class MainWindow_preprocess(QMainWindow):
|
||||
|
||||
self.setting = SettingWindow(mode, root_path, sampID)
|
||||
|
||||
if self.plot_mode == "matplotlib":
|
||||
# 初始化画框
|
||||
self.fig = plt.figure(figsize=(12, 9), dpi=100)
|
||||
self.canvas = FigureCanvasQTAgg(self.fig)
|
||||
self.figToolbar = NavigationToolbar2QT(self.canvas)
|
||||
for action in self.figToolbar.actions():
|
||||
if action.text() == "Subplots" or action.text() == "Customize":
|
||||
self.figToolbar.removeAction(action)
|
||||
self.ui.verticalLayout_canvas.addWidget(self.canvas)
|
||||
self.ui.verticalLayout_canvas.addWidget(self.figToolbar)
|
||||
self.gs = gridspec.GridSpec(1, 1, height_ratios=[1])
|
||||
self.fig.subplots_adjust(top=0.98, bottom=0.05, right=0.98, left=0.1, hspace=0, wspace=0)
|
||||
self.ax0 = self.fig.add_subplot(self.gs[0])
|
||||
self.ax0.grid(True)
|
||||
self.ax0.xaxis.set_major_formatter(Params.FORMATTER)
|
||||
elif self.plot_mode == "vispy":
|
||||
# 初始化画框
|
||||
self.canvas = scene.SceneCanvas(keys='interactive', show=True, bgcolor='white')
|
||||
self.ui.verticalLayout_canvas.addWidget(self.canvas.native)
|
||||
self.main_grid = self.canvas.central_widget.add_grid(spacing=0)
|
||||
self.y_axis = scene.AxisWidget(orientation='left',
|
||||
axis_color='black',
|
||||
tick_color='black',
|
||||
text_color='black',
|
||||
axis_font_size=4,
|
||||
tick_font_size=4,
|
||||
tick_label_margin=20)
|
||||
self.y_axis.width_max = 80
|
||||
self.x_axis = scene.AxisWidget(orientation='bottom',
|
||||
axis_color='black',
|
||||
tick_color='black',
|
||||
text_color='black',
|
||||
axis_font_size=4,
|
||||
tick_font_size=4,
|
||||
tick_label_margin=20)
|
||||
self.x_axis.height_max = 40
|
||||
self.view = self.main_grid.add_view(row=0, col=1, camera='panzoom')
|
||||
self.main_grid.add_widget(self.y_axis, row=0, col=0)
|
||||
self.main_grid.add_widget(self.x_axis, row=1, col=1)
|
||||
self.x_axis.link_view(self.view)
|
||||
self.y_axis.link_view(self.view)
|
||||
self.grid_lines = visuals.GridLines(color=(0.3, 0.3, 0.3, 0.5), parent=self.view.scene)
|
||||
self.grid_lines.set_gl_state(line_width=1)
|
||||
self.grid_lines.order = 100
|
||||
# 初始化画框
|
||||
self.fig = plt.figure(figsize=(12, 9), dpi=100)
|
||||
self.canvas = FigureCanvasQTAgg(self.fig)
|
||||
self.figToolbar = NavigationToolbar2QT(self.canvas)
|
||||
for action in self.figToolbar.actions():
|
||||
if action.text() == "Subplots" or action.text() == "Customize":
|
||||
self.figToolbar.removeAction(action)
|
||||
self.ui.verticalLayout_canvas.addWidget(self.canvas)
|
||||
self.ui.verticalLayout_canvas.addWidget(self.figToolbar)
|
||||
self.gs = gridspec.GridSpec(1, 1, height_ratios=[1])
|
||||
self.fig.subplots_adjust(top=0.98, bottom=0.05, right=0.98, left=0.1, hspace=0, wspace=0)
|
||||
self.ax0 = self.fig.add_subplot(self.gs[0])
|
||||
self.ax0.grid(True)
|
||||
self.ax0.xaxis.set_major_formatter(Params.FORMATTER)
|
||||
|
||||
PublicFunc.__resetAllButton__(self, ButtonState)
|
||||
|
||||
@ -305,17 +253,14 @@ class MainWindow_preprocess(QMainWindow):
|
||||
QApplication.processEvents()
|
||||
|
||||
# 清空画框
|
||||
if self.plot_mode == "matplotlib":
|
||||
if self.ax0 is not None:
|
||||
self.ax0.clear()
|
||||
self.fig.clf()
|
||||
plt.close(self.fig)
|
||||
elif self.plot_mode == "vispy":
|
||||
self.canvas.close()
|
||||
if self.ax0 is not None:
|
||||
self.ax0.clear()
|
||||
|
||||
# 释放资源
|
||||
self.setting.close()
|
||||
del self.data
|
||||
self.fig.clf()
|
||||
plt.close(self.fig)
|
||||
self.deleteLater()
|
||||
collect()
|
||||
self.canvas = None
|
||||
@ -328,53 +273,23 @@ class MainWindow_preprocess(QMainWindow):
|
||||
|
||||
def __plot__(self):
|
||||
# 清空画框
|
||||
if self.plot_mode == "matplotlib":
|
||||
self.reset_axes()
|
||||
sender = self.sender()
|
||||
if sender == self.ui.pushButton_view:
|
||||
self.ax0.plot(self.data.raw_data,
|
||||
color=Constants.PLOT_COLOR_RED,
|
||||
label=Constants.PREPROCESS_PLOT_LABEL_ORIGINAL_DATA)
|
||||
self.ax0.plot(self.data.processed_data + Constants.PREPROCESS_OUTPUT_INPUT_AMP_OFFSET,
|
||||
color=Constants.PLOT_COLOR_BLUE,
|
||||
label=Constants.PREPROCESS_PLOT_LABEL_PROCESSED_DATA)
|
||||
self.ax0.legend(loc=Constants.PLOT_UPPER_RIGHT)
|
||||
self.canvas.draw()
|
||||
return Result().success(info=Constants.DRAW_FINISHED)
|
||||
else:
|
||||
self.canvas.draw()
|
||||
return Result().failure(info=Constants.DRAW_FAILURE)
|
||||
elif self.plot_mode == "vispy":
|
||||
sender = self.sender()
|
||||
if self.line_original is not None:
|
||||
self.line_original.parent = None
|
||||
if self.line_processed is not None:
|
||||
self.line_processed.parent = None
|
||||
if sender == self.ui.pushButton_view:
|
||||
t = arange(len(self.data.raw_data))
|
||||
raw_data_2d = column_stack((t, self.data.raw_data))
|
||||
self.line_original = visuals.Line(raw_data_2d,
|
||||
color=Constants.PLOT_COLOR_RED,
|
||||
parent=self.view.scene,
|
||||
antialias=False,
|
||||
width=2,
|
||||
method='gl')
|
||||
processed_y = self.data.processed_data + Constants.PREPROCESS_OUTPUT_INPUT_AMP_OFFSET
|
||||
processed_data_2d = column_stack((t, processed_y))
|
||||
self.line_processed = visuals.Line(processed_data_2d,
|
||||
color=Constants.PLOT_COLOR_BLUE,
|
||||
parent=self.view.scene,
|
||||
antialias=False,
|
||||
width=2,
|
||||
method='gl')
|
||||
self.line_original.order = 0
|
||||
self.line_processed.order = -1
|
||||
self.view.camera.set_range()
|
||||
self.canvas.update()
|
||||
return Result().success(info=Constants.DRAW_FINISHED)
|
||||
else:
|
||||
self.reset_axes()
|
||||
|
||||
return Result().failure(info=Constants.DRAW_FAILURE)
|
||||
sender = self.sender()
|
||||
|
||||
if sender == self.ui.pushButton_view:
|
||||
self.ax0.plot(self.data.raw_data,
|
||||
color=Constants.PLOT_COLOR_RED,
|
||||
label=Constants.PREPROCESS_PLOT_LABEL_ORIGINAL_DATA)
|
||||
self.ax0.plot(self.data.processed_data + Constants.PREPROCESS_OUTPUT_INPUT_AMP_OFFSET,
|
||||
color=Constants.PLOT_COLOR_BLUE,
|
||||
label=Constants.PREPROCESS_PLOT_LABEL_PROCESSED_DATA)
|
||||
self.ax0.legend(loc=Constants.PLOT_UPPER_RIGHT)
|
||||
self.canvas.draw()
|
||||
return Result().success(info=Constants.DRAW_FINISHED)
|
||||
else:
|
||||
self.canvas.draw()
|
||||
return Result().success(info=Constants.DRAW_FAILURE)
|
||||
|
||||
def __update_config__(self):
|
||||
if self.mode == "BCG":
|
||||
@ -391,12 +306,10 @@ class MainWindow_preprocess(QMainWindow):
|
||||
def __slot_btn_input__(self):
|
||||
PublicFunc.__disableAllButton__(self, ButtonState)
|
||||
|
||||
if self.plot_mode == "matplotlib":
|
||||
# 清空画框
|
||||
self.reset_axes()
|
||||
self.canvas.draw()
|
||||
elif self.plot_mode == "vispy":
|
||||
self.canvas.update()
|
||||
# 清空画框
|
||||
self.reset_axes()
|
||||
|
||||
self.canvas.draw()
|
||||
|
||||
self.data = Data()
|
||||
|
||||
@ -499,18 +412,6 @@ class MainWindow_preprocess(QMainWindow):
|
||||
self.ax0.grid(True)
|
||||
self.ax0.xaxis.set_major_formatter(Params.FORMATTER)
|
||||
|
||||
def update_overlay_position(self):
|
||||
"""手动定位函数"""
|
||||
margin = 20
|
||||
x = self.width() - self.image_overlay.width() - margin
|
||||
y = margin
|
||||
self.image_overlay.move(x, y)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
"""确保窗口变大小时,悬浮窗依然在右上角"""
|
||||
super().resizeEvent(event)
|
||||
self.update_overlay_position()
|
||||
|
||||
|
||||
class Data:
|
||||
|
||||
|
||||
@ -76,8 +76,7 @@ class Params:
|
||||
PUBLIC_CONFIG_NEW_CONTENT = {
|
||||
"Path": {
|
||||
"Root": ""
|
||||
},
|
||||
"Plot_Mode": "matplotlib"
|
||||
}
|
||||
}
|
||||
UTF8_ENCODING: str = "utf-8"
|
||||
GBK_ENCODING: str = "gbk"
|
||||
|
||||
@ -1,95 +0,0 @@
|
||||
from PySide6 import QtWidgets, QtCore, QtGui
|
||||
|
||||
|
||||
class FloatingImagePanel(QtWidgets.QWidget):
|
||||
def __init__(self, parent, title="原始比例预览", image_path=None):
|
||||
super().__init__(parent)
|
||||
self.is_expanded = True
|
||||
|
||||
# 1. 样式配置 (保持深色风格,方便看清信号)
|
||||
self.setAttribute(QtCore.Qt.WA_StyledBackground, True)
|
||||
self.setStyleSheet("""
|
||||
FloatingImagePanel {
|
||||
background-color: rgba(30, 30, 30, 220);
|
||||
border: 2px solid #444;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QLabel#title_label { color: #ffffff; font-weight: bold; font-family: 'Consolas'; }
|
||||
QPushButton#toggle_btn { background: #444; color: white; border: none; }
|
||||
""")
|
||||
|
||||
# 2. 布局初始化
|
||||
self.main_layout = QtWidgets.QVBoxLayout(self)
|
||||
self.main_layout.setContentsMargins(4, 4, 4, 4)
|
||||
self.main_layout.setSpacing(2)
|
||||
|
||||
# --- 标题栏 ---
|
||||
self.header = QtWidgets.QWidget()
|
||||
header_layout = QtWidgets.QHBoxLayout(self.header)
|
||||
header_layout.setContentsMargins(2, 2, 2, 2)
|
||||
|
||||
self.title_label = QtWidgets.QLabel(title)
|
||||
self.title_label.setObjectName("title_label")
|
||||
self.toggle_btn = QtWidgets.QPushButton("▲")
|
||||
self.toggle_btn.setFixedSize(22, 22)
|
||||
|
||||
header_layout.addWidget(self.title_label)
|
||||
header_layout.addStretch()
|
||||
header_layout.addWidget(self.toggle_btn)
|
||||
self.main_layout.addWidget(self.header)
|
||||
|
||||
# --- 图片展示区 ---
|
||||
self.image_label = QtWidgets.QLabel()
|
||||
# 关键点 1:关闭自动填充,确保我们手动控制尺寸
|
||||
self.image_label.setScaledContents(False)
|
||||
# 关键点 2:设置对齐方式为中心
|
||||
self.image_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.main_layout.addWidget(self.image_label)
|
||||
|
||||
# 3. 信号绑定
|
||||
self.toggle_btn.clicked.connect(self.toggle_content)
|
||||
|
||||
# 4. 加载图片
|
||||
if image_path:
|
||||
self.update_image(image_path)
|
||||
|
||||
def update_image(self, path):
|
||||
"""完全根据图片的物理尺寸调整窗口"""
|
||||
pixmap = QtGui.QPixmap(path)
|
||||
if not pixmap.isNull():
|
||||
# 拿到图片的原始宽高
|
||||
w = pixmap.width()
|
||||
h = pixmap.height()
|
||||
|
||||
# 强制 Label 等于图片大小
|
||||
self.image_label.setFixedSize(w, h)
|
||||
self.image_label.setPixmap(pixmap)
|
||||
|
||||
# 让整个窗口重新计算布局,贴合 Label
|
||||
self.adjustSize()
|
||||
|
||||
# 保持展开状态
|
||||
self.set_expanded(True)
|
||||
|
||||
def set_expanded(self, expanded: bool):
|
||||
self.is_expanded = expanded
|
||||
self.image_label.setVisible(expanded)
|
||||
self.toggle_btn.setText("▲" if expanded else "▼")
|
||||
|
||||
# 关键点 3:重置窗口大小
|
||||
# 展开时它会变大包住图片,收起时它会缩成标题栏大小
|
||||
self.adjustSize()
|
||||
|
||||
# 通知父窗口刷新位置,防止因为变大而超出右边界
|
||||
if hasattr(self.parent(), 'update_overlay_position'):
|
||||
self.parent().update_overlay_position()
|
||||
|
||||
def toggle_content(self):
|
||||
self.set_expanded(not self.is_expanded)
|
||||
|
||||
|
||||
# --- 封装的调用函数 ---
|
||||
def add_floating_image_panel(parent_widget, title="图片原尺寸", img_path=None):
|
||||
panel = FloatingImagePanel(parent_widget, title, img_path)
|
||||
panel.raise_()
|
||||
return panel
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.4 KiB |
@ -16,10 +16,10 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||
from PySide6.QtWidgets import (QAbstractSpinBox, QApplication, QCheckBox, QGridLayout,
|
||||
QGroupBox, QHBoxLayout, QLabel, QMainWindow,
|
||||
QPushButton, QRadioButton, QSizePolicy, QSpacerItem,
|
||||
QSpinBox, QStatusBar, QTextBrowser, QVBoxLayout,
|
||||
QWidget)
|
||||
QGroupBox, QHBoxLayout, QLabel, QLineEdit,
|
||||
QMainWindow, QPushButton, QRadioButton, QSizePolicy,
|
||||
QSpacerItem, QSpinBox, QStatusBar, QTextBrowser,
|
||||
QVBoxLayout, QWidget)
|
||||
|
||||
class Ui_MainWindow_approximately_align(object):
|
||||
def setupUi(self, MainWindow_approximately_align):
|
||||
@ -400,26 +400,43 @@ class Ui_MainWindow_approximately_align(object):
|
||||
self.groupBox_2.setMinimumSize(QSize(0, 0))
|
||||
self.layoutWidget = QWidget(self.groupBox_2)
|
||||
self.layoutWidget.setObjectName(u"layoutWidget")
|
||||
self.layoutWidget.setGeometry(QRect(11, 18, 401, 41))
|
||||
self.layoutWidget.setGeometry(QRect(11, 18, 411, 60))
|
||||
self.verticalLayout_3 = QVBoxLayout(self.layoutWidget)
|
||||
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
|
||||
self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
|
||||
self.horizontalLayout_3 = QHBoxLayout()
|
||||
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
|
||||
self.radioButton_freqTHO = QRadioButton(self.layoutWidget)
|
||||
self.radioButton_freqTHO.setObjectName(u"radioButton_freqTHO")
|
||||
self.radioButton_freqTHO.setFont(font1)
|
||||
self.gridLayout_5 = QGridLayout()
|
||||
self.gridLayout_5.setObjectName(u"gridLayout_5")
|
||||
self.radioButton_customFreq = QRadioButton(self.layoutWidget)
|
||||
self.radioButton_customFreq.setObjectName(u"radioButton_customFreq")
|
||||
self.radioButton_customFreq.setFont(font1)
|
||||
|
||||
self.horizontalLayout_3.addWidget(self.radioButton_freqTHO)
|
||||
self.gridLayout_5.addWidget(self.radioButton_customFreq, 1, 0, 1, 1)
|
||||
|
||||
self.radioButton_freqABD = QRadioButton(self.layoutWidget)
|
||||
self.radioButton_freqABD.setObjectName(u"radioButton_freqABD")
|
||||
self.radioButton_freqABD.setFont(font1)
|
||||
|
||||
self.horizontalLayout_3.addWidget(self.radioButton_freqABD)
|
||||
self.gridLayout_5.addWidget(self.radioButton_freqABD, 0, 1, 1, 1)
|
||||
|
||||
self.radioButton_freqTHO = QRadioButton(self.layoutWidget)
|
||||
self.radioButton_freqTHO.setObjectName(u"radioButton_freqTHO")
|
||||
self.radioButton_freqTHO.setFont(font1)
|
||||
|
||||
self.gridLayout_5.addWidget(self.radioButton_freqTHO, 0, 0, 1, 1)
|
||||
|
||||
self.lineEdit_customFreq = QLineEdit(self.layoutWidget)
|
||||
self.lineEdit_customFreq.setObjectName(u"lineEdit_customFreq")
|
||||
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||
sizePolicy1.setHorizontalStretch(0)
|
||||
sizePolicy1.setVerticalStretch(0)
|
||||
sizePolicy1.setHeightForWidth(self.lineEdit_customFreq.sizePolicy().hasHeightForWidth())
|
||||
self.lineEdit_customFreq.setSizePolicy(sizePolicy1)
|
||||
self.lineEdit_customFreq.setFont(font1)
|
||||
|
||||
self.gridLayout_5.addWidget(self.lineEdit_customFreq, 1, 1, 1, 1)
|
||||
|
||||
|
||||
self.verticalLayout_3.addLayout(self.horizontalLayout_3)
|
||||
self.verticalLayout_3.addLayout(self.gridLayout_5)
|
||||
|
||||
|
||||
self.verticalLayout.addWidget(self.groupBox_2)
|
||||
@ -530,7 +547,7 @@ class Ui_MainWindow_approximately_align(object):
|
||||
self.verticalLayout.setStretch(2, 3)
|
||||
self.verticalLayout.setStretch(3, 4)
|
||||
self.verticalLayout.setStretch(4, 3)
|
||||
self.verticalLayout.setStretch(5, 2)
|
||||
self.verticalLayout.setStretch(5, 3)
|
||||
self.verticalLayout.setStretch(6, 4)
|
||||
self.verticalLayout.setStretch(7, 1)
|
||||
self.verticalLayout.setStretch(8, 6)
|
||||
@ -607,8 +624,9 @@ class Ui_MainWindow_approximately_align(object):
|
||||
self.pushButton_GetPos.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u8ba1\u7b97\u5bf9\u9f50", None))
|
||||
self.radioButton_NABD.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u5907\u90094", None))
|
||||
self.groupBox_2.setTitle(QCoreApplication.translate("MainWindow_approximately_align", u"\u91c7\u6837\u7387\u4f30\u8ba1", None))
|
||||
self.radioButton_freqTHO.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u5907\u90091", None))
|
||||
self.radioButton_customFreq.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u81ea\u5b9a\u4e49", None))
|
||||
self.radioButton_freqABD.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u5907\u90092", None))
|
||||
self.radioButton_freqTHO.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u5907\u90091", None))
|
||||
self.groupBox_view_partially.setTitle(QCoreApplication.translate("MainWindow_approximately_align", u"\u5c40\u90e8\u89c2\u6d4b", None))
|
||||
self.label_9.setText(QCoreApplication.translate("MainWindow_approximately_align", u"Epoch\uff1a", None))
|
||||
self.pushButton_JUMP.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u8df3\u8f6c", None))
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
<property name="title">
|
||||
<string>数据粗同步</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,1,3,4,3,2,4,1,6">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,1,3,4,3,3,4,1,6">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
@ -728,14 +728,38 @@
|
||||
<rect>
|
||||
<x>11</x>
|
||||
<y>18</y>
|
||||
<width>401</width>
|
||||
<height>41</height>
|
||||
<width>411</width>
|
||||
<height>60</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="radioButton_customFreq">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>自定义</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QRadioButton" name="radioButton_freqABD">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>备选2</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QRadioButton" name="radioButton_freqTHO">
|
||||
<property name="font">
|
||||
<font>
|
||||
@ -747,16 +771,19 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButton_freqABD">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_customFreq">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>备选2</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
Reference in New Issue
Block a user