diff --git a/func/Module_approximately_align.py b/func/Module_approximately_align.py index e585dd2..2636c13 100644 --- a/func/Module_approximately_align.py +++ b/func/Module_approximately_align.py @@ -12,7 +12,7 @@ from overrides import overrides from pandas import read_csv, DataFrame from scipy.signal import find_peaks, resample, butter, sosfiltfilt, correlate from yaml import dump, load, FullLoader - +from sklearn.linear_model import TheilSenRegressor from func.utils.PublicFunc import PublicFunc from func.utils.Constants import Constants, ConfigParams from func.utils.Result import Result @@ -44,7 +44,9 @@ ButtonState = { "radioButton_PABD": False, "radioButton_NTHO": False, "radioButton_NABD": False, - "radioButton_custom": False + "radioButton_custom": False, + "radioButton_freqTHO": False, + "radioButton_freqABD": False, }, "Current": { "pushButton_input_setting": True, @@ -65,7 +67,9 @@ ButtonState = { "radioButton_PABD": False, "radioButton_NTHO": False, "radioButton_NABD": False, - "radioButton_custom": False + "radioButton_custom": False, + "radioButton_freqTHO": False, + "radioButton_freqABD": False, } } @@ -259,6 +263,8 @@ class MainWindow_approximately_align(QMainWindow): self.ui.radioButton_PABD.clicked.connect(self.__enableAlign__) self.ui.radioButton_NABD.clicked.connect(self.__enableAlign__) self.ui.radioButton_custom.clicked.connect(self.__enableAlign__) + self.ui.radioButton_freqTHO.clicked.connect(self.__EstimateFrequencySelect__) + self.ui.radioButton_freqABD.clicked.connect(self.__EstimateFrequencySelect__) @overrides @@ -305,6 +311,11 @@ class MainWindow_approximately_align(QMainWindow): self.ui.radioButton_NABD.setText("备选4") self.ui.radioButton_NTHO.setText("备选2") self.ui.spinBox_SelectEpoch.setMinimum(0) + self.ui.radioButton_freqABD.setChecked(False) + self.ui.radioButton_freqTHO.setChecked(False) + self.ui.radioButton_freqTHO.setText("备选1") + self.ui.radioButton_freqABD.setText("备选2") + def __plot__(self, *args, **kwargs): sender = self.sender() @@ -328,7 +339,7 @@ class MainWindow_approximately_align(QMainWindow): elif sender == self.ui.radioButton_custom: result = self.DrawPicTryAlign() elif sender == self.ui.pushButton_ChangeView: - result = self.DrawAlignScatter() + result = self.DrawAlignScatter(*args, **kwargs) elif sender == self.ui.pushButton_JUMP: result = self.DrawPicByEpoch(*args, **kwargs) elif sender == self.ui.pushButton_EM1: @@ -570,7 +581,39 @@ class MainWindow_approximately_align(QMainWindow): # 绘图 PublicFunc.progressbar_update(self, 1, 1, Constants.DRAWING_DATA, 0) - result = self.__plot__() + response = self.data.get_corr_by_epoch() + epoch_min = response.data["epoch_min"] + epoch_max = response.data["epoch_max"] + tho_bias_list = response.data["tho_bias_list"] + abd_bias_list = response.data["abd_bias_list"] + + response = self.data.estimate_frequency(tho_bias_list) + tho_y = response.data["estimate_y"] + tho_frequency = 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_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) + Config["estimate"] = {} + Config["estimate"]["tho_frequency"] = tho_frequency + Config["estimate"]["tho_slope"] = tho_slope + Config["estimate"]["tho_intercept"] = tho_intercept + + Config["estimate"]["abd_frequency"] = abd_frequency + Config["estimate"]["abd_slope"] = abd_slope + Config["estimate"]["abd_intercept"] = abd_intercept + + self.ui.radioButton_freqTHO.setText(str(Config["estimate"]["tho_frequency"])) + self.ui.radioButton_freqABD.setText(str(Config["estimate"]["abd_frequency"])) + ButtonState["Current"]["radioButton_freqTHO"] = True + ButtonState["Current"]["radioButton_freqABD"] = True + + if not result.status: PublicFunc.text_output(self.ui, "(1/1)" + result.info, Constants.TIPS_TYPE_ERROR) PublicFunc.msgbox_output(self, result.info, Constants.MSGBOX_TYPE_ERROR) @@ -599,6 +642,25 @@ class MainWindow_approximately_align(QMainWindow): PublicFunc.finish_operation(self, ButtonState) + def __EstimateFrequencySelect__(self): + PublicFunc.__disableAllButton__(self, ButtonState) + if self.ui.radioButton_freqTHO.isChecked(): + frequency = Config["estimate"]["tho_frequency"] + Config["estimate_freq"] = frequency + 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 + Config["estimate_slope"] = Config["estimate"]["abd_slope"] + Config["estimate_intercept"] = Config["estimate"]["abd_intercept"] + else: + return + + ButtonState["Current"]["pushButton_save"] = True + PublicFunc.finish_operation(self, ButtonState) + + def __EpochChange__(self): # 获取当前值 value = self.ui.spinBox_SelectEpoch.value() @@ -682,7 +744,6 @@ class MainWindow_approximately_align(QMainWindow): ButtonState["Current"]["pushButton_EP1"] = True ButtonState["Current"]["pushButton_EP10"] = True ButtonState["Current"]["pushButton_EP100"] = True - ButtonState["Current"]["pushButton_save"] = True ButtonState["Current"]["pushButton_ChangeView"] = True PublicFunc.finish_operation(self, ButtonState) @@ -886,27 +947,26 @@ class MainWindow_approximately_align(QMainWindow): # 返回图片以便存到QPixImage return Result().success(info=Constants.DRAW_FINISHED) - def DrawAlignScatter(self): + def DrawAlignScatter(self, epoch_min, epoch_max, tho_bias_list, abd_bias_list, tho_y, abd_y, + tho_frequency, abd_frequency): try: - response = self.data.get_corr_by_epoch() - epoch_min = response.data["epoch_min"] - epoch_max = response.data["epoch_max"] - tho_bias_list = response.data["tho_bias_list"] - abd_bias_list = response.data["abd_bias_list"] - 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") ax1.axhline(y=0, color='red', linestyle='--', alpha=0.3) ax1.set_xlabel("Epoch") ax1.set_ylabel("Tho Bias / s") ax1.set_title("THO") + ax1.legend() 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") ax2.axhline(y=0, color='red', linestyle='--', alpha=0.3) ax2.set_xlabel("Epoch") ax2.set_ylabel("Abd Bias / s") ax2.set_title("ABD") + ax2.legend() self.fig.canvas.draw() return Result().success(info=Constants.DRAW_FINISHED) @@ -979,8 +1039,18 @@ class Data: try: pos = Config["pos"] ApplyFrequency = Config["ApplyFrequency"] + estimate_freq = Config["estimate_freq"] + estimate_slope = Config["estimate_slope"] + estimate_intercept = Config["estimate_intercept"] # 保存到csv中 - df = DataFrame({"pos": [pos], "epoch": [epoch], "ApplyFrequency": [ApplyFrequency]}) + df = DataFrame({"pos": [pos], + "epoch": [epoch], + "ApplyFrequency": [ApplyFrequency], + "estimate_freq": [estimate_freq], + "estimate_slope": [estimate_slope], + "estimate_intercept": [estimate_intercept] + }) + df.to_csv(Path(Config["Path"]["Save"]), mode="w", header=True, index=False) except Exception as e: return Result().failure(info=Constants.SAVE_FAILURE + Constants.FAILURE_REASON[ @@ -1221,6 +1291,7 @@ class Data: epoch_min = response.data["epoch_min"] epoch_max = response.data["epoch_max"] + epoch_second = ConfigParams.APPROXIMATELY_ALIGN_CONFIG_NEW_CONTENT["Second_PerEpoch"] temp_freq = ConfigParams.APPROXIMATELY_ALIGN_CONFIG_NEW_CONTENT["TempFrequency"] window_epoch = ConfigParams.APPROXIMATELY_ALIGN_CONFIG_NEW_CONTENT["CorrByEpoch"]["window_epoch"] @@ -1231,8 +1302,8 @@ class Data: pos = Config["pos"] * temp_freq // Config["ApplyFrequency"] for epoch in range(epoch_min, epoch_max): - SP = epoch * 30 * temp_freq - EP = (epoch + window_epoch) * 30 * temp_freq + SP = epoch * epoch_second * temp_freq + EP = (epoch + window_epoch) * epoch_second * temp_freq tho_seg = self.processed_Tho[SP:EP] abd_seg = self.processed_Abd[SP:EP] @@ -1260,3 +1331,32 @@ class Data: "Get_Corr_By_Epoch_Exception"] + "\n" + format_exc()) return Result().success(info=Constants.APPROXIMATELY_EPOCH_GET_FINISHED, data=result) + + @staticmethod + def estimate_frequency(bias_list): + try: + epoch_second = ConfigParams.APPROXIMATELY_ALIGN_CONFIG_NEW_CONTENT["Second_PerEpoch"] + temp_freq = ConfigParams.APPROXIMATELY_ALIGN_CONFIG_NEW_CONTENT["TempFrequency"] + + # 生成线性数据 + X = linspace(0, len(bias_list), len(bias_list)).reshape(-1, 1) + y = bias_list + + theilsen = TheilSenRegressor() + theilsen.fit(X, y) + slope = theilsen.coef_[0] + frequency = 1 - slope / epoch_second / temp_freq if slope != 0 else float('inf') + + theilsen_y = theilsen.predict(X) + + return Result().success(info=Constants.APPROXIMATELY_ESTIMATE_FREQUENCY_FINISHED, + data={"estimate_y": theilsen_y, + "frequency": frequency, + "slope": slope, + "intercept": theilsen.intercept_}, + ) + except Exception as e: + return Result().failure( + info=Constants.APPROXIMATELY_ESTIMATE_FREQUENCY_FAILURE + Constants.FAILURE_REASON[ + "Estimate_Frequency_Exception"] + "\n" + format_exc()) + diff --git a/func/utils/ConfigParams.py b/func/utils/ConfigParams.py index 254fa36..0b56844 100644 --- a/func/utils/ConfigParams.py +++ b/func/utils/ConfigParams.py @@ -85,6 +85,7 @@ class ConfigParams: "BandPassHigh": 0.7 }, "Multiple_Factor":100, + "Second_PerEpoch": 30, "CorrByEpoch": { "window_epoch": 6 diff --git a/func/utils/Constants.py b/func/utils/Constants.py index 717a91b..08b8006 100644 --- a/func/utils/Constants.py +++ b/func/utils/Constants.py @@ -182,6 +182,10 @@ class Constants: APPROXIMATELY_EPOCH_GET_FINISHED: str = "获取epoch完成" APPROXIMATELY_EPOCH_GET_FAILURE: str = "获取epoch失败" + APPROXIMATELY_ESTIMATE_FREQUENCY_CALCULATING: str = "正在估计采样率" + APPROXIMATELY_ESTIMATE_FREQUENCY_FINISHED: str = "估计采样率完成" + APPROXIMATELY_ESTIMATE_FREQUENCY_FAILURE: str = "估计采样率失败" + # 预处理 PREPROCESS_PLOT_LABEL_ORIGINAL_DATA: str = "Original_Data" PREPROCESS_PLOT_LABEL_PROCESSED_DATA: str = "Processed_Data" diff --git a/ui/MainWindow/MainWindow_approximately_align.py b/ui/MainWindow/MainWindow_approximately_align.py index c03e1c9..9368fe4 100644 --- a/ui/MainWindow/MainWindow_approximately_align.py +++ b/ui/MainWindow/MainWindow_approximately_align.py @@ -317,11 +317,12 @@ class Ui_MainWindow_approximately_align(object): self.groupBox_align_position.setObjectName(u"groupBox_align_position") self.gridLayout_2 = QGridLayout(self.groupBox_align_position) self.gridLayout_2.setObjectName(u"gridLayout_2") - self.radioButton_PABD = QRadioButton(self.groupBox_align_position) - self.radioButton_PABD.setObjectName(u"radioButton_PABD") - self.radioButton_PABD.setFont(font1) + self.pushButton_ChangeView = QPushButton(self.groupBox_align_position) + self.pushButton_ChangeView.setObjectName(u"pushButton_ChangeView") + self.pushButton_ChangeView.setMinimumSize(QSize(0, 30)) + self.pushButton_ChangeView.setFont(font1) - self.gridLayout_2.addWidget(self.radioButton_PABD, 1, 2, 1, 1) + self.gridLayout_2.addWidget(self.pushButton_ChangeView, 0, 2, 1, 1) self.radioButton_NTHO = QRadioButton(self.groupBox_align_position) self.radioButton_NTHO.setObjectName(u"radioButton_NTHO") @@ -329,12 +330,37 @@ class Ui_MainWindow_approximately_align(object): self.gridLayout_2.addWidget(self.radioButton_NTHO, 1, 1, 1, 1) + self.radioButton_PTHO = QRadioButton(self.groupBox_align_position) + self.radioButton_PTHO.setObjectName(u"radioButton_PTHO") + self.radioButton_PTHO.setFont(font1) + + self.gridLayout_2.addWidget(self.radioButton_PTHO, 1, 0, 1, 1) + self.radioButton_custom = QRadioButton(self.groupBox_align_position) self.radioButton_custom.setObjectName(u"radioButton_custom") self.radioButton_custom.setFont(font1) self.gridLayout_2.addWidget(self.radioButton_custom, 2, 1, 1, 1) + self.radioButton_PABD = QRadioButton(self.groupBox_align_position) + self.radioButton_PABD.setObjectName(u"radioButton_PABD") + self.radioButton_PABD.setFont(font1) + + self.gridLayout_2.addWidget(self.radioButton_PABD, 1, 2, 1, 1) + + self.pushButton_GetPos = QPushButton(self.groupBox_align_position) + self.pushButton_GetPos.setObjectName(u"pushButton_GetPos") + self.pushButton_GetPos.setMinimumSize(QSize(0, 30)) + self.pushButton_GetPos.setFont(font1) + + self.gridLayout_2.addWidget(self.pushButton_GetPos, 0, 0, 1, 1) + + self.radioButton_NABD = QRadioButton(self.groupBox_align_position) + self.radioButton_NABD.setObjectName(u"radioButton_NABD") + self.radioButton_NABD.setFont(font1) + + self.gridLayout_2.addWidget(self.radioButton_NABD, 2, 0, 1, 1) + self.spinBox_custom = QSpinBox(self.groupBox_align_position) self.spinBox_custom.setObjectName(u"spinBox_custom") self.spinBox_custom.setMinimumSize(QSize(90, 0)) @@ -346,35 +372,38 @@ class Ui_MainWindow_approximately_align(object): self.gridLayout_2.addWidget(self.spinBox_custom, 2, 2, 1, 1) - self.radioButton_PTHO = QRadioButton(self.groupBox_align_position) - self.radioButton_PTHO.setObjectName(u"radioButton_PTHO") - self.radioButton_PTHO.setFont(font1) - - self.gridLayout_2.addWidget(self.radioButton_PTHO, 1, 0, 1, 1) - - self.radioButton_NABD = QRadioButton(self.groupBox_align_position) - self.radioButton_NABD.setObjectName(u"radioButton_NABD") - self.radioButton_NABD.setFont(font1) - - self.gridLayout_2.addWidget(self.radioButton_NABD, 2, 0, 1, 1) - - self.pushButton_GetPos = QPushButton(self.groupBox_align_position) - self.pushButton_GetPos.setObjectName(u"pushButton_GetPos") - self.pushButton_GetPos.setMinimumSize(QSize(0, 30)) - self.pushButton_GetPos.setFont(font1) - - self.gridLayout_2.addWidget(self.pushButton_GetPos, 0, 0, 1, 1) - - self.pushButton_ChangeView = QPushButton(self.groupBox_align_position) - self.pushButton_ChangeView.setObjectName(u"pushButton_ChangeView") - self.pushButton_ChangeView.setMinimumSize(QSize(0, 30)) - self.pushButton_ChangeView.setFont(font1) - - self.gridLayout_2.addWidget(self.pushButton_ChangeView, 0, 2, 1, 1) - self.verticalLayout.addWidget(self.groupBox_align_position) + self.groupBox_2 = QGroupBox(self.groupBox_left) + self.groupBox_2.setObjectName(u"groupBox_2") + 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.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.horizontalLayout_3.addWidget(self.radioButton_freqTHO) + + 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.verticalLayout_3.addLayout(self.horizontalLayout_3) + + + self.verticalLayout.addWidget(self.groupBox_2) + self.groupBox_view_partially = QGroupBox(self.groupBox_left) self.groupBox_view_partially.setObjectName(u"groupBox_view_partially") self.verticalLayout_2 = QVBoxLayout(self.groupBox_view_partially) @@ -456,10 +485,6 @@ class Ui_MainWindow_approximately_align(object): self.verticalLayout.addWidget(self.groupBox_view_partially) - self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) - - self.verticalLayout.addItem(self.verticalSpacer) - self.pushButton_save = QPushButton(self.groupBox_left) self.pushButton_save.setObjectName(u"pushButton_save") sizePolicy.setHeightForWidth(self.pushButton_save.sizePolicy().hasHeightForWidth()) @@ -483,11 +508,12 @@ class Ui_MainWindow_approximately_align(object): self.verticalLayout.setStretch(0, 1) self.verticalLayout.setStretch(1, 1) self.verticalLayout.setStretch(2, 3) - self.verticalLayout.setStretch(4, 2) - self.verticalLayout.setStretch(5, 4) - self.verticalLayout.setStretch(6, 1) + self.verticalLayout.setStretch(3, 4) + self.verticalLayout.setStretch(4, 3) + self.verticalLayout.setStretch(5, 2) + self.verticalLayout.setStretch(6, 4) self.verticalLayout.setStretch(7, 1) - self.verticalLayout.setStretch(8, 4) + self.verticalLayout.setStretch(8, 6) self.gridLayout.addWidget(self.groupBox_left, 0, 0, 1, 1) @@ -541,13 +567,16 @@ class Ui_MainWindow_approximately_align(object): self.label_8.setText(QCoreApplication.translate("MainWindow_approximately_align", u"PSG_Post\uff1a", None)) self.pushButton_CutOff.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u5e94\u7528", None)) self.groupBox_align_position.setTitle(QCoreApplication.translate("MainWindow_approximately_align", u"\u5bf9\u9f50\u8d77\u59cb\u4f4d\u7f6e", None)) - self.radioButton_PABD.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u5907\u90093", None)) + self.pushButton_ChangeView.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u4f30\u8ba1\u91c7\u6837\u7387", None)) self.radioButton_NTHO.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u5907\u90092", None)) - self.radioButton_custom.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u81ea\u5b9a\u4e49", None)) self.radioButton_PTHO.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u5907\u90091", None)) - self.radioButton_NABD.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u5907\u90094", None)) + self.radioButton_custom.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u81ea\u5b9a\u4e49", None)) + self.radioButton_PABD.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u5907\u90093", None)) self.pushButton_GetPos.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u8ba1\u7b97\u5bf9\u9f50", None)) - self.pushButton_ChangeView.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u5207\u6362\u89c6\u56fe", 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_freqABD.setText(QCoreApplication.translate("MainWindow_approximately_align", u"\u5907\u90092", 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)) diff --git a/ui/MainWindow/MainWindow_approximately_align.ui b/ui/MainWindow/MainWindow_approximately_align.ui index daf2afc..bd9f6e9 100644 --- a/ui/MainWindow/MainWindow_approximately_align.ui +++ b/ui/MainWindow/MainWindow_approximately_align.ui @@ -25,7 +25,7 @@ 数据粗同步 - + @@ -531,15 +531,21 @@ 对齐起始位置 - - + + + + + 0 + 30 + + 12 - 备选3 + 估计采样率 @@ -555,6 +561,18 @@ + + + + + 12 + + + + 备选1 + + + @@ -567,6 +585,48 @@ + + + + + 12 + + + + 备选3 + + + + + + + + 0 + 30 + + + + + 12 + + + + 计算对齐 + + + + + + + + 12 + + + + 备选4 + + + @@ -594,69 +654,62 @@ - - - - - 12 - - - - 备选1 - - - - - - - - 12 - - - - 备选4 - - - - - - - - 0 - 30 - - - - - 12 - - - - 计算对齐 - - - - - - - - 0 - 30 - - - - - 12 - - - - 切换视图 - - - + + + + + 0 + 0 + + + + 采样率估计 + + + + + 11 + 18 + 401 + 41 + + + + + + + + + + 12 + + + + 备选1 + + + + + + + + 12 + + + + 备选2 + + + + + + + + + @@ -807,19 +860,6 @@ - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - -