From aaae84a77cc7dfd94c293b19e887b96a8d290068 Mon Sep 17 00:00:00 2001
From: Lesser324 <383538326@qq.com>
Date: Wed, 16 Jul 2025 10:10:30 +0800
Subject: [PATCH] =?UTF-8?q?qtcreator=E7=89=88=E6=9C=AC=E4=BB=A3=E7=A0=81?=
=?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E5=9C=A8qtcreator=E4=B8=AD=E8=BF=9B?=
=?UTF-8?q?=E8=A1=8C=E9=A1=B5=E9=9D=A2=E8=AE=BE=E8=AE=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
qtCreatorVer/dialogs/noise_dialog.py | 57 +++++
qtCreatorVer/dialogs/overscan_dialog.py | 56 ++++
qtCreatorVer/dialogs/ptc_dialog.py | 77 ++++++
qtCreatorVer/main.py | 35 +++
qtCreatorVer/pyui/mainwindow_ui.py | 133 ++++++++++
qtCreatorVer/pyui/noise_dialog_ui.py | 91 +++++++
qtCreatorVer/pyui/overscan_dialog_ui.py | 91 +++++++
qtCreatorVer/pyui/ptc_dialog_ui.py | 91 +++++++
qtCreatorVer/ui/generate_pyui.sh | 13 +
qtCreatorVer/ui/mainwindow.ui | 185 ++++++++++++++
qtCreatorVer/ui/noise_dialog.ui | 82 ++++++
qtCreatorVer/ui/overscan_dialog.ui | 82 ++++++
qtCreatorVer/ui/ptc_dialog.ui | 82 ++++++
qtCreatorVer/util/getNoise.py | 84 ++++++
qtCreatorVer/util/plot_prc.py | 68 +++++
qtCreatorVer/util/processPTC.py | 327 ++++++++++++++++++++++++
qtCreatorVer/util/remove_overscan.py | 55 ++++
qtCreatorVer/workers/process_worker.py | 30 +++
18 files changed, 1639 insertions(+)
create mode 100644 qtCreatorVer/dialogs/noise_dialog.py
create mode 100644 qtCreatorVer/dialogs/overscan_dialog.py
create mode 100644 qtCreatorVer/dialogs/ptc_dialog.py
create mode 100644 qtCreatorVer/main.py
create mode 100644 qtCreatorVer/pyui/mainwindow_ui.py
create mode 100644 qtCreatorVer/pyui/noise_dialog_ui.py
create mode 100644 qtCreatorVer/pyui/overscan_dialog_ui.py
create mode 100644 qtCreatorVer/pyui/ptc_dialog_ui.py
create mode 100644 qtCreatorVer/ui/generate_pyui.sh
create mode 100644 qtCreatorVer/ui/mainwindow.ui
create mode 100644 qtCreatorVer/ui/noise_dialog.ui
create mode 100644 qtCreatorVer/ui/overscan_dialog.ui
create mode 100644 qtCreatorVer/ui/ptc_dialog.ui
create mode 100644 qtCreatorVer/util/getNoise.py
create mode 100755 qtCreatorVer/util/plot_prc.py
create mode 100755 qtCreatorVer/util/processPTC.py
create mode 100644 qtCreatorVer/util/remove_overscan.py
create mode 100644 qtCreatorVer/workers/process_worker.py
diff --git a/qtCreatorVer/dialogs/noise_dialog.py b/qtCreatorVer/dialogs/noise_dialog.py
new file mode 100644
index 0000000..c174a3d
--- /dev/null
+++ b/qtCreatorVer/dialogs/noise_dialog.py
@@ -0,0 +1,57 @@
+from PySide6.QtWidgets import QDialog, QFileDialog, QMessageBox
+from pyui.noise_dialog_ui import Ui_NoiseDialog
+import sys
+import os
+import datetime
+from workers.process_worker import ProcessWorker
+
+class NoiseDialog(QDialog, Ui_NoiseDialog):
+ def __init__(self, parent=None, log_widget=None):
+ super().__init__(parent)
+ self.setupUi(self)
+ self.log_widget = log_widget
+ self.inBrowse.clicked.connect(self.browse_input)
+ self.outBrowse.clicked.connect(self.browse_output)
+ self.startBtn.clicked.connect(self.run_noise)
+ self.worker = None
+
+ def browse_input(self):
+ file, _ = QFileDialog.getOpenFileName(self, "选择FITS文件", "", "FITS Files (*.fits)")
+ if file:
+ self.inEdit.setText(file)
+
+ def browse_output(self):
+ directory = QFileDialog.getExistingDirectory(self, "选择输出目录")
+ if directory:
+ self.outEdit.setText(directory)
+
+ def run_noise(self):
+ fits_file = self.inEdit.text().strip()
+ out_dir = self.outEdit.text().strip()
+ if not fits_file or not os.path.isfile(fits_file):
+ QMessageBox.warning(self, "参数错误", "请选择有效的输入FITS文件!")
+ return
+ if not out_dir:
+ out_dir = os.path.dirname(fits_file) # 默认用输入文件所在目录
+ if self.log_widget:
+ self.log_widget.append(f"开始进行Bias/Noise处理")
+ cmd = [sys.executable, os.path.abspath("util/getNoise.py"), fits_file]
+ self.startBtn.setEnabled(False)
+ self.worker = ProcessWorker(cmd, cwd=out_dir)
+ self.worker.output_signal.connect(self.append_log)
+ self.worker.finished_signal.connect(lambda code: self.process_finished(code, out_dir))
+ self.worker.start()
+ self.accept() # 立即关闭对话框
+
+ def append_log(self, text):
+ if self.log_widget:
+ self.log_widget.append(text)
+ else:
+ QMessageBox.information(self, "运行结果", text)
+
+ def process_finished(self, code, result_dir):
+ self.startBtn.setEnabled(True)
+ if code == 0:
+ self.append_log(f"Bias/Noise处理完成,结果保存在: {result_dir}")
+ else:
+ self.append_log("Bias/Noise处理失败或中断。")
\ No newline at end of file
diff --git a/qtCreatorVer/dialogs/overscan_dialog.py b/qtCreatorVer/dialogs/overscan_dialog.py
new file mode 100644
index 0000000..770f4b9
--- /dev/null
+++ b/qtCreatorVer/dialogs/overscan_dialog.py
@@ -0,0 +1,56 @@
+from PySide6.QtWidgets import QDialog, QFileDialog, QMessageBox
+from pyui.overscan_dialog_ui import Ui_OverscanDialog
+import sys
+import os
+from workers.process_worker import ProcessWorker
+
+class OverscanDialog(QDialog, Ui_OverscanDialog):
+ def __init__(self, parent=None, log_widget=None):
+ super().__init__(parent)
+ self.setupUi(self)
+ self.log_widget = log_widget
+ self.inBrowse.clicked.connect(self.browse_input)
+ self.outBrowse.clicked.connect(self.browse_output)
+ self.startBtn.clicked.connect(self.run_overscan)
+ self.worker = None
+
+ def browse_input(self):
+ file, _ = QFileDialog.getOpenFileName(self, "选择FITS文件", "", "FITS Files (*.fits)")
+ if file:
+ self.inEdit.setText(file)
+
+ def browse_output(self):
+ directory = QFileDialog.getExistingDirectory(self, "选择输出目录")
+ if directory:
+ self.outEdit.setText(directory)
+
+ def run_overscan(self):
+ fits_file = self.inEdit.text().strip()
+ out_dir = self.outEdit.text().strip()
+ if not fits_file or not os.path.isfile(fits_file):
+ QMessageBox.warning(self, "参数错误", "请选择有效的输入FITS文件!")
+ return
+ if not out_dir:
+ out_dir = os.path.dirname(fits_file) # 默认用输入文件所在目录
+ if self.log_widget:
+ self.log_widget.append(f"开始进行过扫区修正处理")
+ cmd = [sys.executable, os.path.abspath("util/remove_overscan.py"), fits_file]
+ self.startBtn.setEnabled(False)
+ self.worker = ProcessWorker(cmd, cwd=out_dir)
+ self.worker.output_signal.connect(self.append_log)
+ self.worker.finished_signal.connect(lambda code: self.process_finished(code, out_dir))
+ self.worker.start()
+ self.accept() # 立即关闭对话框
+
+ def append_log(self, text):
+ if self.log_widget:
+ self.log_widget.append(text)
+ else:
+ QMessageBox.information(self, "运行结果", text)
+
+ def process_finished(self, code, out_dir):
+ self.startBtn.setEnabled(True)
+ if code == 0:
+ self.append_log(f"过扫区修正处理完成,结果保存在: {out_dir}")
+ else:
+ self.append_log("过扫区修正处理失败或中断。")
\ No newline at end of file
diff --git a/qtCreatorVer/dialogs/ptc_dialog.py b/qtCreatorVer/dialogs/ptc_dialog.py
new file mode 100644
index 0000000..5fc0276
--- /dev/null
+++ b/qtCreatorVer/dialogs/ptc_dialog.py
@@ -0,0 +1,77 @@
+from PySide6.QtWidgets import QDialog, QFileDialog, QMessageBox
+from pyui.ptc_dialog_ui import Ui_PTCDialog
+import sys
+import os
+import datetime
+from workers.process_worker import ProcessWorker
+
+class PTCDialog(QDialog, Ui_PTCDialog):
+ def __init__(self, parent=None, log_widget=None):
+ super().__init__(parent)
+ self.setupUi(self)
+ self.log_widget = log_widget
+ self.inBrowse.clicked.connect(self.browse_input)
+ self.outBrowse.clicked.connect(self.browse_output)
+ self.startBtn.clicked.connect(self.run_ptc)
+ self.worker = None
+ self.prc_worker = None # 新增PRC分析工作线程
+
+ def browse_input(self):
+ directory = QFileDialog.getExistingDirectory(self, "选择输入目录")
+ if directory:
+ self.inEdit.setText(directory)
+
+ def browse_output(self):
+ directory = QFileDialog.getExistingDirectory(self, "选择输出目录")
+ if directory:
+ self.outEdit.setText(directory)
+
+ def run_ptc(self):
+ in_dir = self.inEdit.text().strip()
+ out_dir = self.outEdit.text().strip()
+ if not in_dir:
+ QMessageBox.warning(self, "参数错误", "请选择输入目录!")
+ return
+ if not out_dir:
+ out_dir = in_dir # 如果未选择输出目录,默认用输入目录
+ # 新建带时间戳的子文件夹
+ timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
+ result_dir = os.path.join(out_dir, f'PTC_PRC_result_{timestamp}')
+ try:
+ os.makedirs(result_dir, exist_ok=True)
+ except Exception as e:
+ QMessageBox.critical(self, "文件夹创建失败", f"无法创建结果文件夹: {result_dir}\n错误: {e}")
+ return
+ self.append_log(f"开始进行PTC处理")
+ cmd = [sys.executable, os.path.abspath("util/processPTC.py"), in_dir, result_dir]
+ self.startBtn.setEnabled(False)
+ self.worker = ProcessWorker(cmd)
+ self.worker.output_signal.connect(self.append_log)
+ self.worker.finished_signal.connect(lambda code: self.process_finished(code, result_dir))
+ self.worker.start()
+ self.accept() # 立即关闭对话框
+
+ def append_log(self, text):
+ if self.log_widget:
+ self.log_widget.append(text)
+ else:
+ QMessageBox.information(self, "运行结果", text)
+
+ def process_finished(self, code, result_dir):
+ self.startBtn.setEnabled(True)
+ if code == 0:
+ self.append_log(f"PTC处理完成,正在进行PRC分析...\n")
+ # 自动调用plot_prc.py
+ prc_cmd = [sys.executable, os.path.abspath("util/plot_prc.py"), result_dir]
+ self.prc_worker = ProcessWorker(prc_cmd)
+ self.prc_worker.output_signal.connect(self.append_log)
+ self.prc_worker.finished_signal.connect(lambda code: self.prc_finished(code, result_dir))
+ self.prc_worker.start()
+ else:
+ self.append_log("PTC处理失败或中断。")
+
+ def prc_finished(self, code, result_dir):
+ if code == 0:
+ self.append_log("PRC分析完成!\n结果保存在: {}".format(result_dir))
+ else:
+ self.append_log("PRC分析失败或中断。")
\ No newline at end of file
diff --git a/qtCreatorVer/main.py b/qtCreatorVer/main.py
new file mode 100644
index 0000000..9064f59
--- /dev/null
+++ b/qtCreatorVer/main.py
@@ -0,0 +1,35 @@
+import subprocess
+from PySide6.QtWidgets import QApplication, QMainWindow
+from pyui.mainwindow_ui import Ui_MainWindow
+from dialogs.noise_dialog import NoiseDialog
+from dialogs.ptc_dialog import PTCDialog
+from dialogs.overscan_dialog import OverscanDialog
+import sys
+import os
+import datetime
+
+class MainWindow(QMainWindow, Ui_MainWindow):
+ def __init__(self):
+ super().__init__()
+ self.setupUi(self)
+ self.noiseButton.clicked.connect(self.show_noise_dialog)
+ self.ptcButton.clicked.connect(self.show_ptc_dialog)
+ self.overscanButton.clicked.connect(self.show_overscan_dialog)
+
+ def show_noise_dialog(self):
+ dialog = NoiseDialog(self, log_widget=self.logTextEdit)
+ dialog.exec()
+
+ def show_ptc_dialog(self):
+ dialog = PTCDialog(self, log_widget=self.logTextEdit)
+ dialog.exec()
+
+ def show_overscan_dialog(self):
+ dialog = OverscanDialog(self, log_widget=self.logTextEdit)
+ dialog.exec()
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ window = MainWindow()
+ window.show()
+ sys.exit(app.exec())
diff --git a/qtCreatorVer/pyui/mainwindow_ui.py b/qtCreatorVer/pyui/mainwindow_ui.py
new file mode 100644
index 0000000..27ab343
--- /dev/null
+++ b/qtCreatorVer/pyui/mainwindow_ui.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'mainwindow.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.16
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import * # type: ignore
+from PySide6.QtGui import * # type: ignore
+from PySide6.QtWidgets import * # type: ignore
+
+
+class Ui_MainWindow(object):
+ def setupUi(self, MainWindow):
+ if not MainWindow.objectName():
+ MainWindow.setObjectName(u"MainWindow")
+ MainWindow.resize(900, 500)
+ self.centralwidget = QWidget(MainWindow)
+ self.centralwidget.setObjectName(u"centralwidget")
+ self.mainHLayout = QHBoxLayout(self.centralwidget)
+ self.mainHLayout.setObjectName(u"mainHLayout")
+ self.mainVLayout = QVBoxLayout()
+ self.mainVLayout.setObjectName(u"mainVLayout")
+ self.titleLabel = QLabel(self.centralwidget)
+ self.titleLabel.setObjectName(u"titleLabel")
+ self.titleLabel.setMinimumSize(QSize(398, 25))
+ self.titleLabel.setMaximumSize(QSize(1000, 25))
+ font = QFont()
+ font.setPointSize(18)
+ font.setBold(True)
+ self.titleLabel.setFont(font)
+ self.titleLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
+
+ self.mainVLayout.addWidget(self.titleLabel)
+
+ self.radioLayout = QHBoxLayout()
+ self.radioLayout.setObjectName(u"radioLayout")
+ self.radioLayout.setContentsMargins(12, -1, -1, -1)
+ self.radioLabel = QLabel(self.centralwidget)
+ self.radioLabel.setObjectName(u"radioLabel")
+ self.radioLabel.setMaximumSize(QSize(200, 30))
+
+ self.radioLayout.addWidget(self.radioLabel)
+
+ self.radio290_00 = QRadioButton(self.centralwidget)
+ self.radio290_00.setObjectName(u"radio290_00")
+ self.radio290_00.setMaximumSize(QSize(200, 30))
+ self.radio290_00.setChecked(True)
+
+ self.radioLayout.addWidget(self.radio290_00)
+
+ self.radio47_20 = QRadioButton(self.centralwidget)
+ self.radio47_20.setObjectName(u"radio47_20")
+ self.radio47_20.setMaximumSize(QSize(200, 30))
+
+ self.radioLayout.addWidget(self.radio47_20)
+
+
+ self.mainVLayout.addLayout(self.radioLayout)
+
+ self.buttonsLayout1 = QHBoxLayout()
+ self.buttonsLayout1.setObjectName(u"buttonsLayout1")
+ self.ptcButton = QPushButton(self.centralwidget)
+ self.ptcButton.setObjectName(u"ptcButton")
+ self.ptcButton.setMaximumSize(QSize(200, 30))
+
+ self.buttonsLayout1.addWidget(self.ptcButton)
+
+ self.noiseButton = QPushButton(self.centralwidget)
+ self.noiseButton.setObjectName(u"noiseButton")
+ self.noiseButton.setMaximumSize(QSize(200, 30))
+
+ self.buttonsLayout1.addWidget(self.noiseButton)
+
+
+ self.mainVLayout.addLayout(self.buttonsLayout1)
+
+ self.buttonsLayout2 = QHBoxLayout()
+ self.buttonsLayout2.setObjectName(u"buttonsLayout2")
+ self.linearityButton = QPushButton(self.centralwidget)
+ self.linearityButton.setObjectName(u"linearityButton")
+ self.linearityButton.setMaximumSize(QSize(200, 30))
+
+ self.buttonsLayout2.addWidget(self.linearityButton)
+
+ self.overscanButton = QPushButton(self.centralwidget)
+ self.overscanButton.setObjectName(u"overscanButton")
+ self.overscanButton.setMaximumSize(QSize(200, 30))
+
+ self.buttonsLayout2.addWidget(self.overscanButton)
+
+
+ self.mainVLayout.addLayout(self.buttonsLayout2)
+
+
+ self.mainHLayout.addLayout(self.mainVLayout)
+
+ self.logTextEdit = QTextEdit(self.centralwidget)
+ self.logTextEdit.setObjectName(u"logTextEdit")
+ self.logTextEdit.setReadOnly(True)
+
+ self.mainHLayout.addWidget(self.logTextEdit)
+
+ MainWindow.setCentralWidget(self.centralwidget)
+ self.menubar = QMenuBar(MainWindow)
+ self.menubar.setObjectName(u"menubar")
+ self.menubar.setGeometry(QRect(0, 0, 900, 24))
+ MainWindow.setMenuBar(self.menubar)
+ self.statusbar = QStatusBar(MainWindow)
+ self.statusbar.setObjectName(u"statusbar")
+ MainWindow.setStatusBar(self.statusbar)
+
+ self.retranslateUi(MainWindow)
+
+ QMetaObject.connectSlotsByName(MainWindow)
+ # setupUi
+
+ def retranslateUi(self, MainWindow):
+ MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"CCD\u6570\u636e\u5904\u7406\u5de5\u5177", None))
+ self.titleLabel.setText(QCoreApplication.translate("MainWindow", u"CCD\u6570\u636e\u5904\u7406\u5de5\u5177", None))
+ self.radioLabel.setText(QCoreApplication.translate("MainWindow", u"\u9009\u62e9CCD\u7c7b\u578b:", None))
+ self.radio290_00.setText(QCoreApplication.translate("MainWindow", u"290-99", None))
+ self.radio47_20.setText(QCoreApplication.translate("MainWindow", u"47-20", None))
+ self.ptcButton.setText(QCoreApplication.translate("MainWindow", u"PTC/PRC", None))
+ self.noiseButton.setText(QCoreApplication.translate("MainWindow", u"Bias/Noise", None))
+ self.linearityButton.setText(QCoreApplication.translate("MainWindow", u"Linearity", None))
+ self.overscanButton.setText(QCoreApplication.translate("MainWindow", u"\u8fc7\u626b\u533a\u4fee\u6b63", None))
+ self.logTextEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"\u65e5\u5fd7\u8f93\u51fa...", None))
+ # retranslateUi
+
diff --git a/qtCreatorVer/pyui/noise_dialog_ui.py b/qtCreatorVer/pyui/noise_dialog_ui.py
new file mode 100644
index 0000000..3562b84
--- /dev/null
+++ b/qtCreatorVer/pyui/noise_dialog_ui.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'noise_dialog.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.16
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import * # type: ignore
+from PySide6.QtGui import * # type: ignore
+from PySide6.QtWidgets import * # type: ignore
+
+
+class Ui_NoiseDialog(object):
+ def setupUi(self, NoiseDialog):
+ if not NoiseDialog.objectName():
+ NoiseDialog.setObjectName(u"NoiseDialog")
+ NoiseDialog.setMinimumWidth(400)
+ self.verticalLayout = QVBoxLayout(NoiseDialog)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.inLayout = QHBoxLayout()
+ self.inLayout.setObjectName(u"inLayout")
+ self.inLabel = QLabel(NoiseDialog)
+ self.inLabel.setObjectName(u"inLabel")
+
+ self.inLayout.addWidget(self.inLabel)
+
+ self.inEdit = QLineEdit(NoiseDialog)
+ self.inEdit.setObjectName(u"inEdit")
+
+ self.inLayout.addWidget(self.inEdit)
+
+ self.inBrowse = QPushButton(NoiseDialog)
+ self.inBrowse.setObjectName(u"inBrowse")
+
+ self.inLayout.addWidget(self.inBrowse)
+
+
+ self.verticalLayout.addLayout(self.inLayout)
+
+ self.outLayout = QHBoxLayout()
+ self.outLayout.setObjectName(u"outLayout")
+ self.outLabel = QLabel(NoiseDialog)
+ self.outLabel.setObjectName(u"outLabel")
+
+ self.outLayout.addWidget(self.outLabel)
+
+ self.outEdit = QLineEdit(NoiseDialog)
+ self.outEdit.setObjectName(u"outEdit")
+
+ self.outLayout.addWidget(self.outEdit)
+
+ self.outBrowse = QPushButton(NoiseDialog)
+ self.outBrowse.setObjectName(u"outBrowse")
+
+ self.outLayout.addWidget(self.outBrowse)
+
+
+ self.verticalLayout.addLayout(self.outLayout)
+
+ self.btnLayout = QHBoxLayout()
+ self.btnLayout.setObjectName(u"btnLayout")
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.btnLayout.addItem(self.horizontalSpacer)
+
+ self.startBtn = QPushButton(NoiseDialog)
+ self.startBtn.setObjectName(u"startBtn")
+
+ self.btnLayout.addWidget(self.startBtn)
+
+
+ self.verticalLayout.addLayout(self.btnLayout)
+
+
+ self.retranslateUi(NoiseDialog)
+
+ QMetaObject.connectSlotsByName(NoiseDialog)
+ # setupUi
+
+ def retranslateUi(self, NoiseDialog):
+ NoiseDialog.setWindowTitle(QCoreApplication.translate("NoiseDialog", u"Bias/Noise\u53c2\u6570\u8bbe\u7f6e", None))
+ self.inLabel.setText(QCoreApplication.translate("NoiseDialog", u"\u8f93\u5165FITS\u6587\u4ef6:", None))
+ self.inBrowse.setText(QCoreApplication.translate("NoiseDialog", u"\u6d4f\u89c8", None))
+ self.outLabel.setText(QCoreApplication.translate("NoiseDialog", u"\u8f93\u51fa\u76ee\u5f55:", None))
+ self.outBrowse.setText(QCoreApplication.translate("NoiseDialog", u"\u6d4f\u89c8", None))
+ self.startBtn.setText(QCoreApplication.translate("NoiseDialog", u"\u5f00\u59cb\u5904\u7406", None))
+ # retranslateUi
+
diff --git a/qtCreatorVer/pyui/overscan_dialog_ui.py b/qtCreatorVer/pyui/overscan_dialog_ui.py
new file mode 100644
index 0000000..5fc70e4
--- /dev/null
+++ b/qtCreatorVer/pyui/overscan_dialog_ui.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'overscan_dialog.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.16
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import * # type: ignore
+from PySide6.QtGui import * # type: ignore
+from PySide6.QtWidgets import * # type: ignore
+
+
+class Ui_OverscanDialog(object):
+ def setupUi(self, OverscanDialog):
+ if not OverscanDialog.objectName():
+ OverscanDialog.setObjectName(u"OverscanDialog")
+ OverscanDialog.setMinimumWidth(400)
+ self.verticalLayout = QVBoxLayout(OverscanDialog)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.inLayout = QHBoxLayout()
+ self.inLayout.setObjectName(u"inLayout")
+ self.inLabel = QLabel(OverscanDialog)
+ self.inLabel.setObjectName(u"inLabel")
+
+ self.inLayout.addWidget(self.inLabel)
+
+ self.inEdit = QLineEdit(OverscanDialog)
+ self.inEdit.setObjectName(u"inEdit")
+
+ self.inLayout.addWidget(self.inEdit)
+
+ self.inBrowse = QPushButton(OverscanDialog)
+ self.inBrowse.setObjectName(u"inBrowse")
+
+ self.inLayout.addWidget(self.inBrowse)
+
+
+ self.verticalLayout.addLayout(self.inLayout)
+
+ self.outLayout = QHBoxLayout()
+ self.outLayout.setObjectName(u"outLayout")
+ self.outLabel = QLabel(OverscanDialog)
+ self.outLabel.setObjectName(u"outLabel")
+
+ self.outLayout.addWidget(self.outLabel)
+
+ self.outEdit = QLineEdit(OverscanDialog)
+ self.outEdit.setObjectName(u"outEdit")
+
+ self.outLayout.addWidget(self.outEdit)
+
+ self.outBrowse = QPushButton(OverscanDialog)
+ self.outBrowse.setObjectName(u"outBrowse")
+
+ self.outLayout.addWidget(self.outBrowse)
+
+
+ self.verticalLayout.addLayout(self.outLayout)
+
+ self.btnLayout = QHBoxLayout()
+ self.btnLayout.setObjectName(u"btnLayout")
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.btnLayout.addItem(self.horizontalSpacer)
+
+ self.startBtn = QPushButton(OverscanDialog)
+ self.startBtn.setObjectName(u"startBtn")
+
+ self.btnLayout.addWidget(self.startBtn)
+
+
+ self.verticalLayout.addLayout(self.btnLayout)
+
+
+ self.retranslateUi(OverscanDialog)
+
+ QMetaObject.connectSlotsByName(OverscanDialog)
+ # setupUi
+
+ def retranslateUi(self, OverscanDialog):
+ OverscanDialog.setWindowTitle(QCoreApplication.translate("OverscanDialog", u"\u8fc7\u626b\u533a\u4fee\u6b63\u53c2\u6570\u8bbe\u7f6e", None))
+ self.inLabel.setText(QCoreApplication.translate("OverscanDialog", u"\u8f93\u5165FITS\u6587\u4ef6:", None))
+ self.inBrowse.setText(QCoreApplication.translate("OverscanDialog", u"\u6d4f\u89c8", None))
+ self.outLabel.setText(QCoreApplication.translate("OverscanDialog", u"\u8f93\u51fa\u76ee\u5f55:", None))
+ self.outBrowse.setText(QCoreApplication.translate("OverscanDialog", u"\u6d4f\u89c8", None))
+ self.startBtn.setText(QCoreApplication.translate("OverscanDialog", u"\u5f00\u59cb\u5904\u7406", None))
+ # retranslateUi
+
diff --git a/qtCreatorVer/pyui/ptc_dialog_ui.py b/qtCreatorVer/pyui/ptc_dialog_ui.py
new file mode 100644
index 0000000..7a61c02
--- /dev/null
+++ b/qtCreatorVer/pyui/ptc_dialog_ui.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'ptc_dialog.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.16
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import * # type: ignore
+from PySide6.QtGui import * # type: ignore
+from PySide6.QtWidgets import * # type: ignore
+
+
+class Ui_PTCDialog(object):
+ def setupUi(self, PTCDialog):
+ if not PTCDialog.objectName():
+ PTCDialog.setObjectName(u"PTCDialog")
+ PTCDialog.setMinimumWidth(400)
+ self.verticalLayout = QVBoxLayout(PTCDialog)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.inLayout = QHBoxLayout()
+ self.inLayout.setObjectName(u"inLayout")
+ self.inLabel = QLabel(PTCDialog)
+ self.inLabel.setObjectName(u"inLabel")
+
+ self.inLayout.addWidget(self.inLabel)
+
+ self.inEdit = QLineEdit(PTCDialog)
+ self.inEdit.setObjectName(u"inEdit")
+
+ self.inLayout.addWidget(self.inEdit)
+
+ self.inBrowse = QPushButton(PTCDialog)
+ self.inBrowse.setObjectName(u"inBrowse")
+
+ self.inLayout.addWidget(self.inBrowse)
+
+
+ self.verticalLayout.addLayout(self.inLayout)
+
+ self.outLayout = QHBoxLayout()
+ self.outLayout.setObjectName(u"outLayout")
+ self.outLabel = QLabel(PTCDialog)
+ self.outLabel.setObjectName(u"outLabel")
+
+ self.outLayout.addWidget(self.outLabel)
+
+ self.outEdit = QLineEdit(PTCDialog)
+ self.outEdit.setObjectName(u"outEdit")
+
+ self.outLayout.addWidget(self.outEdit)
+
+ self.outBrowse = QPushButton(PTCDialog)
+ self.outBrowse.setObjectName(u"outBrowse")
+
+ self.outLayout.addWidget(self.outBrowse)
+
+
+ self.verticalLayout.addLayout(self.outLayout)
+
+ self.btnLayout = QHBoxLayout()
+ self.btnLayout.setObjectName(u"btnLayout")
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.btnLayout.addItem(self.horizontalSpacer)
+
+ self.startBtn = QPushButton(PTCDialog)
+ self.startBtn.setObjectName(u"startBtn")
+
+ self.btnLayout.addWidget(self.startBtn)
+
+
+ self.verticalLayout.addLayout(self.btnLayout)
+
+
+ self.retranslateUi(PTCDialog)
+
+ QMetaObject.connectSlotsByName(PTCDialog)
+ # setupUi
+
+ def retranslateUi(self, PTCDialog):
+ PTCDialog.setWindowTitle(QCoreApplication.translate("PTCDialog", u"PTC/PRC\u53c2\u6570\u8bbe\u7f6e", None))
+ self.inLabel.setText(QCoreApplication.translate("PTCDialog", u"\u8f93\u5165\u76ee\u5f55:", None))
+ self.inBrowse.setText(QCoreApplication.translate("PTCDialog", u"\u6d4f\u89c8", None))
+ self.outLabel.setText(QCoreApplication.translate("PTCDialog", u"\u8f93\u51fa\u76ee\u5f55:", None))
+ self.outBrowse.setText(QCoreApplication.translate("PTCDialog", u"\u6d4f\u89c8", None))
+ self.startBtn.setText(QCoreApplication.translate("PTCDialog", u"\u5f00\u59cb\u5904\u7406", None))
+ # retranslateUi
+
diff --git a/qtCreatorVer/ui/generate_pyui.sh b/qtCreatorVer/ui/generate_pyui.sh
new file mode 100644
index 0000000..34464e8
--- /dev/null
+++ b/qtCreatorVer/ui/generate_pyui.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# 自动将ui目录下所有.ui文件编译为pyui目录下的 _ui.py 文件
+# 需要已安装 pyside6-uic
+
+OUTDIR="../pyui"
+mkdir -p "$OUTDIR"
+
+for ui_file in *.ui; do
+ base_name=$(basename "$ui_file" .ui)
+ pyside6-uic "$ui_file" -o "$OUTDIR/${base_name}_ui.py"
+done
+
+echo "所有UI文件已编译到 $OUTDIR 下。"
\ No newline at end of file
diff --git a/qtCreatorVer/ui/mainwindow.ui b/qtCreatorVer/ui/mainwindow.ui
new file mode 100644
index 0000000..f539ca3
--- /dev/null
+++ b/qtCreatorVer/ui/mainwindow.ui
@@ -0,0 +1,185 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 900
+ 500
+
+
+
+ CCD数据处理工具
+
+
+
+ -
+
+
-
+
+
+
+ 398
+ 25
+
+
+
+
+ 1000
+ 25
+
+
+
+
+ 18
+ true
+
+
+
+ CCD数据处理工具
+
+
+ Qt::AlignmentFlag::AlignCenter
+
+
+
+ -
+
+
+ 12
+
+
-
+
+
+
+ 200
+ 30
+
+
+
+ 选择CCD类型:
+
+
+
+ -
+
+
+
+ 200
+ 30
+
+
+
+ 290-99
+
+
+ true
+
+
+
+ -
+
+
+
+ 200
+ 30
+
+
+
+ 47-20
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 200
+ 30
+
+
+
+ PTC/PRC
+
+
+
+ -
+
+
+
+ 200
+ 30
+
+
+
+ Bias/Noise
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 200
+ 30
+
+
+
+ Linearity
+
+
+
+ -
+
+
+
+ 200
+ 30
+
+
+
+ 过扫区修正
+
+
+
+
+
+
+
+ -
+
+
+ true
+
+
+ 日志输出...
+
+
+
+
+
+
+
+
+
+
+
diff --git a/qtCreatorVer/ui/noise_dialog.ui b/qtCreatorVer/ui/noise_dialog.ui
new file mode 100644
index 0000000..b35cb3f
--- /dev/null
+++ b/qtCreatorVer/ui/noise_dialog.ui
@@ -0,0 +1,82 @@
+
+
+ NoiseDialog
+
+
+ Bias/Noise参数设置
+
+
+ 400
+
+
+ -
+
+
-
+
+
+ 输入FITS文件:
+
+
+
+ -
+
+
+ -
+
+
+ 浏览
+
+
+
+
+
+ -
+
+
-
+
+
+ 输出目录:
+
+
+
+ -
+
+
+ -
+
+
+ 浏览
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ 开始处理
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/qtCreatorVer/ui/overscan_dialog.ui b/qtCreatorVer/ui/overscan_dialog.ui
new file mode 100644
index 0000000..d71dbbb
--- /dev/null
+++ b/qtCreatorVer/ui/overscan_dialog.ui
@@ -0,0 +1,82 @@
+
+
+ OverscanDialog
+
+
+ 过扫区修正参数设置
+
+
+ 400
+
+
+ -
+
+
-
+
+
+ 输入FITS文件:
+
+
+
+ -
+
+
+ -
+
+
+ 浏览
+
+
+
+
+
+ -
+
+
-
+
+
+ 输出目录:
+
+
+
+ -
+
+
+ -
+
+
+ 浏览
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ 开始处理
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/qtCreatorVer/ui/ptc_dialog.ui b/qtCreatorVer/ui/ptc_dialog.ui
new file mode 100644
index 0000000..be1ed66
--- /dev/null
+++ b/qtCreatorVer/ui/ptc_dialog.ui
@@ -0,0 +1,82 @@
+
+
+ PTCDialog
+
+
+ PTC/PRC参数设置
+
+
+ 400
+
+
+ -
+
+
-
+
+
+ 输入目录:
+
+
+
+ -
+
+
+ -
+
+
+ 浏览
+
+
+
+
+
+ -
+
+
-
+
+
+ 输出目录:
+
+
+
+ -
+
+
+ -
+
+
+ 浏览
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ 开始处理
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/qtCreatorVer/util/getNoise.py b/qtCreatorVer/util/getNoise.py
new file mode 100644
index 0000000..b5c896d
--- /dev/null
+++ b/qtCreatorVer/util/getNoise.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+
+import os, sys
+import time
+from astropy.io import fits
+import numpy as np
+from scipy.stats import sigmaclip
+from astropy.table import Table
+
+def write_log(log_msg):
+
+ curTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
+ msg = f'[{curTime}] -- run getNoise.py and saved {log_msg}\n'
+ if os.path.exists('noise_log.txt'):
+ fp = open('noise_log.txt', 'a')
+ else:
+ fp = open('noise_log.txt', 'w')
+
+ fp.write(msg)
+ fp.close()
+
+def remove_overscan(imgdata):
+ print('> doing overscan correction ...')
+ img = np.zeros((9232,9216), dtype=float)
+
+ for i in range(4):
+ img[0:4616, i*1152:(i+1)*1152] = imgdata[0:4616,(i*1250+27):((i+1)*1250-71)]
+ img[4616:, i*1152:(i+1)*1152] = imgdata[4784:,(i*1250+27):((i+1)*1250-71)]
+ oc1 = np.mean(imgdata[0:4616,((i+1)*1250-71):((i+1)*1250)], axis=1).astype(float)
+ oc2 = np.mean(imgdata[4784:,((i+1)*1250-71):((i+1)*1250)], axis=1).astype(float)
+# print('oc1.shape: {}'.format(oc1.shape))
+ for j in range(4616):
+ img[j,i*1152:(i+1)*1152] = img[j,i*1152:(i+1)*1152] - oc1[j]
+ img[4616+j,i*1152:(i+1)*1152] = img[4616+j,i*1152:(i+1)*1152]- oc2[j]
+ for i in range(4,8):
+ img[0:4616, i*1152:(i+1)*1152] = imgdata[0:4616,(i*1250+71):((i+1)*1250-27)]
+ img[4616:, i*1152:(i+1)*1152] = imgdata[4784:,(i*1250+71):((i+1)*1250-27)]
+ oc1 = np.mean(imgdata[0:4616,(i*1250):(i*1250+71)], axis=1).astype(float)
+ oc2 = np.mean(imgdata[4784:,(i*1250):(i*1250+71)], axis=1).astype(float)
+ for j in range(4616):
+ img[j, i*1152:(i+1)*1152] = img[j, i*1152:(i+1)*1152] - oc1[j]
+ img[4616+j,i*1152:(i+1)*1152] = img[4616+j,i*1152:(i+1)*1152] - oc2[j]
+
+ print('> overscan correction done!')
+ return img
+
+def main():
+ fname = sys.argv[1]
+ imgdata = fits.getdata(fname).astype(float)
+ img = remove_overscan(imgdata)
+
+ os = []
+ noise_clip_4_4 = []
+ noise_clip_3_3 = []
+ for ch in range(16):
+# print('> estimating rms for ch-{:02d}'.format(ch+1))
+ os.append(ch+1)
+ if ch < 8:
+ ch_data = img[0:4616,ch*1152:(ch+1)*1152]
+ else:
+ ch_data = img[4616:, (15-ch)*1152:(16-ch)*1152]
+
+ rms44 = np.std( sigmaclip(ch_data, 4, 4)[0] )
+ rms33 = np.std( sigmaclip(ch_data, 3, 3)[0] )
+ noise_clip_4_4.append( np.round(rms44,4) )
+ noise_clip_3_3.append( np.round(rms33,4) )
+
+ tab = Table()
+ tab['OS'] = os
+ tab['Noise_Clip_4_4'] = noise_clip_4_4
+ tab['Noise_Clip_3_3'] = noise_clip_3_3
+ print(tab)
+ curTime = time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime())
+
+ tab_name = fname.replace('.fits','') + '_{}.tab'.format(curTime)
+ tab.write(tab_name, format='ipac', overwrite=True)
+ write_log(tab_name)
+
+# print('\n# Channel noise(clip,4,4) noise(clip,3,3)\n')
+# for i in range(16):
+# print('> OS-{:02d} {:6.3f} {:6.3f}'.format(i, noise_clip_4_4[i], noise_clip_3_3[i]))
+
+if __name__ == '__main__':
+ main()
diff --git a/qtCreatorVer/util/plot_prc.py b/qtCreatorVer/util/plot_prc.py
new file mode 100755
index 0000000..9a2bb49
--- /dev/null
+++ b/qtCreatorVer/util/plot_prc.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+
+import os, sys
+import numpy as np
+from astropy.table import Table
+import matplotlib.pylab as plt
+from scipy import odr
+
+def plotPRC(out_dir):
+ tab = Table.read(f'{out_dir}/ptcdata.tab', format='ipac')
+
+ model = odr.polynomial(1)
+
+ fig = plt.figure(figsize=(20,40))
+
+ for ch in range(16):
+ row = ch // 4
+ pos1 = row*8 + ch - row*4 + 1
+ pos2 = pos1 + 4
+
+ # print(f'pos1: {pos1}, pos2: {pos2}')
+
+ plt.subplot(8,4,pos1)
+
+ index = tab['channel'] == ch+1
+
+ t_exp = tab[index]['t_exp']
+ flux = tab[index]['flux_4']
+
+ # fit prc and calculate residuals
+ data = odr.Data(t_exp, flux, we=1/(t_exp**2), wd=1/(flux**2) )
+ # data = odr.Data(t_exp, flux, we=1/(t_exp**2) )
+ # data = odr.Data(t_exp, flux)
+ fitter = odr.ODR(data, model, ifixb=[1,1], beta0=[2500,800])
+ res = fitter.run()
+ poly = np.poly1d(res.beta[::-1])
+ nonlin = (flux-poly(t_exp)) / poly(t_exp) * 100
+
+ plt.scatter(t_exp, flux/1e3, zorder=1, alpha=0.85, color='m', edgecolors='none', s=30)
+ # plt.plot(t_exp, flux/1e3, 'g--o', alpha=0.75)
+ plt.plot(t_exp, poly(t_exp)/1e3, 'b--', alpha=0.75)
+ plt.xlabel('Expourse Time (s)')
+ plt.ylabel('Flux (kDN)')
+ plt.ylim([-1,6.1e1])
+ # plt.title('channel {}'.format(ch+1))
+ plt.text(5,55,'channel {}'.format(ch+1), color='limegreen', fontsize=14)
+ plt.grid()
+
+
+ plt.subplot(8,4,pos2)
+ print(res.beta)
+ plt.scatter(t_exp, nonlin, alpha=0.75, color='darkorange', edgecolors='none', s=30)
+ plt.hlines(y=1.8, xmin=t_exp.min(), xmax=t_exp.max(), linestyle=':', color='dimgrey')
+ plt.hlines(y=-1.8, xmin=t_exp.min(), xmax=t_exp.max(), linestyle=':', color='dimgrey')
+ plt.plot(t_exp, np.zeros(len(t_exp)), '--', color='grey')
+ plt.ylim([-3.25,3.25])
+ plt.xlabel('Expourse Time (s)')
+ plt.ylabel('PRC Nonlinearity (%)')
+ # plt.title('channel {}'.format(ch+1))
+ plt.text(5,2.5,'channel {}'.format(ch+1), color='brown', fontsize=14)
+ plt.grid()
+
+ plt.tight_layout()
+ plt.savefig(f'{out_dir}/prc.pdf')
+ plt.close()
+
+if __name__ == '__main__':
+ plotPRC(sys.argv[1])
diff --git a/qtCreatorVer/util/processPTC.py b/qtCreatorVer/util/processPTC.py
new file mode 100755
index 0000000..983b25b
--- /dev/null
+++ b/qtCreatorVer/util/processPTC.py
@@ -0,0 +1,327 @@
+#!/usr/bin/env python
+
+import glob, os, sys
+from tqdm import trange
+from astropy.table import Table
+from astropy.io import fits
+from scipy.stats import sigmaclip
+from scipy import odr
+import numpy as np
+from matplotlib import pyplot as plt
+
+def remove_overscan(imgdata, ch):
+ # print('> removing 71 cols overscan ...')
+ img = np.zeros((4616,1152), dtype=float)
+
+# for i in range(4):
+# img[0:4616, i*1152:(i+1)*1152] = imgdata[0:4616,(i*1250+27):((i+1)*1250-71)]
+# img[4616:, i*1152:(i+1)*1152] = imgdata[4784:,(i*1250+27):((i+1)*1250-71)]
+# oc1 = np.median(imgdata[0:4616,((i+1)*1250-71):((i+1)*1250)], axis=1).astype(float)
+# oc2 = np.median(imgdata[4784:,((i+1)*1250-71):((i+1)*1250)], axis=1).astype(float)
+# # print('oc1.shape: {}'.format(oc1.shape))
+# for j in range(4616):
+# img[j,i*1152:(i+1)*1152] = img[j,i*1152:(i+1)*1152] - oc1[j]
+# img[4616+j,i*1152:(i+1)*1152] = img[4616+j,i*1152:(i+1)*1152]- oc2[j]
+# for i in range(4,8):
+# img[0:4616, i*1152:(i+1)*1152] = imgdata[0:4616,(i*1250+71):((i+1)*1250-27)]
+# img[4616:, i*1152:(i+1)*1152] = imgdata[4784:,(i*1250+71):((i+1)*1250-27)]
+# oc1 = np.median(imgdata[0:4616,(i*1250):(i*1250+71)], axis=1).astype(float)
+# oc2 = np.median(imgdata[4784:,(i*1250):(i*1250+71)], axis=1).astype(float)
+# for j in range(4616):
+# img[j, i*1152:(i+1)*1152] = img[j, i*1152:(i+1)*1152] - oc1[j]
+# img[4616+j,i*1152:(i+1)*1152] = img[4616+j,i*1152:(i+1)*1152] - oc2[j]
+
+ if ch < 4:
+ img = imgdata[0:4616,(ch*1250+27):((ch+1)*1250-71)]
+ oc1 = np.median(imgdata[0:4616,((ch+1)*1250-71):((ch+1)*1250)], axis=1).astype(float)
+ for j in range(4616):
+ img[j,:] = img[j,:] - oc1[j]
+ elif ch < 8:
+ img = imgdata[0:4616,(ch*1250+71):((ch+1)*1250-27)]
+ oc1 = np.median(imgdata[0:4616,(ch*1250):(ch*1250+71)], axis=1).astype(float)
+ for j in range(4616):
+ img[j, :] = img[j,:] - oc1[j]
+ elif ch < 12:
+ img = imgdata[4784:,((15-ch)*1250+71):((16-ch)*1250-27)]
+ oc2 = np.median(imgdata[4784:,((15-ch)*1250):((15-ch)*1250+71)], axis=1).astype(float)
+ for j in range(4616):
+ img[j,:] = img[j,:] - oc2[j]
+ else:
+ img = imgdata[4784:,((15-ch)*1250+27):((16-ch)*1250-71)]
+ oc2 = np.median(imgdata[4784:,((16-ch)*1250-71):((16-ch)*1250)], axis=1).astype(float)
+ for j in range(4616):
+ img[j,:] = img[j,:]- oc2[j]
+
+ return img
+
+def bindata(image, n):
+ ny, nx = image.shape
+ nx2 = nx // n
+ x_res = nx - nx2 * n
+ x1 = x_res // 2
+ x2 = x_res - x1
+ ny2 = ny // n
+ y_res = ny - ny2 * n
+ y1 = y_res // 2
+ y2 = y_res - y1
+ data = image[y1:ny-y2, x1:nx-x2]
+ # data = data.reshape((ny2, n, nx2, n)).sum(axis=1).sum(axis=-1)
+ data = data.reshape((ny2, n, nx2, n)).mean(axis=1).mean(axis=-1)
+ return data
+
+#blist = glob.glob("fits/bias/*.fits")
+
+#bias = None
+#bcnt = 0
+#for i in range(len(blist)):
+# if i==0:
+# bias = fits.getdata(blist[i]).astype(float)
+# else:
+# bias += fits.getdata(blist[i]).astype(float)
+
+# bcnt += 1
+
+#bias = bias / bcnt
+
+
+def processPTC(fits_dir, out_dir):
+
+ if os.path.exists(out_dir) is False:
+ os.system(f'mkdir {out_dir}')
+
+ ftable = f'{out_dir}/flat_filelist.tab'
+ fdata = f'{out_dir}/ptcdata.tab'
+
+ if os.path.exists(ftable) is False:
+ flist = glob.glob(f'{fits_dir}/*ptc*.fits')
+ t_exp = []
+ seq = []
+ for f in flist:
+ name = os.path.basename(f)
+ tmp = name.split('_')
+# print(tmp)
+ t_exp.append(float(tmp[-4].replace('s', '')))
+ seq.append(int(tmp[-2].replace('.fits', '')))
+ tab = Table()
+ tab['name'] = flist
+ tab['t_exp'] = t_exp
+ tab['seq'] = seq
+ tab.sort(['t_exp', 'seq'])
+ tab.write(ftable, format='ipac', overwrite=True)
+
+# print('#########')
+# sys.exit(0)
+
+ clip_n = 4
+ tab = Table.read(ftable, format='ipac')
+ if os.path.exists(fdata) is False:
+ t_exp = np.unique(tab['t_exp'])
+ t_exp = t_exp[t_exp < 35]
+ t = []
+ chans = []
+ flux_1 = []
+ var_1 = []
+ flux_2 = []
+ var_2 = []
+ flux_4 = []
+ var_4 = []
+ flux_8 = []
+ var_8 = []
+ for chan in range(16):
+ for i in trange(len(t_exp), desc='channel {}'.format(chan+1)):
+ index = tab['t_exp'] == t_exp[i]
+ f1, f2 = tab['name'][index][0], tab['name'][index][1]
+ with fits.open(f1) as img1, fits.open(f2) as img2:
+ t.append(t_exp[i])
+ chans.append(chan+1)
+ # m1 = img1[0].data.astype(float)[:4617, chan*1250+72:chan*1250+1224]
+ # m2 = img2[0].data.astype(float)[:4617, chan*1250+72:chan*1250+1224]
+
+ # if chan < 4:
+ # m1 = img1[0].data.astype(float)[2:4617, chan*1250+30:chan*1250+30+1170] - bias[2:4617, chan*1250+30:chan*1250+30+1170]*0
+ # m2 = img2[0].data.astype(float)[2:4617, chan*1250+30:chan*1250+30+1170] - bias[2:4617, chan*1250+30:chan*1250+30+1170]*0
+ # elif chan >= 4 and chan < 8:
+ # m1 = img1[0].data.astype(float)[2:4617, chan*1250+72:chan*1250+72+1170] - bias[2:4617, chan*1250+72:chan*1250+72+1170]*0
+ # m2 = img2[0].data.astype(float)[2:4617, chan*1250+72:chan*1250+72+1170] - bias[2:4617, chan*1250+72:chan*1250+72+1170]*0
+ # elif chan >= 8 and chan < 12:
+ # m1 = img1[0].data.astype(float)[4785:, (15-chan)*1250+72:(15-chan)*1250+72+1170] - bias[4785:, (15-chan)*1250+72:(15-chan)*1250+72+1170]*0
+ # m2 = img2[0].data.astype(float)[4785:, (15-chan)*1250+72:(15-chan)*1250+72+1170] - bias[4785:, (15-chan)*1250+72:(15-chan)*1250+72+1170]*0
+ # else:
+ # m1 = img1[0].data.astype(float)[4785:, (15-chan)*1250+30:(15-chan)*1250+30+1170] - bias[4785:, (15-chan)*1250+30:(15-chan)*1250+30+1170]*0
+ # m2 = img2[0].data.astype(float)[4785:, (15-chan)*1250+30:(15-chan)*1250+30+1170] - bias[4785:, (15-chan)*1250+30:(15-chan)*1250+30+1170]*0
+
+# if chan < 4:
+# m1 = img1[0].data.astype(float)[2:4617, chan*1250+30:chan*1250+30+1170] #- bias[2:4617, chan*1250+30:chan*1250+30+1170]*0
+# m2 = img2[0].data.astype(float)[2:4617, chan*1250+30:chan*1250+30+1170] #- bias[2:4617, chan*1250+30:chan*1250+30+1170]*0
+# elif chan >= 4 and chan < 8:
+# m1 = img1[0].data.astype(float)[2:4617, chan*1250+72:chan*1250+72+1170] #- bias[2:4617, chan*1250+72:chan*1250+72+1170]*0
+# m2 = img2[0].data.astype(float)[2:4617, chan*1250+72:chan*1250+72+1170] #- bias[2:4617, chan*1250+72:chan*1250+72+1170]*0
+# elif chan >= 8 and chan < 12:
+# m1 = img1[0].data.astype(float)[4785:, (15-chan)*1250+72:(15-chan)*1250+72+1170] #- bias[4785:, (15-chan)*1250+72:(15-chan)*1250+72+1170]*0
+# m2 = img2[0].data.astype(float)[4785:, (15-chan)*1250+72:(15-chan)*1250+72+1170] #- bias[4785:, (15-chan)*1250+72:(15-chan)*1250+72+1170]*0
+# else:
+# m1 = img1[0].data.astype(float)[4785:, (15-chan)*1250+30:(15-chan)*1250+30+1170] #- bias[4785:, (15-chan)*1250+30:(15-chan)*1250+30+1170]*0
+# m2 = img2[0].data.astype(float)[4785:, (15-chan)*1250+30:(15-chan)*1250+30+1170] #- bias[4785:, (15-chan)*1250+30:(15-chan)*1250+30+1170]*0
+
+ m1 = remove_overscan(img1[0].data, chan)
+ m2 = remove_overscan(img2[0].data, chan)
+
+ flux_1.append(np.median(m1 + m2) / 2)
+ var_1.append(sigmaclip(m1 - m2, clip_n, clip_n)[0].var() / 2)
+
+ mm1 = bindata(m1, 2)
+ mm2 = bindata(m2, 2)
+ flux_2.append(np.median(mm1 + mm2) / 2)
+ var_2.append(sigmaclip(mm1 - mm2, clip_n, clip_n)[0].var() / 2 * 4)
+
+ mm1 = bindata(m1, 4)
+ mm2 = bindata(m2, 4)
+ flux_4.append(np.median(mm1 + mm2) / 2)
+ var_4.append(sigmaclip(mm1 - mm2, clip_n, clip_n)[0].var() / 2 * 16)
+
+ mm1 = bindata(m1, 8)
+ mm2 = bindata(m2, 8)
+ flux_8.append(np.median(mm1 + mm2) / 2)
+ var_8.append(sigmaclip(mm1 - mm2, clip_n, clip_n)[0].var() / 2 * 64)
+
+ cat = Table()
+ cat['t_exp'] = t
+ cat['channel'] = chans
+ cat['flux_1'] = flux_1
+ cat['var_1'] = var_1
+ cat['flux_2'] = flux_2
+ cat['var_2'] = var_2
+ cat['flux_4'] = flux_4
+ cat['var_4'] = var_4
+ cat['flux_8'] = flux_8
+ cat['var_8'] = var_8
+ cat.sort(['channel', 't_exp'])
+ cat.write(fdata, format='ipac', overwrite=True)
+
+
+ cat = Table.read(fdata, format='ipac')
+ for chan in np.unique(cat['channel']):
+ index = cat['channel'] == chan
+ ind = np.argmin(cat['t_exp'][index])
+ cat['flux_1'][index] = cat['flux_1'][index] - cat['flux_1'][index][ind]
+ cat['var_1'][index] = cat['var_1'][index] - cat['var_1'][index][ind]
+ cat['flux_2'][index] = cat['flux_2'][index] - cat['flux_2'][index][ind]
+ cat['var_2'][index] = cat['var_2'][index] - cat['var_2'][index][ind]
+ cat['flux_4'][index] = cat['flux_4'][index] - cat['flux_4'][index][ind]
+ cat['var_4'][index] = cat['var_4'][index] - cat['var_4'][index][ind]
+ cat['flux_8'][index] = cat['flux_8'][index] - cat['flux_8'][index][ind]
+ cat['var_8'][index] = cat['var_8'][index] - cat['var_8'][index][ind]
+ index = cat['t_exp'] > 0
+ cat = cat[index]
+
+ model = odr.polynomial(1)
+ plt.figure(figsize=(20, 40))
+ output_gains = []
+ output_chans = []
+ output_bins = []
+ for chan in range(16):
+ index = cat['channel'] == chan+1
+ bins = ['1', '2', '4', '8']
+ colors = ['red', 'orange', 'blue', 'green']
+ handlers = []
+ txts = []
+ row = chan // 4
+ pos1 = row * 8 + chan - row * 4 + 1
+ pos2 = pos1 + 4
+ for i in range(4):
+ if i != 2:
+ pass
+ else:
+ x = cat['flux_'+bins[i]][index]
+ y = cat['var_'+bins[i]][index]
+ print('x = ', x)
+ print('y = ', y)
+ idx = x > 0
+ x = x[idx]
+ y = y[idx]
+ fw = x[np.argmax(y)]
+ ind = (x > fw * 0.1) * (x < fw * 0.8)
+ data = odr.Data(x[ind], y[ind], we=1/(x[ind]**2), wd=1/(y[ind]**2))
+ fitter = odr.ODR(data, model, ifixb=[0, 1], beta0=[0, 1])
+ res = fitter.run()
+ poly = np.poly1d(res.beta[::-1])
+ nonlin = (y - poly(x)) / poly(x) * 100
+
+ plt.subplot(8, 4, pos1)
+ plt.scatter(x[~ind], y[~ind], color=colors[i], zorder=1, alpha=0.25, edgecolors='none', s=20)
+ s = plt.scatter(x[ind], y[ind], color=colors[i], zorder=2, edgecolors='none', s=20)
+ l, = plt.plot(x, poly(x), color=colors[i], zorder=3, ls='--')
+ handlers.append(s)
+ txts.append('bin {}: gain = {:.3f} e-/DN'.format(bins[i], 1/res.beta[1]))
+
+ output_gains.append(1/res.beta[1])
+ output_chans.append(chan+1)
+ output_bins.append(bins[i])
+
+ plt.subplot(8, 4, pos2)
+ plt.scatter(x[~ind], nonlin[~ind], color=colors[i], zorder=2, alpha=0.25, edgecolors='none', s=20)
+ plt.scatter(x[ind], nonlin[ind], color=colors[i], zorder=3, edgecolors='none', s=20)
+ plt.plot(x, np.zeros(len(x)), color='grey', zorder=1)
+
+
+# x = cat['flux_'+bins[i]][index]
+# y = cat['var_'+bins[i]][index]
+# print('x = ', x)
+# print('y = ', y)
+# idx = x > 0
+# x = x[idx]
+# y = y[idx]
+# fw = x[np.argmax(y)]
+# ind = (x > fw * 0.1) * (x < fw * 0.8)
+# data = odr.Data(x[ind], y[ind], we=1/(x[ind]**2), wd=1/(y[ind]**2))
+# fitter = odr.ODR(data, model, ifixb=[0, 1], beta0=[0, 1])
+# res = fitter.run()
+# poly = np.poly1d(res.beta[::-1])
+# nonlin = (y - poly(x)) / poly(x) * 100
+
+# plt.subplot(8, 4, pos1)
+# plt.scatter(x[~ind], y[~ind], color=colors[i], zorder=1, alpha=0.25, edgecolors='none', s=20)
+# s = plt.scatter(x[ind], y[ind], color=colors[i], zorder=2, edgecolors='none', s=20)
+# l, = plt.plot(x, poly(x), color=colors[i], zorder=3, ls='--')
+# handlers.append(s)
+# txts.append('bin {}: gain = {:.3f} e-/DN'.format(bins[i], 1/res.beta[1]))
+
+# output_gains.append(1/res.beta[1])
+# output_chans.append(chan+1)
+# output_bins.append(bins[i])
+
+# plt.subplot(8, 4, pos2)
+# plt.scatter(x[~ind], nonlin[~ind], color=colors[i], zorder=2, alpha=0.25, edgecolors='none', s=20)
+# plt.scatter(x[ind], nonlin[ind], color=colors[i], zorder=3, edgecolors='none', s=20)
+# plt.plot(x, np.zeros(len(x)), color='grey', zorder=1)
+
+ plt.subplot(8, 4, pos1)
+ plt.xlabel('flux (DN)')
+ plt.ylabel('variance (DN^2)')
+ plt.title('channel {}'.format(chan+1))
+ # plt.text(4e4,6e3,'channel {}'.format(chan+1), color='limegreen', fontsize=14)
+ plt.legend(handlers, txts)
+ plt.grid(True)
+
+ plt.subplot(8, 4, pos2)
+ # plt.xlabel('flux (ADU)')
+ plt.xlabel('flux (DN)')
+ plt.ylabel('non-linearity (%)')
+ plt.title('channel {}'.format(chan+1))
+ # plt.text(4e4,8,'channel {}'.format(chan+1), color='brown', fontsize=14)
+ plt.ylim([-10, 10])
+ plt.grid(True)
+
+ plt.tight_layout()
+ plt.savefig(f'{out_dir}/ptc.pdf')
+ plt.close()
+ tab = Table()
+ tab['channel'] = output_chans
+ tab['bin'] = output_bins
+ tab['gain'] = output_gains
+ tab.write(f'{out_dir}/gain.tab', format='ipac', overwrite=True)
+
+
+if __name__ == '__main__':
+ processPTC(sys.argv[1], sys.argv[2])
+
diff --git a/qtCreatorVer/util/remove_overscan.py b/qtCreatorVer/util/remove_overscan.py
new file mode 100644
index 0000000..887220f
--- /dev/null
+++ b/qtCreatorVer/util/remove_overscan.py
@@ -0,0 +1,55 @@
+import os, sys
+import time
+from astropy.io import fits
+import numpy as np
+from scipy.stats import sigmaclip
+from astropy.table import Table
+
+
+def remove_overscan(imgdata):
+ print('> doing overscan correction ...')
+ img = np.zeros((9232,9216), dtype=float)
+
+ for i in range(4):
+ img[0:4616, i*1152:(i+1)*1152] = imgdata[0:4616,(i*1250+27):((i+1)*1250-71)]
+ img[4616:, i*1152:(i+1)*1152] = imgdata[4784:,(i*1250+27):((i+1)*1250-71)]
+ oc1 = np.mean(imgdata[0:4616,((i+1)*1250-71):((i+1)*1250)], axis=1).astype(float)
+ oc2 = np.mean(imgdata[4784:,((i+1)*1250-71):((i+1)*1250)], axis=1).astype(float)
+# print('oc1.shape: {}'.format(oc1.shape))
+ for j in range(4616):
+ img[j,i*1152:(i+1)*1152] = img[j,i*1152:(i+1)*1152] - oc1[j]
+ img[4616+j,i*1152:(i+1)*1152] = img[4616+j,i*1152:(i+1)*1152]- oc2[j]
+ for i in range(4,8):
+ img[0:4616, i*1152:(i+1)*1152] = imgdata[0:4616,(i*1250+71):((i+1)*1250-27)]
+ img[4616:, i*1152:(i+1)*1152] = imgdata[4784:,(i*1250+71):((i+1)*1250-27)]
+ oc1 = np.mean(imgdata[0:4616,(i*1250):(i*1250+71)], axis=1).astype(float)
+ oc2 = np.mean(imgdata[4784:,(i*1250):(i*1250+71)], axis=1).astype(float)
+ for j in range(4616):
+ img[j, i*1152:(i+1)*1152] = img[j, i*1152:(i+1)*1152] - oc1[j]
+ img[4616+j,i*1152:(i+1)*1152] = img[4616+j,i*1152:(i+1)*1152] - oc2[j]
+
+ print('> overscan correction done!')
+ return img
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print('用法: python remove_overscan.py 输入FITS文件 [输出目录]')
+ sys.exit(1)
+ in_fits = sys.argv[1]
+ if not os.path.isfile(in_fits):
+ print(f'输入文件不存在: {in_fits}')
+ sys.exit(1)
+ if len(sys.argv) > 2:
+ out_dir = sys.argv[2]
+ else:
+ out_dir = os.path.dirname(in_fits)
+ os.makedirs(out_dir, exist_ok=True)
+ base = os.path.splitext(os.path.basename(in_fits))[0]
+ out_fits = os.path.join(out_dir, f'{base}_overscan_corrected.fits')
+
+ with fits.open(in_fits) as hdul:
+ imgdata = hdul[0].data
+ header = hdul[0].header
+ img_corr = remove_overscan(imgdata)
+ fits.writeto(out_fits, img_corr, header, overwrite=True)
+ print(f'> 修正后文件已保存: {out_fits}')
\ No newline at end of file
diff --git a/qtCreatorVer/workers/process_worker.py b/qtCreatorVer/workers/process_worker.py
new file mode 100644
index 0000000..4b38996
--- /dev/null
+++ b/qtCreatorVer/workers/process_worker.py
@@ -0,0 +1,30 @@
+import subprocess
+from PySide6.QtCore import QThread, Signal
+
+class ProcessWorker(QThread):
+ output_signal = Signal(str)
+ finished_signal = Signal(int)
+
+ def __init__(self, cmd, cwd=None):
+ super().__init__()
+ self.cmd = cmd
+ self.cwd = cwd
+
+ def run(self):
+ try:
+ proc = subprocess.Popen(
+ self.cmd,
+ cwd=self.cwd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ bufsize=1
+ )
+ for line in proc.stdout:
+ self.output_signal.emit(line.rstrip())
+ proc.stdout.close()
+ proc.wait()
+ self.finished_signal.emit(proc.returncode)
+ except Exception as e:
+ self.output_signal.emit(f'运行出错: {e}')
+ self.finished_signal.emit(-1)
\ No newline at end of file
--
Gitee