class KeyMonitor(QtCore.QObject): keyPressed = QtCore.pyqtSignal(object) def __init__(self, parent=None): super().__init__(parent) self.listener = Listener(on_press=self.on_press, on_release=self.on_release) self.currentKey = None self.released = False print("Monitor init!") def on_press(self, key): self.released = False self.currentKey = key self.keyPressed.emit(key) def on_release(self, key): self.currentKey = None self.released = True def stop_monitoring(self): self.listener.stop() self.deleteLater() def start_monitoring(self): self.listener.start()
class LineText(QtWidgets.QLineEdit): update = QtCore.pyqtSignal(str, str) def __init__(self, parent): QtWidgets.QTextEdit.__init__(self, parent) self.timer = QtCore.QTimer() self.timer.setInterval(1000) self.timer.timeout.connect(self.fire) def fire(self): self.update.emit(self.parent().objectName(), self.text()) self.timer.stop() def keyPressEvent(self, event): if self.timer.isActive(): self.timer.stop() self.timer.setInterval(1000) self.timer.start() else: self.timer.start() QtWidgets.QLineEdit.keyPressEvent(self, event) def load_data(self, data): key = self.parent().objectName() if key in data: self.setText(data[key])
class ButtonQLabel(QtWidgets.QLabel): # 自定义信号, 注意信号必须为类属性 button_clicked_signal = QtCore.pyqtSignal() def mouseReleaseEvent(self, me): self.button_clicked_signal.emit() # 可在外部与槽函数连接 def onclick(self, func): self.button_clicked_signal.connect(func)
class Mythread(QtCore.QThread): # 定义信号,定义参数为str类型 signal_one = QtCore.pyqtSignal(int) def __init__(self, parent=None): super().__init__(parent) def run(self): test = HtmlParser("=") print("##########") pass
class KeysWidget(QWidget): keyPressed = QtCore.pyqtSignal(QtCore.QEvent) def __init__(self, parent=None): super(KeysWidget, self).__init__(parent) self.classifyExercises = None if parent is not None: self.classifyExercises = parent.classifyExercises self.infoLabel = parent.infoLabel self.ui = Ui_KeysPanel() self.ui.setupUi(self) self.monitor = KeyMonitor() self.monitor.start_monitoring() self.timer = QTimer() self.timer.timeout.connect(self.onTimeout) self.timer.start() self.ui.saveProfile.clicked.connect(self.saveBindings) def saveBindings(self): if self.classifyExercises.subject is not None: key_list = [x.serialize() for x in self.classifyExercises.exercises.values()] content = { self.classifyExercises.subject: key_list } print(content) with open(MAPPED_KEYS_PATH + self.classifyExercises.subject + '.json', "w") as f: json.dump(content, f) def onTimeout(self): if self.monitor.released: for b in self.ui.buttons: b.setStyleSheet( """ QPushButton { border: 1px solid grey; background-color: white; } """) else: for ind in range(0, len(self.ui.exercises)): if self.monitor.currentKey == self.ui.exercises[ind].assigned_key[1]: self.ui.buttons[ind].setStyleSheet( """ QPushButton { border: 1px solid green; background-color: #7FFFD4; } """)
class TrainThread(QThread): taskFinished = QtCore.pyqtSignal() def __init__(self, classify: ClassifyExercises = None): QThread.__init__(self) self.classify = classify # def __del__(self): # self.wait() def run(self): # your logic here self.classify.TrainEMG() self.taskFinished.emit()
class RecordThread(QThread): taskFinished = QtCore.pyqtSignal() def __init__(self, classify: ClassifyExercises = None, exercise: str = None): QThread.__init__(self) self.classify = classify self.exercise = exercise self.result = None def run(self): # your logic here result = self.classify.RecordExercise(self.exercise) self.taskFinished.emit() self.result = result
class ExceptionHandler(QtCore.QObject): exception = QtCore.pyqtSignal(list) def __init__(self): QtCore.QObject.__init__(self) sys.excepthook = self.handle_exception def handle_exception(self, ex_type, ex_value, ex_traceback): error = [] error.append(ex_type.__name__) for line in traceback.format_tb(ex_traceback): error.append(line) self.exception.emit(error) if DEBUG: for line in error: print(line)
class MetaboliteSimulation(QtCore.QObject): postToConsole = QtCore.pyqtSignal(str) outputResults = QtCore.pyqtSignal(object) finished = QtCore.pyqtSignal(int) def __init__(self, thread_num, insysfile, sim_experiment): QtCore.QObject.__init__(self) self.thread_num = thread_num self.insysfile = insysfile self.sim_experiment = sim_experiment def simulate(self): self.postToConsole.emit(' | Simulating ... ' + self.insysfile) print(' | Simulating ...' + self.insysfile) metab_name = self.insysfile.replace('.sys', '') if self.sim_experiment.b0 == 123.3: self.insysfile = 'pints/metabolites/3T_' + self.insysfile elif self.sim_experiment.b0 == 297.2: self.insysfile = 'pints/metabolites/7T_' + self.insysfile elif self.sim_experiment.b0 == 400.2: self.insysfile = 'pints/metabolites/9.4T_' + self.insysfile if self.sim_experiment.name == "semi-LASER (Bruker)": spin_system = pg.spin_system() spin_system.read(self.insysfile) for i in range(spin_system.spins()): spin_system.PPM( i, spin_system.PPM(i) - self.sim_experiment.RF_OFFSET) TE = self.sim_experiment.TE * 1E-3 TE1 = self.sim_experiment.TE1 * 1E-3 TE2 = self.sim_experiment.TE2 * 1E-3 # build 90 degree pulse inpulse90file = self.sim_experiment.inpulse90file A_90 = self.sim_experiment.A_90 PULSE_90_LENGTH = self.sim_experiment.PULSE_90_LENGTH gyratio = self.sim_experiment.getGyratio() pulse90 = Pulse(inpulse90file, PULSE_90_LENGTH, 'bruker') n_old = np.linspace(0, PULSE_90_LENGTH, sp.size(pulse90.waveform)) n_new = np.linspace(0, PULSE_90_LENGTH, sp.size(pulse90.waveform) + 1) waveform_real = sp.interpolate.InterpolatedUnivariateSpline( n_old, np.real(pulse90.waveform) * A_90)(n_new) waveform_imag = sp.interpolate.InterpolatedUnivariateSpline( n_old, np.imag(pulse90.waveform) * A_90)(n_new) pulse90.waveform = waveform_real + 1j * (waveform_imag) ampl_arr = np.abs(pulse90.waveform) phas_arr = np.unwrap(np.angle(pulse90.waveform)) * 180.0 / math.pi pulse = pg.row_vector(len(pulse90.waveform)) ptime = pg.row_vector(len(pulse90.waveform)) for j, val in enumerate(zip(ampl_arr, phas_arr)): pulse.put(pg.complex(val[0], val[1]), j) ptime.put(pg.complex(pulse90.pulsestep, 0), j) pulse_dur_90 = pulse.size() * pulse90.pulsestep pwf_90 = pg.PulWaveform(pulse, ptime, "90excite") pulc_90 = pg.PulComposite(pwf_90, spin_system, self.sim_experiment.obs_iso) Ureal90 = pulc_90.GetUsum(-1) # build 180 degree pulse inpulse180file = self.sim_experiment.inpulse180file A_180 = self.sim_experiment.A_180 PULSE_180_LENGTH = self.sim_experiment.PULSE_180_LENGTH gyratio = self.sim_experiment.getGyratio() pulse180 = Pulse(inpulse180file, PULSE_180_LENGTH, 'bruker') n_old = np.linspace(0, PULSE_180_LENGTH, sp.size(pulse180.waveform)) n_new = np.linspace(0, PULSE_180_LENGTH, sp.size(pulse180.waveform) + 1) waveform_real = sp.interpolate.InterpolatedUnivariateSpline( n_old, np.real(pulse180.waveform) * A_180)(n_new) waveform_imag = sp.interpolate.InterpolatedUnivariateSpline( n_old, np.imag(pulse180.waveform) * A_180)(n_new) pulse180.waveform = waveform_real + 1j * (waveform_imag) ampl_arr = np.abs(pulse180.waveform) phas_arr = np.unwrap(np.angle(pulse180.waveform)) * 180.0 / math.pi freq_arr = np.gradient(phas_arr) pulse = pg.row_vector(len(pulse180.waveform)) ptime = pg.row_vector(len(pulse180.waveform)) for j, val in enumerate(zip(ampl_arr, phas_arr)): pulse.put(pg.complex(val[0], val[1]), j) ptime.put(pg.complex(n_new[1], 0), j) pulse_dur_180 = pulse.size() * pulse180.pulsestep pwf_180 = pg.PulWaveform(pulse, ptime, "180afp") pulc_180 = pg.PulComposite(pwf_180, spin_system, self.sim_experiment.obs_iso) Ureal180 = pulc_180.GetUsum(-1) H = pg.Hcs(spin_system) + pg.HJ(spin_system) D = pg.Fm(spin_system, self.sim_experiment.obs_iso) ac = pg.acquire1D(pg.gen_op(D), H, self.sim_experiment.dwell_time) ACQ = ac delay1 = TE1 / 2.0 - pulse_dur_90 / 2.0 - pulse_dur_180 / 2.0 delay2 = TE1 / 2.0 + TE2 / 2.0 - pulse_dur_180 delay3 = TE2 - pulse_dur_180 delay4 = delay2 delay5 = TE1 / 2.0 - pulse_dur_180 + self.sim_experiment.DigShift Udelay1 = pg.prop(H, delay1) Udelay2 = pg.prop(H, delay2) Udelay3 = pg.prop(H, delay3) Udelay4 = pg.prop(H, delay4) Udelay5 = pg.prop(H, delay5) sigma0 = pg.sigma_eq(spin_system) # init sigma1 = Ureal90.evolve(sigma0) # apply 90-degree pulse sigma0 = pg.evolve(sigma1, Udelay1) sigma1 = Ureal180.evolve(sigma0) # apply AFP1 sigma0 = pg.evolve(sigma1, Udelay2) sigma1 = Ureal180.evolve(sigma0) # apply AFP2 sigma0 = pg.evolve(sigma1, Udelay3) sigma1 = Ureal180.evolve(sigma0) # apply AFP3 sigma0 = pg.evolve(sigma1, Udelay4) sigma1 = Ureal180.evolve(sigma0) # apply AFP4 sigma0 = pg.evolve(sigma1, Udelay5) elif self.sim_experiment.name == "semi-LASER": spin_system = pg.spin_system() spin_system.read(self.insysfile) for i in range(spin_system.spins()): spin_system.PPM( i, spin_system.PPM(i) - self.sim_experiment.RF_OFFSET) TE = self.sim_experiment.TE TE1 = float((TE * 0.31) / 1000.0) TE3 = float((TE * 0.31) / 1000.0) TE2 = float(TE / 1000.0 - TE1 - TE3) TE_fill = TE / 1000.0 - TE1 - TE2 - TE3 # build 90 degree pulse inpulse90file = self.sim_experiment.inpulse90file A_90 = self.sim_experiment.A_90 PULSE_90_LENGTH = self.sim_experiment.PULSE_90_LENGTH gyratio = self.sim_experiment.getGyratio() pulse90 = Pulse(inpulse90file, PULSE_90_LENGTH) n_old = np.linspace(0, PULSE_90_LENGTH, sp.size(pulse90.waveform)) n_new = np.linspace(0, PULSE_90_LENGTH, sp.size(pulse90.waveform) + 1) waveform_real = sp.interpolate.InterpolatedUnivariateSpline( n_old, np.real(pulse90.waveform) * A_90)(n_new) waveform_imag = sp.interpolate.InterpolatedUnivariateSpline( n_old, np.imag(pulse90.waveform) * A_90)(n_new) pulse90.waveform = waveform_real + 1j * (waveform_imag) ampl_arr = np.abs(pulse90.waveform) * gyratio phas_arr = np.unwrap(np.angle(pulse90.waveform)) * 180.0 / math.pi pulse = pg.row_vector(len(pulse90.waveform)) ptime = pg.row_vector(len(pulse90.waveform)) for j, val in enumerate(zip(ampl_arr, phas_arr)): pulse.put(pg.complex(val[0], val[1]), j) ptime.put(pg.complex(pulse90.pulsestep, 0), j) pulse_dur_90 = pulse.size() * pulse90.pulsestep peak_to_end_90 = pulse_dur_90 - ( 209 + self.sim_experiment.fudge_factor) * pulse90.pulsestep pwf_90 = pg.PulWaveform(pulse, ptime, "90excite") pulc_90 = pg.PulComposite(pwf_90, spin_system, self.sim_experiment.obs_iso) Ureal90 = pulc_90.GetUsum(-1) # build 180 degree pulse inpulse180file = self.sim_experiment.inpulse180file A_180 = self.sim_experiment.A_180 PULSE_180_LENGTH = self.sim_experiment.PULSE_180_LENGTH gyratio = self.sim_experiment.getGyratio() pulse180 = Pulse(inpulse180file, PULSE_180_LENGTH) n_old = np.linspace(0, PULSE_180_LENGTH, sp.size(pulse180.waveform)) n_new = np.linspace(0, PULSE_180_LENGTH, sp.size(pulse180.waveform) + 1) waveform_real = sp.interpolate.InterpolatedUnivariateSpline( n_old, np.real(pulse180.waveform) * A_180)(n_new) waveform_imag = sp.interpolate.InterpolatedUnivariateSpline( n_old, np.imag(pulse180.waveform) * A_180)(n_new) pulse180.waveform = waveform_real + 1j * (waveform_imag) ampl_arr = np.abs(pulse180.waveform) * gyratio phas_arr = np.unwrap(np.angle(pulse180.waveform)) * 180.0 / math.pi freq_arr = np.gradient(phas_arr) pulse = pg.row_vector(len(pulse180.waveform)) ptime = pg.row_vector(len(pulse180.waveform)) for j, val in enumerate(zip(ampl_arr, phas_arr)): pulse.put(pg.complex(val[0], val[1]), j) ptime.put(pg.complex(n_new[1], 0), j) pulse_dur_180 = pulse.size() * pulse180.pulsestep pwf_180 = pg.PulWaveform(pulse, ptime, "180afp") pulc_180 = pg.PulComposite(pwf_180, spin_system, self.sim_experiment.obs_iso) Ureal180 = pulc_180.GetUsum(-1) H = pg.Hcs(spin_system) + pg.HJ(spin_system) D = pg.Fm(spin_system, self.sim_experiment.obs_iso) ac = pg.acquire1D(pg.gen_op(D), H, self.sim_experiment.dwell_time) ACQ = ac delay1 = TE1 / 2.0 + TE_fill / 8.0 - pulse_dur_180 / 2.0 - peak_to_end_90 delay2 = TE1 / 2.0 + TE_fill / 8.0 + TE2 / 4.0 + TE_fill / 8.0 - pulse_dur_180 delay3 = TE2 / 4.0 + TE_fill / 8.0 + TE2 / 4.0 + TE_fill / 8.0 - pulse_dur_180 delay4 = TE2 / 4.0 + TE_fill / 8.0 + TE3 / 2.0 + TE_fill / 8.0 - pulse_dur_180 delay5 = TE3 / 2.0 + TE_fill / 8.0 - pulse_dur_180 / 2.0 Udelay1 = pg.prop(H, delay1) Udelay2 = pg.prop(H, delay2) Udelay3 = pg.prop(H, delay3) Udelay4 = pg.prop(H, delay4) Udelay5 = pg.prop(H, delay5) sigma0 = pg.sigma_eq(spin_system) # init sigma1 = Ureal90.evolve(sigma0) # apply 90-degree pulse sigma0 = pg.evolve(sigma1, Udelay1) sigma1 = Ureal180.evolve(sigma0) # apply AFP1 sigma0 = pg.evolve(sigma1, Udelay2) sigma1 = Ureal180.evolve(sigma0) # apply AFP2 sigma0 = pg.evolve(sigma1, Udelay3) sigma1 = Ureal180.evolve(sigma0) # apply AFP3 sigma0 = pg.evolve(sigma1, Udelay4) sigma1 = Ureal180.evolve(sigma0) # apply AFP4 sigma0 = pg.evolve(sigma1, Udelay5) elif self.sim_experiment.name == "LASER": spin_system = pg.spin_system() spin_system.read(self.insysfile) for i in range(spin_system.spins()): spin_system.PPM( i, spin_system.PPM(i) - self.sim_experiment.RF_OFFSET) # build 90 degree AHP pulse inpulse90file = self.sim_experiment.inpulse90file A_90 = self.sim_experiment.A_90 PULSE_90_LENGTH = self.sim_experiment.PULSE_90_LENGTH gyratio = self.sim_experiment.getGyratio() pulse90 = Pulse(inpulse90file, PULSE_90_LENGTH, 'varian') n_new = np.linspace(0, PULSE_90_LENGTH, 256) waveform_real = np.real(pulse90.waveform) * A_90 waveform_imag = np.imag(pulse90.waveform) * A_90 pulse90.waveform = waveform_real + 1j * (waveform_imag) ampl_arr = np.abs(pulse90.waveform) * gyratio phas_arr = np.unwrap(np.angle(pulse90.waveform)) * 180.0 / math.pi pulse = pg.row_vector(len(pulse90.waveform)) ptime = pg.row_vector(len(pulse90.waveform)) for j, val in enumerate(zip(ampl_arr, phas_arr)): pulse.put(pg.complex(val[0], val[1]), j) ptime.put(pg.complex(pulse90.pulsestep, 0), j) pulse_dur_90 = pulse.size() * pulse90.pulsestep pwf_90 = pg.PulWaveform(pulse, ptime, "90excite") pulc_90 = pg.PulComposite(pwf_90, spin_system, self.sim_experiment.obs_iso) Ureal90 = pulc_90.GetUsum(-1) # build 180 degree pulse inpulse180file = self.sim_experiment.inpulse180file A_180 = self.sim_experiment.A_180 PULSE_180_LENGTH = self.sim_experiment.PULSE_180_LENGTH gyratio = self.sim_experiment.getGyratio() pulse180 = Pulse(inpulse180file, PULSE_180_LENGTH, 'varian') n_new = np.linspace(0, PULSE_180_LENGTH, 512) waveform_real = np.real(pulse180.waveform) * A_180 waveform_imag = np.imag(pulse180.waveform) * A_180 pulse180.waveform = waveform_real + 1j * (waveform_imag) ampl_arr = np.abs(pulse180.waveform) * gyratio phas_arr = np.unwrap(np.angle(pulse180.waveform)) * 180.0 / math.pi freq_arr = np.gradient(phas_arr) pulse = pg.row_vector(len(pulse180.waveform)) ptime = pg.row_vector(len(pulse180.waveform)) for j, val in enumerate(zip(ampl_arr, phas_arr)): pulse.put(pg.complex(val[0], val[1]), j) ptime.put(pg.complex(n_new[1], 0), j) pulse_dur_180 = pulse.size() * pulse180.pulsestep pwf_180 = pg.PulWaveform(pulse, ptime, "180afp") pulc_180 = pg.PulComposite(pwf_180, spin_system, self.sim_experiment.obs_iso) Ureal180 = pulc_180.GetUsum(-1) # calculate pulse timings ROF1 = 100E-6 #sec ROF2 = 10E-6 #sec TCRUSH1 = 0.0008 #sec TCRUSH2 = 0.0008 #sec ss_grad_rfDelayFront = 0 #TCRUSH1 - ROF1 ss_grad_rfDelayBack = 0 #TCRUSH2 - ROF2 ro_grad_atDelayFront = 0 ro_grad_atDelayBack = 0 TE = self.sim_experiment.TE / 1000. ipd = (TE - pulse_dur_90 \ - 6*(ss_grad_rfDelayFront + pulse_dur_180 + ss_grad_rfDelayBack) \ - ro_grad_atDelayFront) / 12 delay1 = ipd + ss_grad_rfDelayFront delay2 = ss_grad_rfDelayBack + 2 * ipd + ss_grad_rfDelayFront delay3 = ss_grad_rfDelayBack + 2 * ipd + ss_grad_rfDelayFront delay4 = ss_grad_rfDelayBack + 2 * ipd + ss_grad_rfDelayFront delay5 = ss_grad_rfDelayBack + 2 * ipd + ss_grad_rfDelayFront delay6 = ss_grad_rfDelayBack + 2 * ipd + ss_grad_rfDelayFront delay7 = ss_grad_rfDelayBack + ipd + ro_grad_atDelayFront # print A_90, A_180, pulse_dur_90, pulse_dur_180 # print TE, ipd, pulse_dur_90+6*pulse_dur_180, delay1+delay2+delay3+delay4+delay5+delay6+delay7, pulse_dur_90+6*pulse_dur_180+delay1+delay2+delay3+delay4+delay5+delay6+delay7 # print '' # initialize acquisition H = pg.Hcs(spin_system) + pg.HJ(spin_system) D = pg.Fm(spin_system, self.sim_experiment.obs_iso) ac = pg.acquire1D(pg.gen_op(D), H, self.sim_experiment.dwell_time) ACQ = ac Udelay1 = pg.prop(H, delay1) Udelay2 = pg.prop(H, delay2) Udelay3 = pg.prop(H, delay3) Udelay4 = pg.prop(H, delay4) Udelay5 = pg.prop(H, delay5) Udelay6 = pg.prop(H, delay6) Udelay7 = pg.prop(H, delay7) sigma0 = pg.sigma_eq(spin_system) # init sigma1 = Ureal90.evolve(sigma0) # apply 90-degree pulse sigma0 = pg.evolve(sigma1, Udelay1) sigma1 = Ureal180.evolve(sigma0) # apply AFP1 sigma0 = pg.evolve(sigma1, Udelay2) sigma1 = Ureal180.evolve(sigma0) # apply AFP2 sigma0 = pg.evolve(sigma1, Udelay3) sigma1 = Ureal180.evolve(sigma0) # apply AFP3 sigma0 = pg.evolve(sigma1, Udelay4) sigma1 = Ureal180.evolve(sigma0) # apply AFP4 sigma0 = pg.evolve(sigma1, Udelay5) sigma1 = Ureal180.evolve(sigma0) # apply AFP5 sigma0 = pg.evolve(sigma1, Udelay6) sigma1 = Ureal180.evolve(sigma0) # apply AFP6 sigma0 = pg.evolve(sigma1, Udelay7) # acquire mx = pg.TTable1D(ACQ.table(sigma0)) # binning to remove degenerate peaks # BINNING # Note: Metabolite Peak Normalization and Blending # The transition tables calculated by the GAMMA density matrix simulations frequently contain a # large number of transitions caused by degenerate splittings and other processes. At the # conclusion of each simulation run a routine is called to extract lines from the transition table. # These lines are then normalized using a closed form calculation based on the number of spins. # To reduce the number of lines required for display, multiple lines are blended by binning them # together based on their PPM locations and phases. The following parameters are used to # customize these procedures: # Peak Search Range -- Low/High (PPM): the range in PPM that is searched for lines from the # metabolite simulation. # Peak Blending Tolerance (PPM and Degrees): the width of the bins (+/- in PPM and +/- in # PhaseDegrees) that are used to blend the lines in the simulation. Lines that are included in the # same bin are summed using complex addition based on Amplitude and Phase. b0 = self.sim_experiment.b0 obs_iso = self.sim_experiment.obs_iso tolppm = self.sim_experiment.tolppm tolpha = self.sim_experiment.tolpha ppmlo = self.sim_experiment.ppmlo ppmhi = self.sim_experiment.ppmhi rf_off = self.sim_experiment.RF_OFFSET field = b0 nspins = spin_system.spins() nlines = mx.size() tmp = pg.Isotope(obs_iso) obs_qn = tmp.qn() qnscale = 1.0 for i in range(nspins): qnscale *= 2 * spin_system.qn(i) + 1 qnscale = qnscale / (2.0 * (2.0 * obs_qn + 1)) freqs = [] outf = [] outa = [] outp = [] nbin = 0 found = False PI = 3.14159265358979323846 RAD2DEG = 180.0 / PI indx = mx.Sort(0, -1, 0) for i in range(nlines): freqs.append(-1 * mx.Fr(indx[i]) / (2.0 * PI * field)) for i in range(nlines): freq = freqs[i] if (freq > ppmlo) and (freq < ppmhi): val = mx.I(indx[i]) tmpa = np.sqrt(val.real()**2 + val.imag()**2) / qnscale tmpp = -RAD2DEG * np.angle(val.real() + 1j * val.imag()) if nbin == 0: outf.append(freq) outa.append(tmpa) outp.append(tmpp) nbin += 1 else: for k in range(nbin): if (freq >= outf[k] - tolppm) and (freq <= outf[k] + tolppm): if (tmpp >= outp[k] - tolpha) and (tmpp <= outp[k] + tolpha): ampsum = outa[k] + tmpa outf[k] = (outa[k] * outf[k] + tmpa * freq) / ampsum outp[k] = (outa[k] * outp[k] + tmpa * tmpp) / ampsum outa[k] += tmpa found = True if not found: outf.append(freq) outa.append(tmpa) outp.append(tmpp) nbin += 1 found = False for i, item in enumerate(outf): outf[i] = item + rf_off outp[i] = outp[i] - 90.0 metab = Metabolite() metab.name = metab_name metab.var = 0.0 for i in range(sp.size(outf)): if outf[i] <= 5: metab.ppm.append(outf[i]) metab.area.append(outa[i]) metab.phase.append(-1.0 * outp[i]) insysfile = self.insysfile.replace('pints/metabolites/3T_', '') insysfile = self.insysfile.replace('pints/metabolites/7T_', '') insysfile = self.insysfile.replace('pints/metabolites/9.4T_', '') if insysfile == 'alanine.sys': # metab.A_m = 0.078 metab.T2 = (87E-3) elif insysfile == 'aspartate.sys': metab.A_m = 0.117 metab.T2 = (87E-3) elif insysfile == 'choline_1-CH2_2-CH2.sys': # metab.A_m = 0.165 metab.T2 = (87E-3) elif insysfile == 'choline_N(CH3)3_a.sys' or insysfile == 'choline_N(CH3)3_b.sys': # metab.A_m = 0.165 metab.T2 = (121E-3) elif insysfile == 'creatine_N(CH3).sys': metab.A_m = 0.296 metab.T2 = (90E-3) elif insysfile == 'creatine_X.sys': metab.A_m = 0.296 metab.T2 = (81E-3) elif insysfile == 'd-glucose-alpha.sys': # metab.A_m = 0.049 metab.T2 = (87E-3) elif insysfile == 'd-glucose-beta.sys': # metab.A_m = 0.049 metab.T2 = (87E-3) elif insysfile == 'eth.sys': # metab.A_m = 0.320 metab.T2 = (87E-3) elif insysfile == 'gaba.sys': # metab.A_m = 0.155 metab.T2 = (82E-3) elif insysfile == 'glutamate.sys': metab.A_m = 0.898 metab.T2 = (88E-3) elif insysfile == 'glutamine.sys': metab.A_m = 0.427 metab.T2 = (87E-3) elif insysfile == 'glutathione_cysteine.sys': metab.A_m = 0.194 metab.T2 = (87E-3) elif insysfile == 'glutathione_glutamate.sys': metab.A_m = 0.194 metab.T2 = (87E-3) elif insysfile == 'glutathione_glycine.sys': metab.A_m = 0.194 metab.T2 = (87E-3) elif insysfile == 'glycine.sys': metab.A_m = 0.068 metab.T2 = (87E-3) elif insysfile == 'gpc_7-CH2_8-CH2.sys': # metab.A_m = 0.097 metab.T2 = (87E-3) elif insysfile == 'gpc_glycerol.sys': # metab.A_m = 0.097 metab.T2 = (87E-3) elif insysfile == 'gpc_N(CH3)3_a.sys': # metab.A_m = 0.097 metab.T2 = (121E-3) elif insysfile == 'gpc_N(CH3)3_b.sys': # metab.A_m = 0.097 metab.T2 = (121E-3) elif insysfile == 'lactate.sys': # metab.A_m = 0.039 metab.T2 = (87E-3) elif insysfile == 'myoinositol.sys': metab.A_m = 0.578 metab.T2 = (87E-3) elif insysfile == 'naa_acetyl.sys': metab.A_m = 1.000 metab.T2 = (130E-3) elif insysfile == 'naa_aspartate.sys': metab.A_m = 1.000 metab.T2 = (69E-3) elif insysfile == 'naag_acetyl.sys': metab.A_m = 0.160 metab.T2 = (130E-3) elif insysfile == 'naag_aspartyl.sys': metab.A_m = 0.160 metab.T2 = (87E-3) elif insysfile == 'naag_glutamate.sys': metab.A_m = 0.160 metab.T2 = (87E-3) elif insysfile == 'pcho_N(CH3)3_a.sys': # metab.A_m = 0.058 metab.T2 = (121E-3) elif insysfile == 'pcho_N(CH3)3_b.sys': # metab.A_m = 0.058 metab.T2 = (121E-3) elif insysfile == 'pcho_X.sys': # metab.A_m = 0.058 metab.T2 = (87E-3) elif insysfile == 'pcr_N(CH3).sys': metab.A_m = 0.422 metab.T2 = (90E-3) elif insysfile == 'pcr_X.sys': metab.A_m = 0.422 metab.T2 = (81E-3) elif insysfile == 'peth.sys': metab.A_m = 0.126 metab.T2 = (87E-3) elif insysfile == 'scyllo-inositol.sys': metab.A_m = 0.044 metab.T2 = (87E-3) elif insysfile == 'taurine.sys': metab.A_m = 0.117 metab.T2 = (85E-3) elif insysfile == 'water.sys': metab.A_m = 1.000 metab.T2 = (43.60E-3) # Send save data signal self.outputResults.emit(metab) self.postToConsole.emit(' | Simulation completed for ... ' + self.insysfile) self.finished.emit(self.thread_num)
class SimpleMapWidget(BaseMapWidget): #tracks tracks_lat = np.array([]) tracks_lon = np.array([]) tracks_lat_pos = None tracks_lon_pos = None tracks_timestamp = None course_plot = None plot_verification = None course_points_plot = None course_point_text = None cuesheet_widget = None #misc y_mod = 1.22 #31/25 at Tokyo(N35) pre_zoomlevel = np.nan drawn_tile = {} tile_exists = {} map_cuesheet_ratio = 1 #map:cuesheet = 1:0 font = "" #signal for physical button signal_search_route = QtCore.pyqtSignal() def setup_ui_extra(self): super().setup_ui_extra() #self.plot.showGrid(x=True, y=True, alpha=1) self.track_plot = self.plot.plot( pen=pg.mkPen(color=(0, 128, 255), width=8)) #self.track_plot = self.plot.plot(pen=pg.mkPen(color=(0,192,255,128), width=8)) self.scale_plot = self.plot.plot(pen=pg.mkPen(color=(0, 0, 0), width=3)) self.scale_text = pg.TextItem( text="", anchor=(0.5, 1), angle=0, border=(255, 255, 255, 255), fill=(255, 255, 255, 255), color=(0, 0, 0), ) self.scale_text.setZValue(100) self.plot.addItem(self.scale_text) self.map_attribution = pg.TextItem( #text = self.config.G_MAP_CONFIG[self.config.G_MAP]['attribution'], html= '<div style="text-align: right;"><span style="color: #000; font-size: 10px;">' + self.config.G_MAP_CONFIG[self.config.G_MAP]['attribution'] + '</span></div>', anchor=(1, 1), angle=0, border=(255, 255, 255, 255), fill=(255, 255, 255, 255), color=(0, 0, 0), ) self.map_attribution.setZValue(100) self.plot.addItem(self.map_attribution) #self.load_course() t = datetime.datetime.utcnow() self.get_track() #heavy when resume print("\tpyqt_graph : get_track(init) : ", (datetime.datetime.utcnow() - t).total_seconds(), "sec") def add_extra(self): #map self.layout.addWidget(self.plot, 0, 0, 4, 3) #print("### self.plot.width ###", self.plot.width()) if self.config.G_AVAILABLE_DISPLAY[self.config.G_DISPLAY]['touch']: #zoom self.layout.addWidget(self.button['zoomdown'], 0, 0) self.layout.addWidget(self.button['lock'], 1, 0) self.layout.addWidget(self.button['zoomup'], 2, 0) #arrow self.layout.addWidget(self.button['left'], 0, 2) self.layout.addWidget(self.button['up'], 1, 2) self.layout.addWidget(self.button['down'], 2, 2) self.layout.addWidget(self.button['right'], 3, 2) if self.config.G_HAVE_GOOGLE_DIRECTION_API_TOKEN: self.layout.addWidget(self.button['go'], 3, 0) self.button['go'].clicked.connect(self.search_route) #for expanding column self.layout.setColumnMinimumWidth(0, 40) self.layout.setColumnStretch(1, 1) self.layout.setColumnMinimumWidth(2, 40) #cue sheet self.init_cuesheet() #center point (displays while moving the map) self.center_point = pg.ScatterPlotItem(pxMode=True, symbol="+") self.center_point_data = { 'pos': [np.nan, np.nan], 'size': 15, 'pen': { 'color': (0, 0, 0), 'width': 2 }, } self.center_point_location = [] #connect signal self.signal_search_route.connect(self.search_route) def init_cuesheet(self): #if self.config.G_CUESHEET_DISPLAY_NUM > 0: if len( self.config.logger.course.point_name ) > 0 and self.config.G_CUESHEET_DISPLAY_NUM > 0 and self.config.G_COURSE_INDEXING: self.cuesheet_widget = CueSheetWidget(self, self.config) self.map_cuesheet_ratio = 0.7 self.layout.addWidget(self.cuesheet_widget, 0, 4, 4, 5) def resizeEvent(self, event): if len( self.config.logger.course.point_name ) == 0 or self.config.G_CUESHEET_DISPLAY_NUM == 0 or not self.config.G_COURSE_INDEXING: self.map_cuesheet_ratio = 1.0 #if self.config.G_CUESHEET_DISPLAY_NUM > 0: else: self.cuesheet_widget.setFixedWidth( int(self.width() * (1 - self.map_cuesheet_ratio))) self.cuesheet_widget.setFixedHeight(self.height()) self.plot.setFixedWidth(int(self.width() * (self.map_cuesheet_ratio))) self.plot.setFixedHeight(self.height()) #override for long press def switch_lock(self): if self.button['lock'].isDown(): if self.button['lock']._state == 0: self.button['lock']._state = 1 else: self.button_press_count['lock'] += 1 #long press if self.button_press_count[ 'lock'] == self.config.G_BUTTON_LONG_PRESS: self.change_move() elif self.button['lock']._state == 1: self.button['lock']._state = 0 self.button_press_count['lock'] = 0 #short press else: super().switch_lock() def load_course(self): if len(self.config.logger.course.latitude) == 0: return t = datetime.datetime.utcnow() if self.course_plot != None: self.plot.removeItem(self.course_plot) self.course_plot = pg.CoursePlotItem( x=self.config.logger.course.longitude, y=self.get_mod_lat_np(self.config.logger.course.latitude), brushes=self.config.logger.course.colored_altitude, width=6) self.plot.addItem(self.course_plot) #test if not self.config.G_IS_RASPI: if self.plot_verification != None: self.plot.removeItem(self.plot_verification) self.plot_verification = pg.ScatterPlotItem(pxMode=True) test_points = [] for i in range(len(self.config.logger.course.longitude)): p = { 'pos': [ self.config.logger.course.longitude[i], self.get_mod_lat(self.config.logger.course.latitude[i]) ], 'size': 2, 'pen': { 'color': 'w', 'width': 1 }, 'brush': pg.mkBrush(color=(255, 0, 0)) } test_points.append(p) self.plot_verification.setData(test_points) self.plot.addItem(self.plot_verification) print("\tpyqt_graph : course plot : ", (datetime.datetime.utcnow() - t).total_seconds(), "sec") #course point if len(self.config.logger.course.point_longitude) == 0: return t = datetime.datetime.utcnow() if self.course_points_plot != None: self.plot.removeItem(self.course_points_plot) self.course_points_plot = pg.ScatterPlotItem(pxMode=True, symbol="t") self.course_points = [] for i in reversed(range(len( self.config.logger.course.point_longitude))): #if self.config.logger.course.point_type[i] == "Straight": # continue cp = { 'pos': [ self.config.logger.course.point_longitude[i], self.get_mod_lat( self.config.logger.course.point_latitude[i]) ], 'size': 10, 'pen': { 'color': 'r', 'width': 1 }, 'brush': pg.mkBrush(color=(255, 0, 0)) } self.course_points.append(cp) self.course_points_plot.setData(self.course_points) self.plot.addItem(self.course_points_plot) print("\tpyqt_graph : load course points plot : ", (datetime.datetime.utcnow() - t).total_seconds(), "sec") def update_extra(self): #t = datetime.datetime.utcnow() #display current position if len(self.location) > 0: self.plot.removeItem(self.current_point) self.location.pop() #display center point if len(self.center_point_location) > 0: self.plot.removeItem(self.center_point) self.center_point_location.pop() #current position self.point['pos'] = [self.gps_values['lon'], self.gps_values['lat']] #dummy position if np.isnan(self.gps_values['lon']) and np.isnan( self.gps_values['lat']): #recent point(from log or pre_point) / course start / fix(TOKYO station) if len(self.tracks_lon) > 0 and len(self.tracks_lat) > 0: self.point['pos'] = [self.tracks_lon_pos, self.tracks_lat_pos] elif len(self.config.logger.course.longitude) > 0 and len( self.config.logger.course.latitude) > 0: self.point['pos'] = [ self.config.logger.course.longitude[0], self.config.logger.course.latitude[0] ] else: self.point['pos'] = [ self.config.G_DUMMY_POS_X, self.config.G_DUMMY_POS_Y ] #update y_mod (adjust for lat:lon=1:1) self.y_mod = self.calc_y_mod(self.point['pos'][1]) #add position circle to map if not np.isnan(self.point['pos'][0]) and not np.isnan( self.point['pos'][1]): if self.gps_values['mode'] == 3: self.point['brush'] = self.point_color['fix'] else: self.point['brush'] = self.point_color['lost'] else: #set dummy self.point['brush'] = self.point_color['lost'] #experimental #print("#### {:.2f}".format(self.get_altitude_from_tile(self.point['pos']))) #center position if self.lock_status: self.map_pos['x'] = self.point['pos'][0] self.map_pos['y'] = self.point['pos'][1] #set width and height self.map_area['w'], self.map_area['h'] = self.get_geo_area( self.map_pos['x'], self.map_pos['y']) #move x_move = y_move = 0 if self.lock_status and len( self.config.logger.course.distance ) > 0 and self.gps_values['on_course_status']: index = self.gps_sensor.get_index_with_distance_cutoff( self.gps_values['course_index'], #get some distance [m] self.get_width_distance(self.map_pos['y'], self.map_area['w']) / 1000, ) x2 = self.config.logger.course.longitude[index] y2 = self.config.logger.course.latitude[index] x_delta = x2 - self.map_pos['x'] y_delta = y2 - self.map_pos['y'] #slide from center x_move = 0.25 * self.map_area['w'] y_move = 0.25 * self.map_area['h'] if x_delta > x_move: self.map_pos['x'] += x_move elif x_delta < -x_move: self.map_pos['x'] -= x_move if y_delta > y_move: self.map_pos['y'] += y_move elif y_delta < -y_move: self.map_pos['y'] -= y_move elif not self.lock_status: if self.move_pos['x'] > 0: x_move = self.map_area['w'] / 2 elif self.move_pos['x'] < 0: x_move = -self.map_area['w'] / 2 if self.move_pos['y'] > 0: y_move = self.map_area['h'] / 2 elif self.move_pos['y'] < 0: y_move = -self.map_area['h'] / 2 self.map_pos['x'] += x_move / self.move_factor self.map_pos['y'] += y_move / self.move_factor self.move_pos['x'] = self.move_pos['y'] = 0 self.map_area['w'], self.map_area['h'] = self.get_geo_area( self.map_pos['x'], self.map_pos['y']) #experimental #print("#### {:.2f}".format(self.get_altitude_from_tile([self.map_pos['x'], self.map_pos['y']]))) ########### # drawing # ########### #current point #print(self.point['pos']) self.point['pos'][1] *= self.y_mod self.location.append(self.point) self.current_point.setData(self.location) self.plot.addItem(self.current_point) #center point if not self.lock_status: if self.move_adjust_mode: #self.center_point.setSymbol("d") self.center_point_data['size'] = 7.5 else: #self.center_point.setSymbol("+") self.center_point_data['size'] = 15 self.center_point_data['pos'][0] = self.map_pos['x'] self.center_point_data['pos'][1] = self.get_mod_lat( self.map_pos['y']) self.center_point_location.append(self.center_point_data) self.center_point.setData(self.center_point_location) self.plot.addItem(self.center_point) #print("\tpyqt_graph : update_extra init : ", (datetime.datetime.utcnow()-t).total_seconds(), "sec") #t = datetime.datetime.utcnow() #set x and y ranges x_start = x_end = y_start = y_end = np.nan x_start = self.map_pos['x'] - self.map_area['w'] / 2 x_end = x_start + self.map_area['w'] y_start = self.map_pos['y'] - self.map_area['h'] / 2 y_end = y_start + self.map_area['h'] if not np.isnan(x_start) and not np.isnan(x_end): self.plot.setXRange(x_start, x_end, padding=0) if not np.isnan(y_start) and not np.isnan(y_end): self.plot.setYRange(self.get_mod_lat(y_start), self.get_mod_lat(y_end), padding=0) self.draw_map_tile(self.zoomlevel, x_start, x_end, y_start, y_end) #print("\tpyqt_graph : update_extra map : ", (datetime.datetime.utcnow()-t).total_seconds(), "sec") #t = datetime.datetime.utcnow() if not self.course_loaded: self.load_course() self.course_loaded = True #course_points and cuesheet self.draw_cuesheet() #print("\tpyqt_graph : update_extra cuesheet : ", (datetime.datetime.utcnow()-t).total_seconds(), "sec") #t = datetime.datetime.utcnow() #draw track self.get_track() self.track_plot.setData(self.tracks_lon, self.tracks_lat) #print("\tpyqt_graph : update_extra track : ", (datetime.datetime.utcnow()-t).total_seconds(), "sec") #t = datetime.datetime.utcnow() #draw scale self.draw_scale(x_start, y_start) #draw map attribution self.draw_map_attribution(x_start, y_start) #print("\tpyqt_graph : update_extra draw map : ", (datetime.datetime.utcnow()-t).total_seconds(), "sec") #t = datetime.datetime.utcnow() def get_track(self): #get track from SQL lon = [] lat = [] #not good (input & output) #conversion coordinate (self.tracks_timestamp, lon, lat) = \ self.config.logger.update_track(self.tracks_timestamp) if len(lon) > 0 and len(lat) > 0: self.tracks_lon_pos = lon[-1] self.tracks_lat_pos = lat[-1] self.tracks_lon = np.append(self.tracks_lon, np.array(lon)) self.tracks_lat = np.append(self.tracks_lat, self.get_mod_lat_np(np.array(lat))) def reset_track(self): self.tracks_lon = [] self.tracks_lat = [] def search_route(self): if self.lock_status: return self.config.logger.course.search_route( self.point['pos'][0], self.point['pos'][1] / self.y_mod, self.map_pos['x'], self.map_pos['y'], ) self.init_cuesheet() self.course_loaded = False self.resizeEvent(None) def draw_map_tile(self, pixel_z, x_start, x_end, y_start, y_end): #get tile coordinates of display border points p0 = {"x": min(x_start, x_end), "y": min(y_start, y_end)} p1 = {"x": max(x_start, x_end), "y": max(y_start, y_end)} #tile range t0 = self.get_tilexy_and_xy_in_tile(pixel_z, p0["x"], p0["y"]) t1 = self.get_tilexy_and_xy_in_tile(pixel_z, p1["x"], p1["y"]) tile_x = sorted([t0[0], t1[0]]) tile_y = sorted([t0[1], t1[1]]) #tile download check if self.zoomlevel not in self.tile_exists: self.tile_exists[self.zoomlevel] = {} tiles = [] for i in range(tile_x[0], tile_x[1] + 1): for j in range(tile_y[0], tile_y[1] + 1): tiles.append((i, j)) for i in [tile_x[0] - 1, tile_x[1] + 1]: for j in range(tile_y[0] - 1, tile_y[1] + 2): tiles.append((i, j)) for i in range(tile_x[0], tile_x[1] + 1): for j in [tile_y[0] - 1, tile_y[1] + 1]: tiles.append((i, j)) for tile in tiles: i = tile[0] j = tile[1] filename = self.config.get_maptile_filename( self.config.G_MAP, pixel_z, i, j) key = "{0}-{1}".format(i, j) if os.path.exists(filename) and os.path.getsize(filename) > 0: self.tile_exists[self.zoomlevel][key] = True continue #download is in progress if key in self.tile_exists[self.zoomlevel]: continue #start downloading self.tile_exists[self.zoomlevel][key] = False if not self.config.download_maptile(pixel_z, i, j): if self.tile_exists[self.zoomlevel] == None: self.tile_exists[self.zoomlevel].discard(key) draw_flag = False if self.zoomlevel not in self.drawn_tile: self.drawn_tile[self.zoomlevel] = set() draw_flag = True if self.pre_zoomlevel != self.zoomlevel: self.drawn_tile[self.zoomlevel] = set() draw_flag = True for i in range(tile_x[0], tile_x[1] + 1): for j in range(tile_y[0], tile_y[1] + 1): key = "{0}-{1}".format(i, j) if self.tile_exists[self.zoomlevel][ key] and key not in self.drawn_tile[self.zoomlevel]: self.drawn_tile[self.zoomlevel].add(key) draw_flag = True self.pre_zoomlevel = self.zoomlevel if not draw_flag: return imgarray = np.empty(((tile_x[1] - tile_x[0] + 1) * 256, (tile_y[1] - tile_y[0] + 1) * 256, 3), dtype='uint8') for i in range(tile_x[0], tile_x[1] + 1): for j in range(tile_y[0], tile_y[1] + 1): filename = self.config.get_maptile_filename( self.config.G_MAP, pixel_z, i, j) I = i - tile_x[0] J = tile_y[1] - j if os.path.exists(filename) and os.path.getsize(filename) > 0: imgarray[I*256:(I+1)*256, J*256:(J+1)*256] = \ np.rot90(np.asarray(Image.open(filename).convert('RGB')).astype('uint8'), -1) else: #finally blank area imgarray[I * 256:(I + 1) * 256, J * 256:(J + 1) * 256] = 255 imgitem = pg.ImageItem(imgarray) saveimg = pg.ImageItem(np.fliplr(imgarray)) #saveimg.save("maptile/out.png") imgarray_min_x, imgarray_max_y = \ self.get_lon_lat_from_tile_xy(pixel_z, tile_x[0], tile_y[0]) imgarray_max_x, imgarray_min_y = \ self.get_lon_lat_from_tile_xy(pixel_z, tile_x[1]+1, tile_y[1]+1) self.plot.addItem(imgitem) imgitem.setZValue(-100) imgitem.setRect( pg.QtCore.QRectF( imgarray_min_x, self.get_mod_lat(imgarray_min_y), imgarray_max_x - imgarray_min_x, self.get_mod_lat(imgarray_max_y) - self.get_mod_lat(imgarray_min_y), )) x = imgarray_min_x y = self.get_mod_lat(imgarray_min_y) w = imgarray_max_x - imgarray_min_x h = self.get_mod_lat(imgarray_max_y) - self.get_mod_lat(imgarray_min_y) def draw_scale(self, x_start, y_start): #draw scale at left bottom scale_factor = 8 scale_dist = self.get_width_distance(y_start, self.map_area['w']) / scale_factor num = scale_dist / (10**int(np.log10(scale_dist))) modify = 1 if 1 < num < 2: modify = 2 / num elif 2 < num < 5: modify = 5 / num elif 5 < num < 10: modify = 10 / num scale_x1 = x_start + self.map_area['w'] / 25 scale_x2 = scale_x1 + self.map_area['w'] / scale_factor * modify scale_y1 = y_start + self.map_area['h'] / 25 scale_y2 = scale_y1 + self.map_area['h'] / 30 scale_y1 = self.get_mod_lat(scale_y1) scale_y2 = self.get_mod_lat(scale_y2) self.scale_plot.setData( [scale_x1, scale_x1, scale_x2, scale_x2], [scale_y2, scale_y1, scale_y1, scale_y2], ) scale_unit = "m" scale_label = round(scale_dist * modify) if scale_label >= 1000: scale_label = int(scale_label / 1000) scale_unit = "km" self.scale_text.setPlainText("{0}{1}".format(scale_label, scale_unit)) self.scale_text.setPos((scale_x1 + scale_x2) / 2, scale_y2) def get_altitude_from_tile(self, pos): f_x, f_y, p_x, p_y = self.get_tilexy_and_xy_in_tile( self.zoomlevel, pos[0], pos[1]) filename = self.config.get_maptile_filename(self.config.G_DEM_MAP, self.zoomlevel, f_x, f_y) if not os.path.exists(filename) or os.path.getsize(filename) == 0: return np.nan imgarray = np.asarray(Image.open(filename)) rgb_pos = imgarray[p_x, p_y] altitude = rgb_pos[0] * (2**16) + rgb_pos[1] * (2**8) + rgb_pos[2] if altitude < 2**23: altitude = altitude * 0.01 elif altitude == 2**23: altitude = np.nan else: altitude = (altitude - 2**24) * 0.01 #print("###altiude", altitude, rgb_pos) return altitude def draw_map_attribution(self, x_start, y_start): #draw map attribution at right bottom self.map_attribution.setPos(x_start + self.map_area['w'], self.get_mod_lat(y_start)) def draw_cuesheet(self): if self.cuesheet_widget != None: self.cuesheet_widget.update_extra() def calc_y_mod(self, lat): if np.isnan(lat): return np.nan return self.config.GEO_R2 / (self.config.GEO_R1 * math.cos(lat / 180 * np.pi)) def get_width_distance(self, lat, w): return w * self.config.GEO_R1 * 1000 * 2 * np.pi * math.cos( lat / 180 * np.pi) / 360 def get_mod_lat(self, lat): return lat * self.calc_y_mod(lat) def get_mod_lat_np(self, lat): return lat * self.config.GEO_R2 / (self.config.GEO_R1 * np.cos(lat / 180 * np.pi)) def get_geo_area(self, x, y): tile_x, tile_y, _, _ = self.get_tilexy_and_xy_in_tile( self.zoomlevel, x, y) pos_x0, pos_y0 = self.get_lon_lat_from_tile_xy(self.zoomlevel, tile_x, tile_y) pos_x1, pos_y1 = self.get_lon_lat_from_tile_xy(self.zoomlevel, tile_x + 1, tile_y + 1) return abs(pos_x1 - pos_x0) / 256 * ( self.width() * self.map_cuesheet_ratio ), abs(pos_y1 - pos_y0) / 256 * self.height() def get_tilexy_and_xy_in_tile(self, z, x, y): n = 2.0**z _y = math.radians(y) x_in_tile, tile_x = math.modf((x + 180.0) / 360.0 * n) y_in_tile, tile_y = math.modf( (1.0 - math.log(math.tan(_y) + (1.0 / math.cos(_y))) / math.pi) / 2.0 * n) return int(tile_x), int(tile_y), int(x_in_tile * 256), int(y_in_tile * 256) def get_lon_lat_from_tile_xy(self, z, x, y): n = 2.0**z lon = x / n * 360.0 - 180.0 lat = math.degrees(math.atan(math.sinh(math.pi * (1 - 2 * y / n)))) return lon, lat
class PointWidget(QtWidgets.QWidget, WIDGET): hide_custom_fields = QtCore.pyqtSignal(bool) saving = QtCore.pyqtSignal() def __init__(self, canvas, parent=None): QtWidgets.QWidget.__init__(self, parent) self.setupUi(self) self.canvas = canvas self.pushButtonAddClass.clicked.connect(self.add_class) self.pushButtonRemoveClass.clicked.connect(self.remove_class) self.pushButtonImport.clicked.connect(self.import_metadata) self.pushButtonSave.clicked.connect(self.save) self.pushButtonLoadPoints.clicked.connect(self.load) self.pushButtonReset.clicked.connect(self.reset) self.pushButtonExport.clicked.connect(self.export) self.pushButtonExport.setIcon(QtGui.QIcon('icons:export.svg')) self.pushButtonReset.setIcon(QtGui.QIcon('icons:reset.svg')) self.pushButtonImport.setIcon(QtGui.QIcon('icons:import.svg')) self.pushButtonSave.setIcon(QtGui.QIcon('icons:save.svg')) self.pushButtonLoadPoints.setIcon(QtGui.QIcon('icons:load.svg')) self.pushButtonRemoveClass.setIcon(QtGui.QIcon('icons:delete.svg')) self.pushButtonAddClass.setIcon(QtGui.QIcon('icons:add.svg')) self.tableWidgetClasses.verticalHeader().setVisible(False) self.tableWidgetClasses.horizontalHeader().setMinimumSectionSize(1) self.tableWidgetClasses.horizontalHeader().setStretchLastSection(False) self.tableWidgetClasses.horizontalHeader().setSectionResizeMode( 0, QtWidgets.QHeaderView.ResizeMode.Stretch) self.tableWidgetClasses.setColumnWidth(1, 30) self.tableWidgetClasses.cellClicked.connect(self.cell_clicked) self.tableWidgetClasses.cellChanged.connect(self.cell_changed) self.tableWidgetClasses.selectionModel().selectionChanged.connect( self.selection_changed) self.checkBoxDisplayPoints.toggled.connect(self.display_points) self.checkBoxDisplayGrid.toggled.connect(self.display_grid) self.canvas.image_loaded.connect(self.image_loaded) self.canvas.update_point_count.connect(self.update_point_count) self.canvas.points_loaded.connect(self.points_loaded) self.canvas.metadata_imported.connect(self.display_count_tree) self.model = QtGui.QStandardItemModel() self.current_model_index = QtCore.QModelIndex() self.treeView.setModel(self.model) self.reset_model() self.treeView.doubleClicked.connect(self.select_model_item) self.previous_file_name = None # used for quick save self.spinBoxPointRadius.valueChanged.connect( self.canvas.set_point_radius) self.spinBoxGrid.valueChanged.connect(self.canvas.set_grid_size) icon = QtGui.QPixmap(20, 20) icon.fill(QtCore.Qt.GlobalColor.yellow) self.labelPointColor.setPixmap(icon) self.labelPointColor.mousePressEvent = self.change_active_point_color icon = QtGui.QPixmap(20, 20) icon.fill(QtCore.Qt.GlobalColor.white) self.labelGridColor.setPixmap(icon) self.labelGridColor.mousePressEvent = self.change_grid_color self.checkBoxImageFields.clicked.connect(self.hide_custom_fields.emit) def add_class(self): class_name, ok = QtWidgets.QInputDialog.getText( self, 'New Class', 'Class Name') if ok: self.canvas.add_class(class_name) self.display_classes() self.display_count_tree() def display_grid(self, display): self.canvas.toggle_grid(display=display) def display_points(self, display): self.canvas.toggle_points(display=display) def cell_changed(self, row, column): if column == 0: old_class = self.canvas.classes[row] new_class = self.tableWidgetClasses.item(row, column).text() if old_class != new_class: self.tableWidgetClasses.selectionModel().clear() self.canvas.rename_class(old_class, new_class) self.display_classes() self.display_count_tree() def cell_clicked(self, row, column): if column == 1: color = QtWidgets.QColorDialog.getColor() if color.isValid(): self.canvas.colors[self.canvas.classes[row]] = color item = QtWidgets.QTableWidgetItem() icon = QtGui.QPixmap(20, 20) icon.fill(color) item.setData(QtCore.Qt.ItemDataRole.DecorationRole, icon) self.tableWidgetClasses.setItem(row, 1, item) def change_active_point_color(self, event): color = QtWidgets.QColorDialog.getColor() if color.isValid(): self.set_active_point_color(color) def change_grid_color(self, event): color = QtWidgets.QColorDialog.getColor() if color.isValid(): self.set_grid_color(color) def display_classes(self): self.tableWidgetClasses.setRowCount(len(self.canvas.classes)) row = 0 for class_name in self.canvas.classes: item = QtWidgets.QTableWidgetItem(class_name) self.tableWidgetClasses.setItem(row, 0, item) item = QtWidgets.QTableWidgetItem() icon = QtGui.QPixmap(20, 20) icon.fill(self.canvas.colors[class_name]) item.setData(QtCore.Qt.ItemDataRole.DecorationRole, icon) self.tableWidgetClasses.setItem(row, 1, item) row += 1 self.tableWidgetClasses.selectionModel().clear() def display_count_tree(self): self.reset_model() for image in self.canvas.points: image_item = QtGui.QStandardItem(image) image_item.setEditable(False) class_item = QtGui.QStandardItem('') class_item.setEditable(False) self.model.appendRow([image_item, class_item]) if image == self.canvas.current_image_name: font = image_item.font() font.setBold(True) image_item.setFont(font) self.treeView.setExpanded(image_item.index(), True) self.current_model_index = image_item.index() for class_name in self.canvas.classes: class_item = QtGui.QStandardItem(class_name) class_item.setEditable(False) class_item.setSelectable(False) class_count = QtGui.QStandardItem('0') if class_name in self.canvas.points[image]: class_count = QtGui.QStandardItem( str(len(self.canvas.points[image][class_name]))) class_count.setEditable(False) class_count.setSelectable(False) image_item.appendRow([class_item, class_count]) self.treeView.scrollTo(self.current_model_index) def export(self): if self.radioButtonCounts.isChecked(): file_name = QtWidgets.QFileDialog.getSaveFileName( self, 'Export Count Summary', os.path.join(self.canvas.directory, 'counts.csv'), 'Text CSV (*.csv)') if file_name[0] != '': self.canvas.export_counts(file_name[0], self.lineEditSurveyId.text()) elif self.radioButtonPoints.isChecked(): file_name = QtWidgets.QFileDialog.getSaveFileName( self, 'Export Points', os.path.join(self.canvas.directory, 'points.csv'), 'Text CSV (*.csv)') if file_name[0] != '': self.canvas.export_points(file_name[0], self.lineEditSurveyId.text()) else: self.chip_dialog = ChipDialog(self.canvas.classes, self.canvas.points, self.canvas.directory, self.lineEditSurveyId.text()) self.chip_dialog.show() def image_loaded(self, directory, file_name): # self.tableWidgetClasses.selectionModel().clear() self.display_count_tree() def import_metadata(self): file_name = QtWidgets.QFileDialog.getOpenFileName( self, 'Select Points File', self.canvas.directory, 'Point Files (*.pnt)') if file_name[0] != '': self.canvas.import_metadata(file_name[0]) def load(self): file_name = QtWidgets.QFileDialog.getOpenFileName( self, 'Select Points File', self.canvas.directory, 'Point Files (*.pnt)') if file_name[0] != '': self.previous_file_name = file_name[0] self.canvas.load_points(file_name[0]) def next(self): max_index = self.model.rowCount() next_index = self.current_model_index.row() + 1 if next_index < max_index: item = self.model.item(next_index) self.select_model_item(item.index()) def points_loaded(self, survey_id): self.lineEditSurveyId.setText(survey_id) self.display_classes() self.update_ui_settings() def previous(self): next_index = self.current_model_index.row() - 1 if next_index >= 0: item = self.model.item(next_index) self.select_model_item(item.index()) def reset(self): msgBox = QtWidgets.QMessageBox() msgBox.setWindowTitle('Warning') msgBox.setText('You are about to clear all data') msgBox.setInformativeText('Do you want to continue?') msgBox.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Cancel | QtWidgets.QMessageBox.StandardButton.Ok) msgBox.setDefaultButton(QtWidgets.QMessageBox.StandardButton.Cancel) response = msgBox.exec() if response == QtWidgets.QMessageBox.StandardButton.Ok: self.canvas.reset() self.display_classes() self.display_count_tree() self.previous_file_name = None def reset_model(self): self.current_model_index = QtCore.QModelIndex() self.model.clear() self.model.setColumnCount(2) self.model.setHeaderData(0, QtCore.Qt.Orientation.Horizontal, 'Image') self.model.setHeaderData(1, QtCore.Qt.Orientation.Horizontal, 'Count') self.treeView.setExpandsOnDoubleClick(False) self.treeView.header().setStretchLastSection(False) self.treeView.header().setSectionResizeMode( 0, QtWidgets.QHeaderView.ResizeMode.Stretch) self.treeView.setTextElideMode(QtCore.Qt.TextElideMode.ElideMiddle) def remove_class(self): indexes = self.tableWidgetClasses.selectedIndexes() if len(indexes) > 0: class_name = self.canvas.classes[indexes[0].row()] msgBox = QtWidgets.QMessageBox() msgBox.setWindowTitle('Warning') msgBox.setText( 'You are about to remove class [{}] '.format(class_name)) msgBox.setInformativeText('Do you want to continue?') msgBox.setStandardButtons(QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Ok) msgBox.setDefaultButton(QtWidgets.QMessageBox.Cancel) response = msgBox.exec() if response == QtWidgets.QMessageBox.Ok: self.canvas.remove_class(class_name) self.display_classes() self.display_count_tree() def quick_save(self): if self.previous_file_name is None: self.save() else: self.saving.emit() self.canvas.save_points(self.previous_file_name, self.lineEditSurveyId.text()) def save(self, override=False): file_name = QtWidgets.QFileDialog.getSaveFileName( self, 'Save Points', os.path.join(self.canvas.directory, 'untitled.pnt'), 'Point Files (*.pnt)') if file_name[0] != '': self.previous_file_name = file_name[0] if override is False and self.canvas.directory != os.path.split( file_name[0])[0]: QtWidgets.QMessageBox.warning( self.parent(), 'ERROR', 'You are attempting to save the pnt file outside of the working directory. Operation canceled. POINT DATA NOT SAVED.', QtWidgets.QMessageBox.Ok) else: if self.canvas.save_points( file_name[0], self.lineEditSurveyId.text()) is False: msg_box = QtWidgets.QMessageBox() msg_box.setWindowTitle('ERROR') msg_box.setText('Save Failed!') msg_box.setInformativeText( 'It appears you cannot save your pnt file in the working directory, possibly due to permissions.\n\nEither change the permissions on the folder or click the SAVE button and select another location outside of the working directory. Remember to copy of the pnt file back into the current working directory. ' ) msg_box.setStandardButtons(QtWidgets.QMessageBox.Save | QtWidgets.QMessageBox.Cancel) msg_box.setDefaultButton(QtWidgets.QMessageBox.Save) response = msg_box.exec() if response == QtWidgets.QMessageBox.Save: self.save(True) def select_model_item(self, model_index): item = self.model.itemFromIndex(model_index) if item.isSelectable(): if item.column() != 0: index = self.model.index(item.row(), 0) item = self.model.itemFromIndex(index) path = os.path.join(self.canvas.directory, item.text()) self.canvas.load_image(path) def selection_changed(self, selected, deselected): if len(selected.indexes()) > 0: self.canvas.set_current_class(selected.indexes()[0].row()) else: self.canvas.set_current_class(None) def set_active_point_color(self, color): icon = QtGui.QPixmap(20, 20) icon.fill(color) self.labelPointColor.setPixmap(icon) self.canvas.set_point_color(color) def set_active_class(self, row): if row < self.tableWidgetClasses.rowCount(): self.tableWidgetClasses.selectRow(row) def set_grid_color(self, color): icon = QtGui.QPixmap(20, 20) icon.fill(color) self.labelGridColor.setPixmap(icon) self.canvas.set_grid_color(color) def update_point_count(self, image_name, class_name, class_count): items = self.model.findItems(image_name) if len(items) == 0: self.display_count_tree() else: items[0].child(self.canvas.classes.index(class_name), 1).setText(str(class_count)) def update_ui_settings(self): ui = self.canvas.ui color = QtGui.QColor(ui['point']['color'][0], ui['point']['color'][1], ui['point']['color'][2]) self.set_active_point_color(color) self.spinBoxPointRadius.setValue(ui['point']['radius']) color = QtGui.QColor(ui['grid']['color'][0], ui['grid']['color'][1], ui['grid']['color'][2]) self.set_grid_color(color) self.spinBoxGrid.setValue(ui['grid']['size'])
class PanningWebView(QWidget): ZOOM_WHEEL = 0.2 webViewScrolled = QtCore.pyqtSignal(bool) webViewResized = QtCore.pyqtSignal() def __init__(self, parent=None): super(PanningWebView, self).__init__() self.zoom = 1.0 self.wheel_dir = 1.0 self.setImgSize(QtCore.QSize(100, 80)) self.pressed = False self.scrolling = False self.positionMousePress = None self.scrollMousePress = None self.handIsClosed = False # self.setContextMenuPolicy(Qt.CustomContextMenu) todo fix this self.scrollPos = QPointF(0.0, 0.0) # self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) todo fix this self.setMouseTracking(True) self.svgRenderer = QtSvg.QSvgRenderer() self.svgRenderer.setAspectRatioMode( Qt.AspectRatioMode.KeepAspectRatioByExpanding) self.svgRenderer.repaintNeeded.connect(self.update) def setContent(self, cnt, type): if self.scrolling: return False if not self.svgRenderer.load(cnt): logging.error("error during parse of svg data") self.setImgSize(self.svgRenderer.defaultSize()) self.svgRenderer.setFramesPerSecond(0) self.svgRenderer.setAspectRatioMode(Qt.AspectRatioMode.KeepAspectRatio) return True def setImgSize(self, newsize: QtCore.QSize): self.imgSize = newsize def resizeEvent(self, event: QResizeEvent): self.webViewResized.emit() super().resizeEvent(event) def paintEvent(self, event): if self.svgRenderer: painter = QPainter(self) rect = QtCore.QRectF( -self.scrollPos.x(), -self.scrollPos.y(), self.svgRenderer.defaultSize().width() * self.zoom, self.svgRenderer.defaultSize().height() * self.zoom, ) self.svgRenderer.render(painter, rect) def setZoomFactor(self, zoom): if zoom > 8: zoom = 8 elif zoom < 0.5: zoom = 0.5 if self.zoom != zoom: self.zoom = zoom self.webViewResized.emit() if self.imgSize.isValid(): self.setImgSize(self.imgSize) self.update() def zoomFactor(self): return self.zoom def scrollPosition(self) -> QPointF: return self.scrollPos def setScrollPosition(self, pos: QPoint): if self.scrollPos != pos: self.scrollPos = pos self.webViewResized.emit() self.update() def zoomIn(self, pos=None): if pos == None: self.setZoomFactor(self.zoomFactor() * (1.0 + self.ZOOM_WHEEL)) else: elemOri = self.mapPosFromPos(pos) self.setZoomFactor(self.zoom * (1.0 + self.ZOOM_WHEEL)) elemDelta = elemOri - self.mapPosFromPos(pos) self.scrollPos = QPointF(self.scrollPos) + elemDelta * self.zoom def zoomOut(self, pos=None): if pos == None: self.setZoomFactor(self.zoom * (1.0 - self.ZOOM_WHEEL)) else: elem_ori = self.mapPosFromPos(pos) self.setZoomFactor(self.zoom * (1.0 - self.ZOOM_WHEEL)) elem_delta = elem_ori - self.mapPosFromPos(pos) self.scrollPos = QPointF(self.scrollPos) + elem_delta * self.zoom def wheelEvent(self, event: QWheelEvent): if (self.wheel_dir * event.angleDelta().y()) < 0: self.zoomIn(event.position()) elif (self.wheel_dir * event.angleDelta().y()) > 0: self.zoomOut(event.position()) def mousePressEvent(self, mouseEvent: QMouseEvent): if (not self.pressed and not self.scrolling and mouseEvent.modifiers() == QtCore.Qt.KeyboardModifier.NoModifier): if mouseEvent.buttons() == QtCore.Qt.MouseButton.LeftButton: self.pressed = True self.scrolling = False self.handIsClosed = False QApplication.setOverrideCursor( QtCore.Qt.CursorShape.OpenHandCursor) self.scrollMousePress = self.scrollPosition() self.positionMousePress = mouseEvent.pos() def mouseReleaseEvent(self, mouseEvent: QMouseEvent): if self.scrolling: self.pressed = False self.scrolling = False self.handIsClosed = False self.positionMousePress = None QApplication.restoreOverrideCursor() self.webViewScrolled.emit(False) return if self.pressed: self.pressed = False self.scrolling = False self.handIsClosed = False QApplication.restoreOverrideCursor() return def hoveCheck(self, pos: QPointF) -> bool: return False def doubleClicked(self, pos: QPointF) -> bool: return False def mouseDoubleClickEvent(self, mouseEvent: QMouseEvent): self.doubleClicked(self.mapPosFromEvent(mouseEvent)) def mapPosFromPos(self, pos: QPointF) -> QPointF: return (pos + QPointF(self.scrollPos)) / self.zoom def mapPosFromPoint(self, mouseEvent: QPoint) -> QPoint: return (mouseEvent + QPointF(self.scrollPos)) / self.zoom def mapPosFromEvent(self, mouseEvent: QMouseEvent) -> QPointF: return (QPointF(mouseEvent.pos()) + QPointF(self.scrollPos)) / self.zoom def mouseMoveEvent(self, mouseEvent: QMouseEvent): if self.scrolling: if not self.handIsClosed: QApplication.restoreOverrideCursor() QApplication.setOverrideCursor( QtCore.Qt.CursorShape.OpenHandCursor) self.handIsClosed = True if self.scrollMousePress != None: delta = mouseEvent.pos() - self.positionMousePress self.setScrollPosition( QPoint(int(self.scrollMousePress.x()), int(self.scrollMousePress.y())) - delta) return if self.pressed: self.pressed = False self.scrolling = True self.webViewScrolled.emit(True) return if self.hoveCheck(self.mapPosFromEvent(mouseEvent)): QApplication.setOverrideCursor( QtCore.Qt.CursorShape.PointingHandCursor) else: QApplication.setOverrideCursor(QtCore.Qt.CursorShape.ArrowCursor) return
class Exporter(QtCore.QThread): progress = QtCore.pyqtSignal(int) def __init__(self, survey_id, classes, points, working_directory, output_directory, width, height, file_type): QtCore.QThread.__init__(self) self.survey_id = survey_id self.classes = classes self.points = points self.working_directory = working_directory self.output_directory = output_directory self.x_offset = width // 2 self.y_offset = height // 2 self.file_type = file_type self.totals = {} for class_name in classes: self.totals[class_name] = 0 def run(self): for class_name in self.classes: os.makedirs('{}{}{}'.format(self.output_directory, os.path.sep, class_name)) summary_file_name = '{}{}summary.csv'.format(self.output_directory, os.path.sep) summary_file = open(summary_file_name, 'w') output = 'survey id,image,class,x,y,chip name' summary_file.write(output) progress = 2 for image in self.points: file = Image.open('{}{}{}'.format(self.working_directory, os.path.sep, image)) img = np.array(file) file.close() for class_name in self.classes: if class_name in self.points[image]: directory = '{}{}{}{}'.format(self.output_directory, os.path.sep, class_name, os.path.sep) for point in self.points[image][class_name]: progress += 1 # set up file name and summary entry self.totals[class_name] += 1 file_name = '{:010d}{}'.format(self.totals[class_name], self.file_type) chip_name = '{}{}'.format(directory, file_name) output = '\n{},{},{},{},{},{}'.format( self.survey_id, image, class_name, point.x(), point.y(), chip_name) summary_file.write(output) # caculate the clip window x = max(0, int(point.x()) - self.x_offset) y = max(0, int(point.y()) - self.y_offset) x2 = min((int(point.x()) - self.x_offset) + (self.x_offset * 2), img.shape[1]) y2 = min((int(point.y()) - self.y_offset) + (self.y_offset * 2), img.shape[0]) window = img[y:y2, x:x2] # fill the chip with data and save chip = np.zeros((self.y_offset * 2, self.x_offset * 2, img.shape[2]), img.dtype) chip[0:window.shape[0], 0:window.shape[1]] = window out_image = Image.fromarray(chip) out_image.save(chip_name) out_image.close() self.progress.emit(progress) summary_file.close()
class CentralWidget(QtWidgets.QDialog, CLASS_DIALOG): load_custom_data = QtCore.pyqtSignal(dict) def __init__(self, parent=None): QtWidgets.QDialog.__init__(self) self.setupUi(self) self.canvas = Canvas() self.point_widget = PointWidget(self.canvas, self) self.findChild(QtWidgets.QFrame, 'framePointWidget').layout().addWidget( self.point_widget) self.point_widget.hide_custom_fields.connect(self.hide_custom_fields) self.point_widget.saving.connect(self.display_quick_save) # Set up keyboard shortcuts self.save_shortcut = QtGui.QShortcut( QtGui.QKeySequence(self.tr("Ctrl+S")), self) # quick save using Ctrl+S self.save_shortcut.setContext( QtCore.Qt.ShortcutContext.WidgetWithChildrenShortcut) self.save_shortcut.activated.connect(self.point_widget.quick_save) self.up_arrow = QtGui.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key.Key_Up), self) self.up_arrow.setContext( QtCore.Qt.ShortcutContext.WidgetWithChildrenShortcut) self.up_arrow.activated.connect(self.point_widget.previous) self.down_arrow = QtGui.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key.Key_Down), self) self.down_arrow.setContext( QtCore.Qt.ShortcutContext.WidgetWithChildrenShortcut) self.down_arrow.activated.connect(self.point_widget.next) # same as arrows but conventient for right handed people self.up_arrow = QtGui.QShortcut(QtGui.QKeySequence(self.tr("W")), self) self.up_arrow.setContext( QtCore.Qt.ShortcutContext.WidgetWithChildrenShortcut) self.up_arrow.activated.connect(self.point_widget.previous) self.down_arrow = QtGui.QShortcut(QtGui.QKeySequence(self.tr("S")), self) self.down_arrow.setContext( QtCore.Qt.ShortcutContext.WidgetWithChildrenShortcut) self.down_arrow.activated.connect(self.point_widget.next) # Make signal slot connections self.graphicsView.setScene(self.canvas) self.graphicsView.drop_complete.connect(self.canvas.load) self.graphicsView.region_selected.connect(self.canvas.select_points) self.graphicsView.delete_selection.connect( self.canvas.delete_selected_points) self.graphicsView.relabel_selection.connect( self.canvas.relabel_selected_points) self.graphicsView.toggle_points.connect( self.point_widget.checkBoxDisplayPoints.toggle) self.graphicsView.toggle_grid.connect( self.point_widget.checkBoxDisplayGrid.toggle) self.graphicsView.switch_class.connect( self.point_widget.set_active_class) self.graphicsView.add_point.connect(self.canvas.add_point) self.canvas.image_loaded.connect(self.graphicsView.image_loaded) self.canvas.directory_set.connect(self.display_working_directory) # Image data fields self.canvas.image_loaded.connect(self.display_coordinates) self.canvas.image_loaded.connect(self.get_custom_field_data) self.canvas.fields_updated.connect(self.display_custom_fields) self.lineEditX.textEdited.connect(self.update_coordinates) self.lineEditY.textEdited.connect(self.update_coordinates) # Buttons self.pushButtonAddField.clicked.connect(self.add_field_dialog) self.pushButtonDeleteField.clicked.connect(self.delete_field_dialog) self.pushButtonFolder.clicked.connect(self.select_folder) self.pushButtonZoomOut.clicked.connect(self.graphicsView.zoom_out) self.pushButtonZoomIn.clicked.connect(self.graphicsView.zoom_in) # Fix icons since no QRC file integration self.pushButtonFolder.setIcon(QtGui.QIcon('icons:folder.svg')) self.pushButtonZoomIn.setIcon(QtGui.QIcon('icons:zoom_in.svg')) self.pushButtonZoomOut.setIcon(QtGui.QIcon('icons:zoom_out.svg')) self.pushButtonDeleteField.setIcon(QtGui.QIcon('icons:delete.svg')) self.pushButtonAddField.setIcon(QtGui.QIcon('icons:add.svg')) self.quick_save_frame = QtWidgets.QFrame(self.graphicsView) self.quick_save_frame.setStyleSheet( "QFrame { background: #4caf50;color: #FFF;font-weight: bold}") self.quick_save_frame.setLayout(QtWidgets.QHBoxLayout()) self.quick_save_frame.layout().addWidget(QtWidgets.QLabel('Saving...')) self.quick_save_frame.setGeometry(3, 3, 100, 35) self.quick_save_frame.hide() def resizeEvent(self, theEvent): self.graphicsView.resize_image() # Image data field functions def add_field(self): field_def = (self.field_name.text(), self.field_type.currentText()) field_names = [x[0] for x in self.canvas.custom_fields['fields']] if field_def[0] in field_names: QtWidgets.QMessageBox.warning(self, 'Warning', 'Field name already exists') else: self.canvas.add_custom_field(field_def) self.add_dialog.close() def add_field_dialog(self): self.field_name = QtWidgets.QLineEdit() self.field_type = QtWidgets.QComboBox() self.field_type.addItems(['line', 'box']) self.add_button = QtWidgets.QPushButton('Save') self.add_button.clicked.connect(self.add_field) self.add_dialog = QtWidgets.QDialog(self) self.add_dialog.setWindowTitle('Add Custom Field') self.add_dialog.setLayout(QtWidgets.QVBoxLayout()) self.add_dialog.layout().addWidget(self.field_name) self.add_dialog.layout().addWidget(self.field_type) self.add_dialog.layout().addWidget(self.add_button) self.add_dialog.resize(250, self.add_dialog.height()) self.add_dialog.show() def delete_field(self): self.canvas.delete_custom_field(self.field_list.currentText()) self.delete_dialog.close() def delete_field_dialog(self): self.field_list = QtWidgets.QComboBox() self.field_list.addItems( [x[0] for x in self.canvas.custom_fields['fields']]) self.delete_button = QtWidgets.QPushButton('Delete') self.delete_button.clicked.connect(self.delete_field) self.delete_dialog = QtWidgets.QDialog(self) self.delete_dialog.setWindowTitle('Delete Custom Field') self.delete_dialog.setLayout(QtWidgets.QVBoxLayout()) self.delete_dialog.layout().addWidget(self.field_list) self.delete_dialog.layout().addWidget(self.delete_button) self.delete_dialog.resize(250, self.delete_dialog.height()) self.delete_dialog.show() def display_coordinates(self, directory, image): if image in self.canvas.coordinates: self.lineEditX.setText(self.canvas.coordinates[image]['x']) self.lineEditY.setText(self.canvas.coordinates[image]['y']) else: self.lineEditX.setText('') self.lineEditY.setText('') def display_custom_fields(self, fields): def build(item): container = QtWidgets.QGroupBox(item[0], self) container.setObjectName(item[0]) container.setLayout(QtWidgets.QVBoxLayout()) if item[1].lower() == 'line': edit = LineText(container) else: edit = BoxText(container) edit.update.connect(self.canvas.save_custom_field_data) self.load_custom_data.connect(edit.load_data) container.layout().addWidget(edit) return container custom_fields = self.findChild(QtWidgets.QFrame, 'frameCustomFields') if custom_fields.layout() is None: custom_fields.setLayout(QtWidgets.QVBoxLayout()) else: layout = custom_fields.layout() while layout.count(): child = layout.takeAt(0) if child.widget(): child.widget().deleteLater() for item in fields: widget = build(item) custom_fields.layout().addWidget(widget) v = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) custom_fields.layout().addItem(v) self.get_custom_field_data() def display_working_directory(self, directory): self.labelWorkingDirectory.setText(directory) def display_quick_save(self): self.quick_save_frame.show() QtCore.QTimer.singleShot(500, self.quick_save_frame.hide) def get_custom_field_data(self): self.load_custom_data.emit(self.canvas.get_custom_field_data()) def hide_custom_fields(self, hide): if hide is True: self.frameCustomField.hide() else: self.frameCustomField.show() def select_folder(self): name = QtWidgets.QFileDialog.getExistingDirectory( self, 'Select image folder', self.canvas.directory) if name != '': self.canvas.load([QtCore.QUrl('file:{}'.format(name))]) def update_coordinates(self, text): x = self.lineEditX.text() y = self.lineEditY.text() self.canvas.save_coordinates(x, y)
class GUI_PyQt(QtCore.QObject): config = None gui_config = None logger = None app = None style = None stack_widget = None main_page = None main_page_index = 0 altitude_graph_widget = None acc_graph_widget = None performance_graph_widget = None course_profile_graph_widget = None simple_map_widget = None cuesheet_widget = None multi_scan_widget = None display_buffer = None #for long press lap_button_count = 0 start_button_count = 0 #signal signal_next_button = QtCore.pyqtSignal(int) signal_prev_button = QtCore.pyqtSignal(int) signal_menu_button = QtCore.pyqtSignal(int) signal_menu_back_button = QtCore.pyqtSignal() signal_get_screenshot = QtCore.pyqtSignal() def __init__(self, config): super().__init__() self.config = config self.config.gui = self self.gui_config = GUI_Config(config) #from other program, call self.config.gui.style self.style = PyQtStyle() self.logger = self.config.logger try: signal.signal(signal.SIGTERM, self.quit_by_ctrl_c) signal.signal(signal.SIGINT, self.quit_by_ctrl_c) signal.signal(signal.SIGQUIT, self.quit_by_ctrl_c) signal.signal(signal.SIGHUP, self.quit_by_ctrl_c) except: pass self.display_buffer = QtCore.QBuffer() self.init_window() def quit_by_ctrl_c(self, signal, frame): self.quit() def quit(self): self.config.quit() self.app.quit() def init_window(self): self.app = QtWidgets.QApplication(sys.argv) self.icon_dir = "" if self.config.G_IS_RASPI: self.icon_dir = self.config.G_INSTALL_PATH #self.main_window # stack_widget # splash_widget # main_widget # main_layout # main_page # button_box_widget # menu_widget time_profile = [ datetime.now(), ] #for time profile self.main_window = MyWindow() self.main_window.set_config(self.config) self.main_window.set_gui(self) self.main_window.setWindowTitle(self.config.G_PRODUCT) self.main_window.setMinimumSize(self.config.G_WIDTH, self.config.G_HEIGHT) self.main_window.show() self.set_color() #base stack_widget self.stack_widget = QtWidgets.QStackedWidget(self.main_window) self.main_window.setCentralWidget(self.stack_widget) self.stack_widget.setContentsMargins(0, 0, 0, 0) #default font res = QtGui.QFontDatabase.addApplicationFont( 'fonts/Yantramanav/Yantramanav-Black.ttf') if res != -1: font_name = QtGui.QFontDatabase.applicationFontFamilies(res)[0] self.stack_widget.setStyleSheet( "font-family: {}".format(font_name)) print("add font:", font_name) #Additional font from setting.conf if self.config.G_FONT_FULLPATH != "": res = QtGui.QFontDatabase.addApplicationFont( self.config.G_FONT_FULLPATH) if res != -1: self.config.G_FONT_NAME = QtGui.QFontDatabase.applicationFontFamilies( res)[0] #self.stack_widget.setStyleSheet("font-family: {}".format(self.config.G_FONT_NAME)) print("add font:", self.config.G_FONT_NAME) #self.stack_widget.setWindowFlags(QtCore.Qt.FramelessWindowHint) #elements #splash self.splash_widget = QtWidgets.QWidget(self.stack_widget) self.splash_widget.setContentsMargins(0, 0, 0, 0) #main self.main_widget = QtWidgets.QWidget(self.stack_widget) self.main_widget.setContentsMargins(0, 0, 0, 0) #menu top self.menu_widget = TopMenuWidget(self.stack_widget, "Settings", self.config) self.menu_widget.setContentsMargins(0, 0, 0, 0) #ANT+ menu self.ant_menu_widget = ANTMenuWidget(self.stack_widget, "ANT+ Sensors", self.config) self.ant_menu_widget.setContentsMargins(0, 0, 0, 0) #ANT+ detail self.ant_detail_widget = ANTDetailWidget(self.stack_widget, "ANT+ Detail", self.config) self.ant_detail_widget.setContentsMargins(0, 0, 0, 0) #adjust altitude self.adjust_wheel_circumference_widget = AdjustWheelCircumferenceWidget( self.stack_widget, "Wheel Size (Circumference)", self.config) self.adjust_wheel_circumference_widget.setContentsMargins(0, 0, 0, 0) #adjust altitude self.adjust_atitude_widget = AdjustAltitudeWidget( self.stack_widget, "Adjust Altitude", self.config) self.adjust_atitude_widget.setContentsMargins(0, 0, 0, 0) #Debug log viewer self.debug_log_viewer_widget = DebugLogViewerWidget( self.stack_widget, "Debug Log Viewer", self.config) self.debug_log_viewer_widget.setContentsMargins(0, 0, 0, 0) #integrate self.stack_widget.addWidget(self.splash_widget) self.stack_widget.addWidget(self.main_widget) self.stack_widget.addWidget(self.menu_widget) self.stack_widget.addWidget(self.ant_menu_widget) self.stack_widget.addWidget(self.ant_detail_widget) self.stack_widget.addWidget(self.adjust_wheel_circumference_widget) self.stack_widget.addWidget(self.adjust_atitude_widget) self.stack_widget.addWidget(self.debug_log_viewer_widget) self.stack_widget.setCurrentIndex(1) #main layout self.main_layout = QtWidgets.QVBoxLayout(self.main_widget) self.main_layout.setContentsMargins(0, 0, 0, 0) self.main_layout.setSpacing(0) self.main_widget.setLayout(self.main_layout) #main Widget self.main_page = QtWidgets.QStackedWidget(self.main_widget) self.main_page.setContentsMargins(0, 0, 0, 0) time_profile.append(datetime.now()) for k in self.gui_config.G_LAYOUT: if not self.gui_config.G_LAYOUT[k]["STATUS"]: continue if "LAYOUT" in self.gui_config.G_LAYOUT[k]: self.main_page.addWidget( ValuesWidget(self.main_page, self.config, self.gui_config.G_LAYOUT[k]["LAYOUT"])) else: if k == "ALTITUDE_GRAPH": self.altitude_graph_widget = pyqt_graph_debug.AltitudeGraphWidget( self.main_page, self.config) self.main_page.addWidget(self.altitude_graph_widget) elif k == "ACC_GRAPH": self.acc_graph_widget = pyqt_graph_debug.AccelerationGraphWidget( self.main_page, self.config) self.main_page.addWidget(self.acc_graph_widget) elif k == "PERFORMANCE_GRAPH": self.performance_graph_widget = pyqt_graph.PerformanceGraphWidget( self.main_page, self.config) self.main_page.addWidget(self.performance_graph_widget) elif k == "COURSE_PROFILE_GRAPH" and os.path.exists( self.config.G_COURSE_FILE ) and self.config.G_COURSE_INDEXING: self.course_profile_graph_widget = pyqt_graph.CourseProfileGraphWidget( self.main_page, self.config) self.main_page.addWidget(self.course_profile_graph_widget) elif k == "SIMPLE_MAP": self.simple_map_widget = pyqt_graph.SimpleMapWidget( self.main_page, self.config) self.main_page.addWidget(self.simple_map_widget) elif k == "CUESHEET" and len(self.config.logger.course.point_name) > 0 and self.config.G_COURSE_INDEXING and \ self.config.G_CUESHEET_DISPLAY_NUM > 0: self.cuesheet_widget = pyqt_graph.CueSheetWidget( self.main_page, self.config) self.main_page.addWidget(self.cuesheet_widget) elif k == "MULTI_SCAN": self.multi_scan_widget = pyqt_multiscan.MultiScanWidget( self.main_page, self.config) self.main_page.addWidget(self.multi_scan_widget) time_profile.append(datetime.now()) #button self.button_box_widget = ButtonBoxWidget(self.main_widget, self.config) self.button_box_widget.start_button.clicked.connect( self.gui_start_and_stop_quit) self.button_box_widget.lap_button.clicked.connect(self.gui_lap_reset) self.button_box_widget.menu_button.clicked.connect(self.goto_menu) self.button_box_widget.scrollnext_button.clicked.connect( self.scroll_next) self.button_box_widget.scrollprev_button.clicked.connect( self.scroll_prev) #physical button self.signal_next_button.connect(self.scroll) self.signal_prev_button.connect(self.scroll) self.signal_menu_button.connect(self.change_menu_page) self.signal_menu_back_button.connect(self.change_menu_back) #other self.signal_get_screenshot.connect(self.screenshot) #integrate main_layout self.main_layout.addWidget(self.main_page) if not self.config.G_AVAILABLE_DISPLAY[self.config.G_DISPLAY]['touch']: self.button_box_widget.setVisible(False) else: self.main_layout.addWidget(self.button_box_widget) time_profile.append(datetime.now()) #for time profile #self.main_window.show() #fullscreen if self.config.G_FULLSCREEN: self.main_window.showFullScreen() self.on_change_main_page(self.main_page_index) diff_label = ["base", "widget", "button"] print(" gui_pyqt:") for i in range(len(time_profile)): if i == 0: continue t = "{0:.4f}".format( (time_profile[i] - time_profile[i - 1]).total_seconds()) print(" ", '{:<13}'.format(diff_label[i - 1]), ":", t) self.app.exec() #exit this line #sys.exit(self.app.exec_()) #for stack_widget page transition def on_change_main_page(self, index): self.main_page.widget(self.main_page_index).stop() self.main_page.widget(index).start() self.main_page_index = index #def send_key(self, e): # if e.key() == QtCore.Qt.Key_N: # self.scroll_next() def start_and_stop_manual(self): self.logger.start_and_stop_manual() def count_laps(self): self.logger.count_laps() def reset_count(self): self.logger.reset_count() def press_key(self, key): e_press = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key, QtCore.Qt.NoModifier, None) e_release = QtGui.QKeyEvent(QtCore.QEvent.KeyRelease, key, QtCore.Qt.NoModifier, None) QtCore.QCoreApplication.postEvent(QtWidgets.QApplication.focusWidget(), e_press) QtCore.QCoreApplication.postEvent(QtWidgets.QApplication.focusWidget(), e_release) def press_tab(self): #self.press_key(QtCore.Qt.Key_Tab) self.main_page.widget(self.main_page_index).focusPreviousChild() def press_down(self): #self.press_key(QtCore.Qt.Key_Down) self.main_page.widget(self.main_page_index).focusNextChild() def press_space(self): self.press_key(QtCore.Qt.Key_Space) def scroll_next(self): self.signal_next_button.emit(1) def scroll_prev(self): self.signal_next_button.emit(-1) def enter_menu(self): i = self.stack_widget.currentIndex() if i == 1: #goto_menu: self.signal_menu_button.emit(2) elif i >= 2: #back self.back_menu() def back_menu(self): self.signal_menu_back_button.emit() def change_mode(self): #if displays MAP, change MAP_1, MAP_2, MAIN #check MAIN if self.stack_widget.currentIndex() != 1: return #check MAP w = self.main_page.widget(self.main_page.currentIndex()) w_name = w.__class__.__name__ b_m_g = self.config.G_BUTTON_MODE_GROUPS b_m_i = self.config.G_BUTTON_MODE_INDEX if w_name == 'SimpleMapWidget': #change_mode m = 'MAP' self.config.logger.sensor.sensor_i2c.change_button_mode(m) if b_m_g[m][b_m_i[m]] == "MAIN": w.lock_on() else: w.lock_off() def map_move_x_plus(self): self.map_method("move_x_plus") def map_move_x_minus(self): self.map_method("move_x_minus") def map_move_y_plus(self): self.map_method("move_y_plus") def map_move_y_minus(self): self.map_method("move_y_minus") def map_change_move(self): self.map_method("change_move") def map_zoom_plus(self): self.map_method("zoom_plus") def map_zoom_minus(self): self.map_method("zoom_minus") def map_search_route(self): self.map_method("search_route") def map_method(self, mode): w = self.main_page.widget(self.main_page.currentIndex()) widget_name = w.__class__.__name__ if widget_name == 'SimpleMapWidget': eval('w.signal_' + mode + '.emit()') def dummy(self): pass def scroll(self, delta): mod_index = self.main_page.currentIndex() while True: mod_index += delta if mod_index == self.main_page.count(): mod_index = 0 elif mod_index == -1: mod_index = self.main_page.count() - 1 if self.main_page.widget(mod_index).onoff == True: break self.on_change_main_page(mod_index) self.main_page.setCurrentIndex(mod_index) def get_screenshot(self): self.signal_get_screenshot.emit() def screenshot(self): date = datetime.now() print("screenshot") filename = date.strftime('%Y-%m-%d_%H-%M-%S.jpg') p = self.stack_widget.grab() p.save(self.config.G_SCREENSHOT_DIR + filename, 'jpg') def draw_display(self): if not self.config.logger.sensor.sensor_spi.send_display: return p = self.stack_widget.grab() self.display_buffer.open(QtCore.QBuffer.ReadWrite) p.save(self.display_buffer, 'BMP') self.config.logger.sensor.sensor_spi.update( io.BytesIO(self.display_buffer.data())) self.display_buffer.close() def gui_lap_reset(self): if self.button_box_widget.lap_button.isDown(): if self.button_box_widget.lap_button._state == 0: self.button_box_widget.lap_button._state = 1 else: self.lap_button_count += 1 print('lap button pressing : ', self.lap_button_count) if self.lap_button_count == self.config.G_BUTTON_LONG_PRESS: print('reset') self.logger.reset_count() self.simple_map_widget.reset_track() self.lap_button_count = 0 elif self.button_box_widget.lap_button._state == 1: self.button_box_widget.lap_button._state = 0 self.lap_button_count = 0 else: self.logger.count_laps() def gui_start_and_stop_quit(self): if self.button_box_widget.start_button.isDown(): if self.button_box_widget.start_button._state == 0: self.button_box_widget.start_button._state = 1 else: self.start_button_count += 1 print('start button pressing : ', self.start_button_count) if self.start_button_count == self.config.G_BUTTON_LONG_PRESS: print('quit or poweroff') self.quit() elif self.button_box_widget.start_button._state == 1: self.button_box_widget.start_button._state = 0 self.start_button_count = 0 else: self.logger.start_and_stop_manual() def change_start_stop_button(self, status): icon = QtGui.QIcon(self.icon_dir + 'img/pause_white.png') if status == "START": icon = QtGui.QIcon(self.icon_dir + 'img/next_white.png') self.button_box_widget.start_button.setIcon(icon) #in mip display, setIcon seems not to occur paint event self.draw_display() def brightness_control(self): self.config.logger.sensor.sensor_spi.brightness_control() def change_menu_page(self, page): self.stack_widget.setCurrentIndex(page) def change_menu_back(self): self.stack_widget.currentWidget().back() def goto_menu(self): self.change_menu_page(self.gui_config.G_GUI_INDEX['menu']) def set_color(self, daylight=True): if daylight: self.main_window.setStyleSheet( "color: black; background-color: white") else: self.main_window.setStyleSheet( "color: white; background-color: #222222")
class CentralGraphicsView(QtWidgets.QGraphicsView): add_point = QtCore.pyqtSignal(QtCore.QPointF) drop_complete = QtCore.pyqtSignal(list) region_selected = QtCore.pyqtSignal(QtCore.QRectF) delete_selection = QtCore.pyqtSignal() relabel_selection = QtCore.pyqtSignal() toggle_points = QtCore.pyqtSignal() toggle_grid = QtCore.pyqtSignal() switch_class = QtCore.pyqtSignal(int) def __init__(self, parent=None): QtWidgets.QGraphicsView.__init__(self, parent) self.setMouseTracking(True) self.setAcceptDrops(True) self.shift = False self.ctrl = False self.alt = False self.delay = 0 self.setViewportUpdateMode( QtWidgets.QGraphicsView.ViewportUpdateMode.FullViewportUpdate) def enterEvent(self, event): self.setFocus() def dragEnterEvent(self, event): event.setAccepted(True) def dragMoveEvent(self, event): pass def dropEvent(self, event): if len(event.mimeData().urls()) > 0: self.drop_complete.emit(event.mimeData().urls()) def image_loaded(self, directory, file_name): self.resetTransform() self.fitInView(self.scene().itemsBoundingRect(), QtCore.Qt.AspectRatioMode.KeepAspectRatio) self.setSceneRect(self.scene().itemsBoundingRect()) def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key.Key_Alt: self.alt = True elif event.key() == QtCore.Qt.Key.Key_Control: self.ctrl = True elif event.key() == QtCore.Qt.Key.Key_Shift: self.shift = True elif event.key() == QtCore.Qt.Key.Key_Delete or event.key( ) == QtCore.Qt.Key.Key_Backspace: self.delete_selection.emit() elif event.key() == QtCore.Qt.Key.Key_R: self.relabel_selection.emit() elif event.key() == QtCore.Qt.Key.Key_D: self.toggle_points.emit() elif event.key() == QtCore.Qt.Key.Key_G: self.toggle_grid.emit() elif event.key() == QtCore.Qt.Key.Key_1: self.switch_class.emit(0) elif event.key() == QtCore.Qt.Key.Key_2: self.switch_class.emit(1) elif event.key() == QtCore.Qt.Key.Key_3: self.switch_class.emit(2) elif event.key() == QtCore.Qt.Key.Key_4: self.switch_class.emit(3) elif event.key() == QtCore.Qt.Key.Key_5: self.switch_class.emit(4) elif event.key() == QtCore.Qt.Key.Key_6: self.switch_class.emit(5) elif event.key() == QtCore.Qt.Key.Key_7: self.switch_class.emit(6) elif event.key() == QtCore.Qt.Key.Key_8: self.switch_class.emit(7) elif event.key() == QtCore.Qt.Key.Key_9: self.switch_class.emit(8) elif event.key() == QtCore.Qt.Key.Key_0: self.switch_class.emit(9) def keyReleaseEvent(self, event): if event.key() == QtCore.Qt.Key.Key_Alt: self.alt = False elif event.key() == QtCore.Qt.Key.Key_Control: self.ctrl = False elif event.key() == QtCore.Qt.Key.Key_Shift: self.shift = False def mouseMoveEvent(self, event): QtWidgets.QGraphicsView.mouseMoveEvent(self, event) def mousePressEvent(self, event): if self.ctrl: self.add_point.emit(self.mapToScene(event.pos())) elif self.shift: self.setDragMode(QtWidgets.QGraphicsView.DragMode.RubberBandDrag) QtWidgets.QGraphicsView.mousePressEvent(self, event) else: self.setDragMode(QtWidgets.QGraphicsView.DragMode.ScrollHandDrag) QtWidgets.QGraphicsView.mousePressEvent(self, event) def mouseReleaseEvent(self, event): if self.dragMode() == QtWidgets.QGraphicsView.DragMode.RubberBandDrag: rect = self.rubberBandRect() self.region_selected.emit(self.mapToScene(rect).boundingRect()) QtWidgets.QGraphicsView.mouseReleaseEvent(self, event) self.setDragMode(QtWidgets.QGraphicsView.DragMode.NoDrag) def resizeEvent(self, event): self.resize_image() def resize_image(self): vsb = self.verticalScrollBar().isVisible() hsb = self.horizontalScrollBar().isVisible() if not (vsb or hsb): self.fitInView(self.scene().itemsBoundingRect(), QtCore.Qt.AspectRatioMode.KeepAspectRatio) self.setSceneRect(self.scene().itemsBoundingRect()) def wheelEvent(self, event): if len(self.scene().items()) > 0: if event.angleDelta().y() > 0: self.zoom_in() else: self.zoom_out() def zoom_in(self): self.scale(1.1, 1.1) # Fix for MacOS and PyQt5 > v5.10 self.repaint() def zoom_out(self): self.scale(0.9, 0.9) # Fix for MacOS and PyQt5 > v5.10 self.repaint()
class Mythread(QtCore.QThread): # 定义信号,定义参数为str类型 signal_one = QtCore.pyqtSignal(int) def __init__(self, parent=None): super().__init__(parent) self.headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36", "Referer": "https://www.mzitu.com/" } self.url = None self.max_page_num = 0 self.dir = None def get_max_page_num(self,url): response = requests.get(url, headers=self.headers) response.encoding = "utf-8" page = BeautifulSoup(response.text, "html.parser") ### 获得节点的src属性 img_url = page.find('div', class_="pagenavi") try: lasturl = img_url.find_all('a')[-2].get('href') self.max_page_num = int(lasturl.split("/")[-1]) return self.max_page_num except: print("查找照片个数失败") def download_current_img(self,url): #下载图片 response = requests.get(url,headers=self.headers) response.encoding = "utf-8" #print(response.text) page = BeautifulSoup(response.text,"html.parser") ### 获得节点的src属性 img_url = page.find('div',class_='main-image').img['src'] #下载 img_name= img_url.split("/")[-1] response_img = requests.get(img_url, headers=self.headers) with open("%s/%s"%(self.dir,img_name), 'wb') as f: f.write(response_img.content) # 图片内容写入文件 f.close() print("url: %s 图片抓取成功:%s"%(img_url, img_name)) def run(self): while True: # 爬虫图片 if self.url: print("开始爬虫") self.signal_one.emit(0) num = self.get_max_page_num(self.url) for i in range(0,num+1): if i == 0: self.download_current_img(self.url) elif i<100: self.download_current_img(self.url + "/%02d" % (i)) else: self.download_current_img(self.url + "/%03d" % (i)) self.signal_one.emit(int(i*100//num)) self.url = None self.signal_one.emit(100) print("爬取完成")
class Canvas(QtWidgets.QGraphicsScene): image_loaded = QtCore.pyqtSignal(str, str) points_loaded = QtCore.pyqtSignal(str) directory_set = QtCore.pyqtSignal(str) fields_updated = QtCore.pyqtSignal(list) update_point_count = QtCore.pyqtSignal(str, str, int) metadata_imported = QtCore.pyqtSignal() def __init__(self): QtWidgets.QGraphicsScene.__init__(self) self.points = {} self.colors = {} self.coordinates = {} self.custom_fields = {'fields': [], 'data': {}} self.classes = [] self.selection = [] self.ui = { 'grid': { 'size': 200, 'color': [255, 255, 255] }, 'point': { 'radius': 25, 'color': [255, 255, 0] } } self.directory = '' self.current_image_name = None self.current_class_name = None self.qt_image = None self.show_grid = True self.selected_pen = QtGui.QPen( QtGui.QBrush(QtCore.Qt.GlobalColor.red, QtCore.Qt.BrushStyle.SolidPattern), 1) def add_class(self, class_name): if class_name not in self.classes: self.classes.append(class_name) self.classes.sort() self.colors[class_name] = QtGui.QColor(QtCore.Qt.GlobalColor.black) def add_custom_field(self, field_def): self.custom_fields['fields'].append(field_def) self.custom_fields['data'][field_def[0]] = {} self.fields_updated.emit(self.custom_fields['fields']) def add_point(self, point): if self.current_image_name is not None and self.current_class_name is not None: if self.current_class_name not in self.points[ self.current_image_name]: self.points[self.current_image_name][ self.current_class_name] = [] display_radius = self.ui['point']['radius'] active_color = QtGui.QColor(self.ui['point']['color'][0], self.ui['point']['color'][1], self.ui['point']['color'][2]) active_brush = QtGui.QBrush(active_color, QtCore.Qt.BrushStyle.SolidPattern) active_pen = QtGui.QPen(active_brush, 2) self.points[self.current_image_name][ self.current_class_name].append(point) self.addEllipse( QtCore.QRectF(point.x() - ((display_radius - 1) / 2), point.y() - ((display_radius - 1) / 2), display_radius, display_radius), active_pen, active_brush) self.update_point_count.emit( self.current_image_name, self.current_class_name, len(self.points[self.current_image_name][ self.current_class_name])) def clear_grid(self): for graphic in self.items(): if type(graphic) == QtWidgets.QGraphicsLineItem: self.removeItem(graphic) def clear_points(self): for graphic in self.items(): if type(graphic) == QtWidgets.QGraphicsEllipseItem: self.removeItem(graphic) def delete_selected_points(self): if self.current_image_name is not None: points = self.points[self.current_image_name] for class_name, point in self.selection: points[class_name].remove(point) self.update_point_count.emit( self.current_image_name, class_name, len(self.points[self.current_image_name][class_name])) self.selection = [] self.display_points() def delete_custom_field(self, field): if field in self.custom_fields['data']: self.custom_fields['data'].pop(field) index = -1 for i, (field_name, _) in enumerate(self.custom_fields['fields']): if field_name == field: index = i if index >= 0: self.custom_fields['fields'].pop(index) self.fields_updated.emit(self.custom_fields['fields']) def display_grid(self): self.clear_grid() if self.current_image_name and self.show_grid: grid_color = QtGui.QColor(self.ui['grid']['color'][0], self.ui['grid']['color'][1], self.ui['grid']['color'][2]) grid_size = self.ui['grid']['size'] rect = self.itemsBoundingRect() brush = QtGui.QBrush(grid_color, QtCore.Qt.BrushStyle.SolidPattern) pen = QtGui.QPen(brush, 1) for x in range(grid_size, int(rect.width()), grid_size): line = QtCore.QLineF(x, 0.0, x, rect.height()) self.addLine(line, pen) for y in range(grid_size, int(rect.height()), grid_size): line = QtCore.QLineF(0.0, y, rect.width(), y) self.addLine(line, pen) def display_points(self): self.clear_points() if self.current_image_name in self.points: display_radius = self.ui['point']['radius'] active_color = QtGui.QColor(self.ui['point']['color'][0], self.ui['point']['color'][1], self.ui['point']['color'][2]) active_brush = QtGui.QBrush(active_color, QtCore.Qt.BrushStyle.SolidPattern) active_pen = QtGui.QPen(active_brush, 2) for class_name in self.points[self.current_image_name]: points = self.points[self.current_image_name][class_name] brush = QtGui.QBrush(self.colors[class_name], QtCore.Qt.BrushStyle.SolidPattern) pen = QtGui.QPen(brush, 2) for point in points: if class_name == self.current_class_name: self.addEllipse( QtCore.QRectF( point.x() - ((display_radius - 1) / 2), point.y() - ((display_radius - 1) / 2), display_radius, display_radius), active_pen, active_brush) else: self.addEllipse( QtCore.QRectF( point.x() - ((display_radius - 1) / 2), point.y() - ((display_radius - 1) / 2), display_radius, display_radius), pen, brush) def export_counts(self, file_name, survey_id): if self.current_image_name is not None: file = open(file_name, 'w') output = 'survey id,image' for class_name in self.classes: output += ',' + class_name output += ",x,y" for field_name, _ in self.custom_fields['fields']: output += ',{}'.format(field_name) output += '\n' file.write(output) for image in self.points: output = survey_id + ',' + image for class_name in self.classes: if class_name in self.points[image]: output += ',' + str(len( self.points[image][class_name])) else: output += ',0' if image in self.coordinates: output += ',' + self.coordinates[image]['x'] output += ',' + self.coordinates[image]['y'] else: output += ',,' for field_name, _ in self.custom_fields['fields']: if image in self.custom_fields['data'][field_name]: output += ',{}'.format( self.custom_fields['data'][field_name][image]) else: output += ',' output += "\n" file.write(output) file.close() def export_points(self, file_name, survey_id): if self.current_image_name is not None: file = open(file_name, 'w') output = 'survey id,image,class,x,y' file.write(output) for image in self.points: for class_name in self.classes: if class_name in self.points[image]: for point in self.points[image][class_name]: output = '\n{},{},{},{},{}'.format( survey_id, image, class_name, point.x(), point.y()) file.write(output) file.close() def get_custom_field_data(self): data = {} if self.current_image_name is not None: for field_def in self.custom_fields['fields']: if self.current_image_name in self.custom_fields['data'][ field_def[0]]: data[field_def[0]] = self.custom_fields['data'][ field_def[0]][self.current_image_name] else: data[field_def[0]] = '' return data def import_metadata(self, file_name): file = open(file_name, 'r') data = json.load(file) file.close() # Backward compat if 'custom_fields' in data: self.custom_fields = data['custom_fields'] else: self.custom_fields = {'fields': [], 'data': {}} if 'ui' in data: self.ui = data['ui'] else: self.ui = { 'grid': { 'size': 200, 'color': [255, 255, 255] }, 'point': { 'radius': 25, 'color': [255, 255, 0] } } # End Backward compat self.colors = data['colors'] for class_name in data['colors']: self.colors[class_name] = QtGui.QColor(self.colors[class_name][0], self.colors[class_name][1], self.colors[class_name][2]) self.classes = data['classes'] self.fields_updated.emit(self.custom_fields['fields']) self.points_loaded.emit('') self.metadata_imported.emit() def load(self, drop_list): peek = drop_list[0].toLocalFile() if os.path.isdir(peek): if self.directory == '': # strip off trailing sep from path osx_hack = os.path.join(peek, 'OSX') self.directory = os.path.split(osx_hack)[0] # end self.directory_set.emit(self.directory) files = glob.glob(os.path.join(self.directory, '*')) image_format = [".jpg", ".jpeg", ".png", ".tif"] f = (lambda x: os.path.splitext(x)[1].lower() in image_format) image_list = list(filter(f, files)) image_list = sorted(image_list) self.load_images(image_list) else: QtWidgets.QMessageBox.warning( self.parent(), 'Warning', 'Working directory already set. Load canceled.', QtWidgets.QMessageBox.Ok) else: base_path = os.path.split(peek)[0] for entry in drop_list: file_name = entry.toLocalFile() path = os.path.split(file_name)[0] error = False message = '' if os.path.isdir(file_name): error = True message = 'Mix of files and directories detected. Load canceled.' if base_path != path: error = True message = 'Files from multiple directories detected. Load canceled.' if self.directory != '' and self.directory != path: error = True message = 'Image originated outside current working directory. Load canceled.' if error: QtWidgets.QMessageBox.warning(self.parent(), 'Warning', message, QtWidgets.QMessageBox.Ok) return None self.directory = base_path self.directory_set.emit(self.directory) self.load_images(drop_list) def load_image(self, in_file_name): Image.MAX_IMAGE_PIXELS = 1000000000 file_name = in_file_name if type(file_name) == QtCore.QUrl: file_name = in_file_name.toLocalFile() if self.directory == '': self.directory = os.path.split(file_name)[0] self.directory_set.emit(self.directory) if self.directory == os.path.split(file_name)[0]: QtWidgets.QApplication.setOverrideCursor( QtCore.Qt.CursorShape.WaitCursor) self.selection = [] self.clear() self.current_image_name = os.path.split(file_name)[1] if self.current_image_name not in self.points: self.points[self.current_image_name] = {} try: img = Image.open(file_name) channels = len(img.getbands()) array = np.array(img) img.close() if array.shape[0] > 10000 or array.shape[1] > 10000: # Make smaller tiles to save memory stride = 100 max_stride = (array.shape[1] // stride) * stride tail = array.shape[1] - max_stride tile = np.zeros((array.shape[0], stride, array.shape[2]), dtype=np.uint8) for s in range(0, max_stride, stride): tile[:, :] = array[:, s:s + stride] qt_image = QtGui.QImage( tile.data, tile.shape[1], tile.shape[0], QtGui.QImage.Format.Format_RGB888) pixmap = QtGui.QPixmap.fromImage(qt_image) item = self.addPixmap(pixmap) item.moveBy(s, 0) # Fix for windows, thin slivers at the end cause the app to hang. QImage bug? if tail > 0: tile2 = np.ones( (array.shape[0], stride, array.shape[2]), dtype=np.uint8) * 255 tile2[:, 0:tail] = array[:, max_stride:array.shape[1]] qt_image = QtGui.QImage( tile2.data, tile2.shape[1], tile2.shape[0], QtGui.QImage.Format.Format_RGB888) pixmap = QtGui.QPixmap.fromImage(qt_image) item = self.addPixmap(pixmap) item.moveBy(max_stride, 0) else: if channels == 1: self.qt_image = QtGui.QImage( array.data, array.shape[1], array.shape[0], QtGui.QImage.Format.Format_Grayscale8) else: # Apply basic min max stretch to the image for chan in range(channels): array[:, :, chan] = np.interp(array[:, :, chan], (array[:, :, chan].min(), array[:, :, chan].max()), (0, 255)) bpl = int(array.nbytes / array.shape[0]) if array.shape[2] == 4: self.qt_image = QtGui.QImage( array.data, array.shape[1], array.shape[0], QtGui.QImage.Format.Format_RGBA8888) else: self.qt_image = QtGui.QImage( array.data, array.shape[1], array.shape[0], bpl, QtGui.QImage.Format.Format_RGB888) self.pixmap = QtGui.QPixmap.fromImage(self.qt_image) self.addPixmap(self.pixmap) except FileNotFoundError: QtWidgets.QMessageBox.critical( None, 'File Not Found', '{} is not in the same folder as the point file.'.format( self.current_image_name)) self.image_loaded.emit(self.directory, self.current_image_name) self.image_loaded.emit(self.directory, self.current_image_name) self.display_points() self.display_grid() QtWidgets.QApplication.restoreOverrideCursor() def load_images(self, images): for file in images: file_name = file if type(file) == QtCore.QUrl: file_name = file.toLocalFile() image_name = os.path.split(file_name)[1] if image_name not in self.points: self.points[image_name] = {} if len(images) > 0: self.load_image(images[0]) def load_points(self, file_name): file = open(file_name, 'r') self.directory = os.path.split(file_name)[0] self.directory_set.emit(self.directory) data = json.load(file) file.close() survey_id = data['metadata']['survey_id'] # Backward compat if 'custom_fields' in data: self.custom_fields = data['custom_fields'] else: self.custom_fields = {'fields': [], 'data': {}} if 'ui' in data: self.ui = data['ui'] else: self.ui = { 'grid': { 'size': 200, 'color': [255, 255, 255] }, 'point': { 'radius': 25, 'color': [255, 255, 0] } } # End Backward compat self.colors = data['colors'] self.classes = data['classes'] self.coordinates = data['metadata']['coordinates'] self.points = {} if 'points' in data: self.points = data['points'] for image in self.points: for class_name in self.points[image]: for p in range(len(self.points[image][class_name])): point = self.points[image][class_name][p] self.points[image][class_name][p] = QtCore.QPointF( point['x'], point['y']) for class_name in data['colors']: self.colors[class_name] = QtGui.QColor(self.colors[class_name][0], self.colors[class_name][1], self.colors[class_name][2]) self.points_loaded.emit(survey_id) self.fields_updated.emit(self.custom_fields['fields']) path = os.path.split(file_name)[0] if self.points.keys(): path = os.path.join(path, list(self.points.keys())[0]) self.load_image(path) def package_points(self, survey_id): count = 0 package = { 'classes': [], 'points': {}, 'colors': {}, 'metadata': { 'survey_id': survey_id, 'coordinates': self.coordinates }, 'custom_fields': self.custom_fields, 'ui': self.ui } package['classes'] = self.classes for class_name in self.colors: r = self.colors[class_name].red() g = self.colors[class_name].green() b = self.colors[class_name].blue() package['colors'][class_name] = [r, g, b] for image in self.points: package['points'][image] = {} for class_name in self.points[image]: package['points'][image][class_name] = [] src = self.points[image][class_name] dst = package['points'][image][class_name] for point in src: p = {'x': point.x(), 'y': point.y()} dst.append(p) count += 1 return (package, count) def relabel_selected_points(self): if self.current_class_name is not None: # for class_name, point in self.selection: for _, point in self.selection: self.add_point(point) self.delete_selected_points() def rename_class(self, old_class, new_class): index = self.classes.index(old_class) del self.classes[index] if new_class not in self.classes: self.colors[new_class] = self.colors.pop(old_class) self.classes.append(new_class) self.classes.sort() else: del self.colors[old_class] for image in self.points: if old_class in self.points[image] and new_class in self.points[ image]: self.points[image][new_class] += self.points[image].pop( old_class) elif old_class in self.points[image]: self.points[image][new_class] = self.points[image].pop( old_class) self.display_points() def reset(self, clear_image=False): self.points = {} self.colors = {} self.classes = [] self.classes = [] self.selection = [] self.coordinates = {} self.custom_fields = {'fields': [], 'data': {}} self.clear() self.directory = '' self.current_image_name = '' self.current_class_name = None self.fields_updated.emit([]) self.points_loaded.emit('') self.image_loaded.emit('', '') self.directory_set.emit('') def remove_class(self, class_name): index = self.classes.index(class_name) del self.colors[class_name] del self.classes[index] for image in self.points: if class_name in self.points[image]: del self.points[image][class_name] self.display_points() def save_coordinates(self, x, y): if self.current_image_name is not None: if self.current_image_name not in self.coordinates: self.coordinates[self.current_image_name] = {'x': '', 'y': ''} self.coordinates[self.current_image_name]['x'] = x self.coordinates[self.current_image_name]['y'] = y def save_custom_field_data(self, field, data): if self.current_image_name is not None: if self.current_image_name not in self.custom_fields['data'][ field]: self.custom_fields['data'][field][self.current_image_name] = '' self.custom_fields['data'][field][self.current_image_name] = data def save_points(self, file_name, survey_id): try: output, _ = self.package_points(survey_id) file = open(file_name, 'w') json.dump(output, file) file.close() except OSError: return False return True def select_points(self, rect): self.selection = [] self.display_points() current = self.points[self.current_image_name] display_radius = self.ui['point']['radius'] for class_name in current: for point in current[class_name]: if rect.contains(point): offset = ((display_radius + 6) // 2) self.addEllipse( QtCore.QRectF(point.x() - offset, point.y() - offset, display_radius + 6, display_radius + 6), self.selected_pen) self.selection.append((class_name, point)) def set_current_class(self, class_index): if class_index is None or class_index >= len(self.classes): self.current_class_name = None else: self.current_class_name = self.classes[class_index] self.display_points() def set_grid_color(self, color): self.ui['grid']['color'] = [color.red(), color.green(), color.blue()] self.display_grid() def set_grid_size(self, size): self.ui['grid']['size'] = size self.display_grid() def set_point_color(self, color): self.ui['point']['color'] = [color.red(), color.green(), color.blue()] self.display_points() def set_point_radius(self, radius): self.ui['point']['radius'] = radius self.display_points() def toggle_grid(self, display): if display: self.show_grid = True self.display_grid() else: self.show_grid = False self.clear_grid() def toggle_points(self, display): if display: self.display_points() self.selection = [] else: self.clear_points()
class BaseMapWidget(ScreenWidget): #map button button = {} button_name = [ 'lock', 'zoomup', 'zoomdown', 'left', 'right', 'up', 'down', 'go' ] lock_status = True button_press_count = {} #map position map_pos = {'x': np.nan, 'y': np.nan} #center map_area = { 'w': np.nan, 'h': np.nan } #witdh(longitude diff) and height(latitude diff) move_pos = {'x': 0, 'y': 0} #current point location = [] point_color = {'fix': None, 'lost': None} #show range from zoom zoom = 2000 #[m] #for CourseProfileGraphWidget zoomlevel = 13 #for SimpleMapWidget #load course course_loaded = False #course points course_points_label = [] #signal for physical button signal_move_x_plus = QtCore.pyqtSignal() signal_move_x_minus = QtCore.pyqtSignal() signal_move_y_plus = QtCore.pyqtSignal() signal_move_y_minus = QtCore.pyqtSignal() signal_zoom_plus = QtCore.pyqtSignal() signal_zoom_minus = QtCore.pyqtSignal() signal_change_move = QtCore.pyqtSignal() #for change_move move_adjust_mode = False move_factor = 1.0 def init_extra(self): self.gps_values = self.config.logger.sensor.values['GPS'] self.gps_sensor = self.config.logger.sensor.sensor_gps self.signal_move_x_plus.connect(self.move_x_plus) self.signal_move_x_minus.connect(self.move_x_minus) self.signal_move_y_plus.connect(self.move_y_plus) self.signal_move_y_minus.connect(self.move_y_minus) self.signal_zoom_plus.connect(self.zoom_plus) self.signal_zoom_minus.connect(self.zoom_minus) self.signal_change_move.connect(self.change_move) def setup_ui_extra(self): #main graph from pyqtgraph self.plot = pg.PlotWidget() self.plot.setBackground(None) self.plot.hideAxis('left') self.plot.hideAxis('bottom') #current point self.current_point = pg.ScatterPlotItem(pxMode=True) self.point_color = { #'fix':pg.mkBrush(color=(0,0,160,128)), 'fix': pg.mkBrush(color=(0, 0, 255)), #'lost':pg.mkBrush(color=(96,96,96,128)) 'lost': pg.mkBrush(color=(128, 128, 128)) } self.point = { 'pos': [np.nan, np.nan], 'size': 20, 'pen': { 'color': 'w', 'width': 3 }, 'brush': self.point_color['lost'] } #self.plot.setMouseEnabled(x=False, y=False) #pg.setConfigOptions(antialias=True) #make buttons self.button['lock'] = QtWidgets.QPushButton("L") self.button['zoomup'] = QtWidgets.QPushButton("+") self.button['zoomdown'] = QtWidgets.QPushButton("-") self.button['left'] = QtWidgets.QPushButton("←") self.button['right'] = QtWidgets.QPushButton("→") self.button['up'] = QtWidgets.QPushButton("↑") self.button['down'] = QtWidgets.QPushButton("↓") self.button['go'] = QtWidgets.QPushButton("Go") for b in self.button_name: self.button[b].setStyleSheet( self.config.gui.style.G_GUI_PYQT_buttonStyle_map) self.button['lock'].clicked.connect(self.switch_lock) self.button['right'].clicked.connect(self.move_x_plus) self.button['left'].clicked.connect(self.move_x_minus) self.button['up'].clicked.connect(self.move_y_plus) self.button['down'].clicked.connect(self.move_y_minus) self.button['zoomdown'].clicked.connect(self.zoom_minus) self.button['zoomup'].clicked.connect(self.zoom_plus) #long press for key in ['lock']: self.button[key].setAutoRepeat(True) self.button[key].setAutoRepeatDelay(1000) self.button[key].setAutoRepeatInterval(1000) self.button[key]._state = 0 self.button_press_count[key] = 0 self.get_max_zoom() def make_item_layout(self): self.item_layout = {} def add_extra(self): pass #override disable def set_minimum_size(self): pass #for expanding row def resize_extra(self): n = self.layout.rowCount() h = int(self.size().height() / n) for i in range(n): self.layout.setRowMinimumHeight(i, h) def set_border(self): self.max_height = 1 self.max_width = 3 #def set_font_size(self): # self.font_size = int(length / 8) def lock_off(self): self.lock_status = False def lock_on(self): self.lock_status = True def switch_lock(self): if self.lock_status: self.lock_off() else: self.lock_on() def change_move(self): if not self.move_adjust_mode: self.move_factor = 16 self.move_adjust_mode = True else: self.move_factor = 1.0 self.move_adjust_mode = False def move_x_plus(self): self.move_x(+self.zoom / 2) def move_x_minus(self): self.move_x(-self.zoom / 2) def move_y_plus(self): self.move_y(+self.zoom / 2) def move_y_minus(self): self.move_y(-self.zoom / 2) def move_x(self, delta): self.move_pos['x'] += delta self.update_extra() def move_y(self, delta): self.move_pos['y'] += delta self.update_extra() def zoom_plus(self): self.zoom /= 2 self.zoomlevel += 1 self.update_extra() def zoom_minus(self): self.zoom *= 2 self.zoomlevel -= 1 self.update_extra() def get_max_zoom(self): if len(self.config.logger.course.distance) == 0: return if self.config.G_MAX_ZOOM != 0: return z = self.zoom dist = self.config.logger.course.distance[-1] if (z / 1000 < dist): while (z / 1000 < dist): z *= 2 z *= 2 else: while (z / 1000 > dist): z /= 2 self.config.G_MAX_ZOOM = z print("MAX_ZOOM", self.config.G_MAX_ZOOM, dist) def load_course(self): pass def update_extra(self): pass
class WorkerSignals(QtCore.QObject): result = QtCore.pyqtSignal(dict)
class DownLoadTaskWidget(QWidget): update_task_signa = QtCore.pyqtSignal() def __init__(self, task: DownloadTask): super().__init__() self.task = task constant.download_task_widget_map[task.chapterInfo.url] = self self.setMinimumHeight(40) self.setMaximumHeight(40) self.groupBox = QGroupBox(self) self.title = QLabel(self.groupBox) self.title.setText(task.comicInfo.title + task.chapterInfo.title) self.title.setGeometry(10, 5, 300, 20) # 下载进度 self.schedule = QLabel(self.groupBox) self.schedule.setText( f"总页数:{len(self.task.imageInfos)} 已下载:{len(self.task.success)} 下载失败:{len(self.task.error)}") self.schedule.setGeometry(310, 5, 200, 20) # 进度条 self.pbar = QProgressBar(self.groupBox) self.pbar.setGeometry(10, 25, 600, 10) self.pbar.setMinimum(0) self.pbar.setMaximum(len(task.imageInfos)) self.pbar.setValue(0) self.update_task_signa.connect(self.update_task_thread) # 状态 self.status = QLabel(self.groupBox) self.status.setGeometry(620, 12, 70, 20) self.status.setText("等待下载") # 按钮 self.button = ButtonQLabel(self.groupBox) self.button.setGeometry(700, 12, 100, 20) self.button.setText("暂停") self.button.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)) button_font = QtGui.QFont() button_font.setUnderline(True) self.button.setFont(button_font) self.button.onclick(self.button_click) def change_status(self): # 按钮逻辑 if self.task.status == 0: self.status.setText("等待下载") self.button.setVisible(False) elif self.task.status == 1: self.button.setVisible(True) self.button.setText("暂停") self.status.setText("正在下载") elif self.task.status == 2 or self.task.status == -3: self.button.setVisible(True) self.button.setText("继续") self.status.setText("暂停") elif self.task.status == -1: self.button.setVisible(False) self.status.setText("下载完成") elif self.task.status == -2: self.button.setVisible(True) self.button.setText("重试") self.status.setText("下载错误") def button_click(self): if self.task.status == 1: # 正在下载,点击暂停 self.task.status = 2 elif self.task.status == 2 or self.task.status == -3: # 暂停 ,点击等待下载,添加到队列 self.task.status = 0 constant.SERVICE.add_task(self.task) elif self.task.status == -2: # 错误,点击重试 constant.SERVICE.add_task(self.task) self.change_status() def update_task_thread(self): self.schedule.setText( f"总页数:{len(self.task.imageInfos)} 已下载:{len(self.task.success)} 下载失败:{len(self.task.error)}") self.pbar.setValue(len(self.task.success)) self.change_status() def update_task(self, task: DownloadTask): self.task = task self.update_task_signa.emit()
class MainWindowWidget(QWidget): """ 主窗口界面 搜索框+tab页 """ load_comic_list_signa = QtCore.pyqtSignal(ComicInfo) def __init__(self, main_window: QWidget): super().__init__() self.search_callback = None # 主题空间 子组件都放这个Widget里 self.centralWidget = QtWidgets.QWidget(main_window) self.centralWidget.setObjectName("centralWidget") # 搜索框 self.souInput = QtWidgets.QLineEdit(self.centralWidget) self.souInput.setGeometry(QtCore.QRect(40, 30, 800, 30)) font = QtGui.QFont() font.setPointSize(22) font.setKerning(True) font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferDefault) self.souInput.setFont(font) self.souInput.setObjectName("souInput") self.souInput.setText("龙珠") self.modBox = CheckableComboBox(self.centralWidget) self.modBox.setGeometry(QtCore.QRect(850, 30, 120, 30)) for k in constant.mod_dist.keys(): if k in constant.mod_list: self.modBox.addItem(QtCore.Qt.CheckState.Checked, k) else: self.modBox.addItem(QtCore.Qt.CheckState.Unchecked, k) # QTabWidget tab页签 self.tabWidget = QtWidgets.QTabWidget(self.centralWidget) self.tabWidget.setGeometry(QtCore.QRect(40, 70, 944, 668)) self.tabWidget.setTabsClosable(True) self.tabWidget.setObjectName("tabWidget") # 下载页面 self.down_tab = QtWidgets.QWidget() self.down_tab.setStatusTip("") self.down_tab.setAutoFillBackground(False) self.down_tab.setObjectName("down_tab") self.tabWidget.addTab(self.down_tab, "下载列表") # 书架页面 self.bookshelf_tab = QtWidgets.QWidget() self.bookshelf_tab.setObjectName("bookshelf_tab") self.tabWidget.addTab(self.bookshelf_tab, "书架") # 搜索结果页面 self.search_tab = QtWidgets.QWidget() self.search_tab.setObjectName("search_tab") self.tabWidget.addTab(self.search_tab, "搜索结果") # None 空按钮,tab签右侧按钮,设置到前面 tbr = self.tabWidget.tabBar().tabButton(0, QTabBar.ButtonPosition.RightSide) self.tabWidget.tabBar().setTabButton(0, QTabBar.ButtonPosition.LeftSide, tbr) self.tabWidget.tabBar().setTabButton(1, QTabBar.ButtonPosition.LeftSide, tbr) self.tabWidget.tabBar().setTabButton(2, QTabBar.ButtonPosition.LeftSide, tbr) # 启用关闭页签的功能 self.tabWidget.tabCloseRequested.connect(self.tab_close) # 默认打开到书架 self.tabWidget.setCurrentIndex(1) # 主体的centralWidget 放到主窗口中 main_window.setCentralWidget(self.centralWidget) # 书架页 self.bookshelfVBoxLayout = QVBoxLayout() self.bookshelfGroupBox = QGroupBox() self.bookshelfScroll = QScrollArea() self.bookshelfLayout = QVBoxLayout(self.bookshelf_tab) self.bookshelfVBoxLayout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop) self.bookshelfGroupBox.setLayout(self.bookshelfVBoxLayout) self.bookshelfScroll.setWidget(self.bookshelfGroupBox) self.bookshelfScroll.setWidgetResizable(True) self.bookshelfLayout.addWidget(self.bookshelfScroll) # 搜索页 self.searchVBoxLayout = QVBoxLayout() self.searchGroupBox = QGroupBox() self.searchScroll = QScrollArea() self.searchLayout = QVBoxLayout(self.search_tab) self.searchVBoxLayout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop) self.searchGroupBox.setLayout(self.searchVBoxLayout) self.searchScroll.setWidget(self.searchGroupBox) self.searchScroll.setWidgetResizable(True) self.searchLayout.addWidget(self.searchScroll) # 下载页 self.downVBoxLayout = QVBoxLayout() self.downGroupBox = QGroupBox() self.downScroll = QScrollArea() self.downLayout = QVBoxLayout(self.down_tab) self.downVBoxLayout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop) self.downGroupBox.setLayout(self.downVBoxLayout) self.downScroll.setWidget(self.downGroupBox) self.downScroll.setWidgetResizable(True) self.downLayout.addWidget(self.downScroll) down_button_layout = QHBoxLayout() self.downLayout.addLayout(down_button_layout) down_button_layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight) all_start = QPushButton() all_start.setText("全部开始") all_stop = QPushButton() all_stop.setText("全部停止") clear_done = QPushButton() clear_done.setText("清理已完成") down_button_layout.addWidget(all_start) down_button_layout.addWidget(all_stop) down_button_layout.addWidget(clear_done) self.souInput.returnPressed.connect(self.input_return_pressed) # 回车搜索 self.load_comic_list_signa.connect(self.search_load_comic_list) # 更新ui的插槽 self.bookshelf_load_comic_list() self.download_callback() def tab_close(self, index): """ 关闭tab,删掉缓存的列表 :param index: tab索引 :return: """ self.tabWidget.removeTab(index) del constant.OPEN_TAB[index - 3] def input_return_pressed(self): """ 搜索框回车函数 :return: """ for i in range(self.searchVBoxLayout.count()): # 清理显示的内容 self.searchVBoxLayout.itemAt(i).widget().deleteLater() constant.mod_list.clear() for i in range(self.modBox.count()): if self.modBox.item_checked(i): constant.mod_list.add(self.modBox.model().item(i).text()) if len(constant.mod_list) > 0: constant.SERVICE.search(self.souInput.text(), self.load_comic_list_signa.emit) # 查询回调出发插槽 self.tabWidget.setCurrentIndex(2) def search_load_comic_list(self, info: ComicInfo): """ 解析的漫画信息,通过回调到本方法,加载页面 :param info: :return: """ # 加载到展示视图,需要判断你,是否还是之前的搜索项 if self.souInput.text() == info.searchKey: comic_info_widget = UIComicListWidget(info, self.tabWidget, self.downVBoxLayout) self.searchVBoxLayout.addWidget(comic_info_widget) def bookshelf_load_comic_list(self): for item in constant.downloaded_comic_map.values(): comic_info_widget = UIComicListWidget(item, self.tabWidget, self.downVBoxLayout) self.bookshelfVBoxLayout.addWidget(comic_info_widget) def download_callback(self): for item in constant.downloaded_task_map.values(): widget = DownLoadTaskWidget(item) widget.update_task(item) self.downVBoxLayout.addWidget(widget)
class UIComicInfoWidget(QWidget): load_chapter_list_signa = QtCore.pyqtSignal(ChapterInfo) load_download_task_signa = QtCore.pyqtSignal(DownloadTask) def __init__(self, comic_info: ComicInfo, down_v_box_layout: QVBoxLayout): super().__init__() self.comic_info = comic_info self.down_v_box_layout = down_v_box_layout self.img_label = QLabel(self) self.img_label.setScaledContents(True) img = QImage.fromData(comic_info.cover) w, h = image_resize(comic_info.cover, width=200) self.img_label.resize(QtCore.QSize(w, h)) self.img_label.setGeometry(10, 10, w, h) self.img_label.setPixmap(QPixmap.fromImage(img)) # self.img_label.setPixmap(QtGui.QPixmap("/Users/bo/my/tmp/老夫子2/第1卷/1.jpg")) self.title = QLabel(self) self.title.setGeometry(220, 10, 100, 40) title_font = QtGui.QFont() title_font.setPointSize(16) title_font.setBold(True) title_font.setUnderline(True) self.title.setFont(title_font) self.title.setText(comic_info.title) self.title.setWordWrap(True) info_font = QtGui.QFont() info_font.setPointSize(14) # 作者 self.author = QLabel(self) self.author.setText("作者 : " + comic_info.author) self.author.setGeometry(220, 50, 150, 40) self.author.setWordWrap(True) self.author.setFont(info_font) # 状态 self.status = QLabel(self) self.status.setText("更新状态 : " + comic_info.status) self.status.setGeometry(220, 90, 150, 40) self.status.setFont(info_font) # 热度 self.heat = QLabel(self) self.heat.setText("热度 : " + str(comic_info.heat)) self.heat.setGeometry(220, 130, 150, 40) self.heat.setFont(info_font) # 类型 self.tip = QLabel(self) self.tip.setText("类型 : " + comic_info.tip) self.tip.setGeometry(220, 170, 150, 40) self.tip.setWordWrap(True) self.tip.setFont(info_font) # web self.domain = QLabel(self) self.domain.setText(f"查看原网页 : {comic_info.domain}") self.domain.setText(f'查看原网页 : <a href="{comic_info.url}">{comic_info.domain}</a>') self.domain.setGeometry(220, 210, 150, 40) self.domain.setOpenExternalLinks(True) self.domain.setFont(info_font) # 描述 self.describe = QLabel(self) self.describe.setText(" " + comic_info.describe) self.describe.setGeometry(10, 320, 350, 330) self.describe.setWordWrap(True) # 对齐方式 self.describe.setAlignment( QtCore.Qt.AlignmentFlag.AlignLeading | QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignTop) # 章节列表,创建一个区域 self.searchHBoxLayout = QHBoxLayout() # self.searchHBoxLayout.addSpacing() self.searchGroupBox = QGroupBox() self.searchGroupBox.setLayout(self.searchHBoxLayout) self.searchScroll = QScrollArea(self) self.searchScroll.setGeometry(370, 10, 574, 590) self.searchScroll.setWidget(self.searchGroupBox) self.searchScroll.setWidgetResizable(True) # 全选 self.check_all = QCheckBox(self) self.check_all.setText("全选") self.check_all.setGeometry(700, 610, 100, 20) self.check_all.stateChanged.connect(self.check_all_fun) # 下载 self.down_button = QPushButton(self) self.down_button.setText("下载") self.down_button.setGeometry(780, 605, 50, 30) self.down_button.clicked.connect(self.download_button_click) self.load_chapter_list_signa.connect(self.load_chapter) self.load_download_task_signa.connect(self.download_callback) # 调用对应的service的接口,获取章节列表 constant.SERVICE.chapter(comic_info, self.load_chapter_list_signa.emit) i = 0 searchVBoxLayout: QVBoxLayout check_box_list: List[QCheckBox] = [] def check_all_fun(self): for check_box in self.check_box_list: check_box.setChecked(self.check_all.isChecked()) def download_callback(self, task: DownloadTask): widget = DownLoadTaskWidget(task) self.down_v_box_layout.addWidget(widget) def download_button_click(self): flag = False for check_box in self.check_box_list: if check_box.isChecked(): constant.SERVICE.parse_image(self.comic_info, check_box.property("chapter_info"), self.load_download_task_signa.emit) if not flag: QMessageBox.information(self, "下载通知", "正在解析选中章节", QMessageBox.StandardButton.Yes) flag = True def load_chapter(self, chapter_info: ChapterInfo): if self.i % 26 == 0: self.searchVBoxLayout = QVBoxLayout() self.searchVBoxLayout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop) # 对齐方式,研究了3个小时 o(╥﹏╥)o self.searchHBoxLayout.addLayout(self.searchVBoxLayout) check_box = QCheckBox() self.check_box_list.append(check_box) check_box.setText(chapter_info.title) check_box.setProperty("chapter_info", chapter_info) task = constant.downloaded_task_map.get(chapter_info.url) if task and task.status == -1: check_box.setStyleSheet('color:red') check_box.setChecked(True) self.searchVBoxLayout.addWidget(check_box) self.i += 1