コード例 #1
0
class AnalyzeTab(QWidget):
    def __init__(self, ctx):
        super().__init__()
        self.ctx = ctx
        self.bins = 20
        self.initVar()
        self.initModel()
        self.initUI()
        self.set_axis_opts()
        self.set_filter()
        self.sigConnect()
        self.apply_filter()

    def initVar(self):
        self.x_opts = [
            'Record ID', 'CTDIvol', 'Age', 'Deff', 'Dw', 'SSDE',
            'Effective Dose', 'DLP', 'DLPc'
        ]
        self.x_units = [
            '', 'mGy', 'Year', 'cm', 'cm', 'mGy', 'mSv', 'mGy-cm', 'mGy-cm'
        ]
        self.y_opts = [
            'CTDIvol', 'Age', 'Deff', 'Dw', 'SSDE', 'Effective Dose', 'DLP',
            'DLPc', 'Frequency'
        ]
        self.y_units = [
            'mGy', 'Year', 'cm', 'cm', 'mGy', 'mSv', 'mGy-cm', 'mGy-cm', ''
        ]

        self.age_ftr1 = -1
        self.age_ftr2 = -1
        self.date_ftr1 = QDate.currentDate()
        self.date_ftr2 = QDate.currentDate()
        self.brand_ftr = 'All'
        self.instn_ftr = 'All'
        self.protocol_ftr = 'All'
        self.scanner_ftr = 'All'
        self.sex_ftr = 'All'

        self.x_opt = self.x_opts[0]
        self.y_opt = self.y_opts[0]

    def initModel(self):
        self.query_model = QSqlQueryModel()
        self.data_query_model = QSqlQueryModel()

    def sigConnect(self):
        self.x_cb.activated[str].connect(self.on_x_changed)
        self.y_cb.activated[str].connect(self.on_y_changed)
        self.sex_cb.activated[int].connect(self.on_sex_changed)
        self.protocol_cb.activated[int].connect(self.on_protocol_changed)
        self.instn_cb.activated[int].connect(self.on_instn_changed)
        self.brand_cb.activated[int].connect(self.on_brand_changed)
        self.scanner_cb.activated[int].connect(self.on_scanner_changed)
        self.age_sb1.valueChanged.connect(self.on_age1_changed)
        self.age_sb2.valueChanged.connect(self.on_age2_changed)
        self.date_edit1.dateChanged.connect(self.on_date1_changed)
        self.date_edit2.dateChanged.connect(self.on_date2_changed)
        self.bins_sb.valueChanged.connect(self.on_bins_changed)
        self.generate_btn.clicked.connect(self.on_generate)
        self.reset_btn.clicked.connect(self.set_filter)

    def initUI(self):
        self.figure = PlotDialog()
        self.x_cb = QComboBox()
        self.y_cb = QComboBox()
        self.sex_cb = QComboBox()
        self.protocol_cb = QComboBox()
        self.age_sb1 = QSpinBox()
        self.age_sb2 = QSpinBox()
        self.date_edit1 = QDateEdit()
        self.date_edit2 = QDateEdit()
        self.generate_btn = QPushButton('Generate')
        self.bins_sb = QSpinBox()
        self.bins_lbl = QLabel('Bins')
        self.reset_btn = QPushButton('Reset Filter')
        self.brand_cb = QComboBox()
        self.scanner_cb = QComboBox()
        self.instn_cb = QComboBox()
        self.data_count_lbl = QLabel(str(self.data_query_model.rowCount()))

        self.age_sb1.setSpecialValueText('-')
        self.age_sb1.setRange(-1, -1)
        self.age_sb1.setMinimumWidth(90)
        self.age_sb2.setSpecialValueText('-')
        self.age_sb2.setRange(-1, -1)
        self.age_sb2.setMinimumWidth(90)
        self.date_edit1.setDisplayFormat('dd/MM/yyyy')
        self.date_edit1.setMinimumWidth(90)
        self.date_edit2.setDisplayFormat('dd/MM/yyyy')
        self.date_edit2.setMinimumWidth(90)
        self.bins_sb.setMinimum(1)
        self.bins_sb.setValue(self.bins)
        self.bins_sb.setVisible(False)
        self.bins_lbl.setVisible(False)

        age_layout = QHBoxLayout()
        age_layout.addWidget(self.age_sb1)
        age_layout.addWidget(QLabel('to'))
        age_layout.addWidget(self.age_sb2)
        age_layout.addStretch()

        date_layout = QHBoxLayout()
        date_layout.addWidget(self.date_edit1)
        date_layout.addWidget(QLabel('to'))
        date_layout.addWidget(self.date_edit2)
        date_layout.addStretch()

        self.axis_grpbox = QGroupBox('Axis selection')
        ax_layout = QFormLayout()
        ax_layout.addRow(QLabel('x-axis'), self.x_cb)
        ax_layout.addRow(QLabel('y-axis'), self.y_cb)
        ax_layout.addRow(self.bins_lbl, self.bins_sb)
        ax_layout.addWidget(self.generate_btn)
        self.axis_grpbox.setLayout(ax_layout)

        self.filter_grpbox = QGroupBox('Filter')
        flt_layout = QFormLayout()
        flt_layout.addRow(QLabel('Institution'), self.instn_cb)
        flt_layout.addRow(QLabel('Manufacturer'), self.brand_cb)
        flt_layout.addRow(QLabel('Scanner'), self.scanner_cb)
        flt_layout.addRow(QLabel('Protocol'), self.protocol_cb)
        flt_layout.addRow(QLabel('Sex'), self.sex_cb)
        flt_layout.addRow(QLabel('Age'), age_layout)
        flt_layout.addRow(QLabel('Exam Date'), date_layout)
        flt_layout.addRow(QLabel('Data Count'), self.data_count_lbl)
        flt_layout.addWidget(self.reset_btn)
        self.filter_grpbox.setLayout(flt_layout)

        mainlayout = QHBoxLayout()
        mainlayout.addWidget(self.filter_grpbox)
        mainlayout.addWidget(self.axis_grpbox)
        self.setLayout(mainlayout)

    def set_filter(self):
        self.set_instn()
        self.set_brand()
        self.set_scanner()
        self.set_protocol()
        self.set_sex()
        self.set_age_range()
        self.set_date_range()

    def set_instn(self):
        sql = "SELECT DISTINCT institution FROM PATIENTS"
        self.query_model.setQuery(sql, self.ctx.database.patient_db)
        self.instns = [
            self.query_model.record(idx).value('Institution')
            for idx in range(self.query_model.rowCount())
        ]
        try:
            self.instns[self.instns.index('')] = 'Unspecified'
        except:
            pass
        self.instns.insert(0, 'All')
        self.instn_cb.clear()
        self.instn_cb.addItems(self.instns)
        self.on_instn_changed(0)

    def set_brand(self):
        sql = "SELECT DISTINCT manufacturer FROM PATIENTS"
        self.query_model.setQuery(sql, self.ctx.database.patient_db)
        self.brands = [
            self.query_model.record(idx).value('Manufacturer')
            for idx in range(self.query_model.rowCount())
        ]
        try:
            self.brands[self.brands.index('')] = 'Unspecified'
        except:
            pass
        self.brands.insert(0, 'All')
        self.brand_cb.clear()
        self.brand_cb.addItems(self.brands)
        self.on_brand_changed(0)

    def set_scanner(self, filter=None):
        if filter is not None:
            sql = f'SELECT DISTINCT model FROM PATIENTS WHERE manufacturer="{filter}"'
            self.query_model.setQuery(sql, self.ctx.database.patient_db)
            self.scanners = [
                self.query_model.record(idx).value('Model')
                for idx in range(self.query_model.rowCount())
            ]
            try:
                self.scanners[self.scanners.index('')] = 'Unspecified'
            except:
                print('gagal')
        else:
            self.scanners = ['Unspecified']
        self.scanners.insert(0, 'All')
        self.scanner_cb.clear()
        self.scanner_cb.addItems(self.scanners)
        self.on_scanner_changed(0)

    def set_protocol(self):
        sql = "SELECT DISTINCT protocol FROM PATIENTS"
        self.query_model.setQuery(sql, self.ctx.database.patient_db)
        self.protocols = [
            self.query_model.record(idx).value('Protocol')
            for idx in range(self.query_model.rowCount())
        ]
        try:
            self.protocols[self.protocols.index('')] = 'Unspecified'
        except:
            pass
        self.protocols.insert(0, 'All')
        self.protocol_cb.clear()
        self.protocol_cb.addItems(self.protocols)
        self.on_protocol_changed(0)

    def set_sex(self):
        sql = "SELECT DISTINCT sex FROM PATIENTS"
        self.query_model.setQuery(sql, self.ctx.database.patient_db)
        self.sexes = [
            self.query_model.record(idx).value('Sex')
            for idx in range(self.query_model.rowCount())
        ]
        try:
            self.sexes[self.sexes.index('')] = 'Unspecified'
        except:
            pass
        self.sexes.insert(0, 'All')
        self.sex_cb.clear()
        self.sex_cb.addItems(self.sexes)
        self.on_sex_changed(0)

    def set_age_range(self):
        sql = "SELECT MAX(age) as max FROM PATIENTS"
        self.query_model.setQuery(sql, self.ctx.database.patient_db)
        try:
            age_max = int(self.query_model.record(0).value('max'))
        except:
            age_max = -1

        self.age_sb1.setRange(-1, age_max)
        self.age_sb1.setValue(-1)
        self.age_sb2.setRange(-1, age_max)
        self.age_sb2.setValue(-1)
        self.age_ftr1 = -1
        self.age_ftr2 = -1

    def set_date_range(self):
        sql = "SELECT MAX(exam_date) as max FROM PATIENTS"
        self.query_model.setQuery(sql, self.ctx.database.patient_db)
        try:
            date_max = QDate.fromString(
                self.query_model.record(0).value('max'), 'yyyyMMdd')
        except:
            date_max = QDate.currentDate()

        sql = "SELECT MIN(exam_date) as min FROM PATIENTS"
        self.query_model.setQuery(sql, self.ctx.database.patient_db)
        try:
            date_min = QDate.fromString(
                self.query_model.record(0).value('min'), 'yyyyMMdd')
        except:
            date_min = QDate(2000, 1, 1)

        self.date_edit1.setDate(date_min)
        self.date_edit2.setDate(date_max)
        self.date_ftr1 = date_min
        self.date_ftr2 = date_max

    def set_axis_opts(self):
        sql = "SELECT * FROM PATIENTS LIMIT 1"
        self.query_model.setQuery(sql, self.ctx.database.patient_db)
        self.x_cb.clear()
        self.y_cb.clear()
        self.x_cb.addItems(self.x_opts)
        self.y_cb.addItems(self.y_opts)
        self.on_x_changed(self.x_opts[0])
        self.on_y_changed(self.y_opts[0])

    def on_x_changed(self, sel):
        if sel == 'Dw' or sel == 'Deff':
            dw = self.y_cb.findText('Dw')
            de = self.y_cb.findText('Deff')
            self.y_cb.removeItem(dw)
            self.y_cb.removeItem(de)
        else:
            y_txt = self.y_cb.currentText()
            self.y_cb.clear()
            self.y_cb.addItems(self.y_opts)
            self.y_cb.setCurrentText(y_txt)
        unit_idx = self.x_opts.index(sel)
        self.x_unit = self.x_units[unit_idx]
        self.x_opt = sel
        self.apply_filter()

    def on_y_changed(self, sel):
        if sel == 'Frequency':
            self.bins_lbl.setVisible(True)
            self.bins_sb.setVisible(True)
        else:
            self.bins_lbl.setVisible(False)
            self.bins_sb.setVisible(False)
        if sel == 'Dw' or sel == 'Deff':
            dw = self.x_cb.findText('Dw')
            de = self.x_cb.findText('Deff')
            self.x_cb.removeItem(dw)
            self.x_cb.removeItem(de)
        else:
            x_txt = self.x_cb.currentText()
            self.x_cb.clear()
            self.x_cb.addItems(self.x_opts)
            self.x_cb.setCurrentText(x_txt)
        unit_idx = self.y_opts.index(sel)
        self.y_unit = self.y_units[unit_idx]
        self.y_opt = sel
        self.apply_filter()

    def on_instn_changed(self, idx):
        self.instn_ftr = self.instns[idx]
        self.apply_filter()

    def on_brand_changed(self, idx):
        self.brand_ftr = self.brands[idx]
        self.set_scanner(self.brand_ftr if idx != 0 else None)
        self.apply_filter()

    def on_scanner_changed(self, idx):
        self.scanner_ftr = self.scanners[idx]
        self.apply_filter()

    def on_sex_changed(self, idx):
        self.sex_ftr = self.sexes[idx]
        self.apply_filter()

    def on_protocol_changed(self, idx):
        self.protocol_ftr = self.protocols[idx]
        self.apply_filter()

    def on_age1_changed(self):
        self.age_ftr1 = self.age_sb1.value()
        self.apply_filter()

    def on_age2_changed(self):
        self.age_ftr2 = self.age_sb2.value()
        self.apply_filter()

    def on_date1_changed(self):
        self.date_ftr1 = self.date_edit1.date()
        self.apply_filter()

    def on_date2_changed(self):
        self.date_ftr2 = self.date_edit2.date()
        self.apply_filter()

    def on_bins_changed(self):
        self.bins = self.bins_sb.value()
        self.apply_filter()

    def get_data(self):
        sql = f"SELECT {self.x_name}, {self.y_name} FROM PATIENTS WHERE {self.filter} ORDER BY {self.x_name}"
        self.data_query_model.setQuery(sql, self.ctx.database.patient_db)
        self.x_data = np.array([
            self.data_query_model.record(n).value(self.x_name)
            for n in range(self.data_query_model.rowCount())
        ])

        if self.y_opt != 'Frequency':
            self.y_data = np.array([
                self.data_query_model.record(n).value(self.y_name)
                for n in range(self.data_query_model.rowCount())
            ])

    def apply_sex_filter(self):
        if self.sex_ftr != 'All':
            if self.filter:
                self.filter += ' AND '
            if self.sex_ftr == 'Unspecified':
                self.filter += 'sex is NULL'
            else:
                self.filter += f'sex="{self.sex_ftr}"'

    def apply_protocol_filter(self):
        if self.protocol_ftr != 'All':
            if self.filter:
                self.filter += ' AND '
            if self.protocol_ftr == 'Unspecified':
                self.filter += 'protocol is NULL'
            else:
                self.filter += f'protocol="{self.protocol_ftr}"'

    def apply_instn_filter(self):
        if self.instn_ftr != 'All':
            if self.filter:
                self.filter += ' AND '
            if self.instn_ftr == 'Unspecified':
                self.filter += 'institution is NULL'
            else:
                self.filter += f'institution="{self.instn_ftr}"'

    def apply_brand_filter(self):
        if self.brand_ftr != 'All':
            if self.filter:
                self.filter += ' AND '
            if self.brand_ftr == 'Unspecified':
                self.filter += 'manufacturer is NULL'
            else:
                self.filter += f'manufacturer="{self.brand_ftr}"'

    def apply_scanner_filter(self):
        if self.scanner_ftr != 'All':
            if self.filter:
                self.filter += ' AND '
            if self.scanner_ftr == 'Unspecified':
                self.filter += 'model is NULL'
            else:
                self.filter += f'model="{self.scanner_ftr}"'

    def apply_age_filter(self):
        if self.age_ftr1 != -1 and self.age_ftr2 != -1:
            if self.age_ftr1 <= self.age_ftr2:
                lo_age = self.age_ftr1
                hi_age = self.age_ftr2
            else:
                lo_age = self.age_ftr2
                hi_age = self.age_ftr1
            if self.filter:
                self.filter += ' AND '
            self.filter += f'age BETWEEN {lo_age} and {hi_age}'

    def apply_date_filter(self):
        if self.filter:
            self.filter += ' AND '
        if self.date_ftr1.toString('yyyyMMdd') <= self.date_ftr2.toString(
                'yyyyMMdd'):
            lo_date = self.date_ftr1.toString('yyyyMMdd')
            hi_date = self.date_ftr2.toString('yyyyMMdd')
        else:
            lo_date = self.date_ftr2.toString('yyyyMMdd')
            hi_date = self.date_ftr1.toString('yyyyMMdd')
        self.filter += f'exam_date BETWEEN {lo_date} and {hi_date}'

    def set_x_data(self):
        if self.x_opt == 'Record ID':
            self.x_name = 'id'
        elif self.x_opt == 'Effective Dose':
            self.x_name = 'effective_dose'
        elif self.x_opt == 'Deff' or self.x_opt == 'Dw':
            self.x_name = 'diameter'
            if self.filter:
                self.filter += ' AND '
            self.filter += f'diameter_type="{self.x_opt}"'
        else:
            self.x_name = self.x_opt

    def set_y_data(self):
        if self.y_opt == 'Effective Dose':
            self.y_name = 'effective_dose'
        elif self.y_opt == 'Deff' or self.y_opt == 'Dw':
            self.y_name = 'diameter'
            if self.filter:
                self.filter += ' AND '
            self.filter += f'diameter_type="{self.y_opt}"'
        elif self.y_opt == 'Frequency':
            self.y_name = 'NULL'
        else:
            self.y_name = self.y_opt

        if self.filter:
            self.filter += ' AND '
        if self.y_opt != 'Frequency':
            self.filter += f'{self.x_name} is NOT NULL AND {self.y_name} is NOT NULL'
        else:
            self.filter += f'{self.x_name} is NOT NULL'

    def apply_filter(self):
        self.filter = ''
        self.apply_sex_filter()
        self.apply_protocol_filter()
        self.apply_instn_filter()
        self.apply_brand_filter()
        self.apply_scanner_filter()
        self.apply_age_filter()
        self.apply_date_filter()

        self.set_x_data()
        self.set_y_data()
        self.get_data()
        self.data_count_lbl.setText(str(self.data_query_model.rowCount()))

    def plot(self):
        self.figure = PlotDialog()
        self.figure.axes.addLegend(pen='w', brush=(64, 64, 64, 127))
        self.figure.actionEnabled(True)
        if self.y_opt == 'Frequency':
            self.figure.trendActionEnabled(False)
            self.figure.histogram(self.x_data,
                                  bins=self.bins,
                                  name=f'{self.y_opt}',
                                  fillLevel=0,
                                  brush=(0, 0, 255, 150),
                                  symbol='o',
                                  symbolSize=5)
        else:
            self.figure.plot(self.x_data,
                             self.y_data,
                             name='data points',
                             pen=None,
                             symbol='o',
                             symbolSize=8,
                             symbolPen='k',
                             symbolBrush=(255, 255, 0, 255))
        self.figure.axes.showGrid(True, True)
        self.figure.setLabels(self.x_opt, self.y_opt, self.x_unit, self.y_unit)
        self.figure.setTitle(f'{self.x_opt} - {self.y_opt}')
        self.figure.show()

    def on_generate(self):
        print(self.filter)
        isempty = lambda arr: arr.size == 0
        if isempty(self.x_data) or isempty(self.y_data):
            QMessageBox.information(
                None, "No Data",
                "Matching data not found.\nPlease try to reduce the filter.")
            return
        self.plot()

    def reset_fields(self):
        self.set_filter()
        self.bins_sb.setValue(20)
        self.x_cb.setCurrentIndex(0)
        self.y_cb.setCurrentIndex(0)
        self.on_bins_changed()
        self.on_x_changed(self.x_cb.currentText)
        self.on_y_changed(self.x_cb.currentText)