예제 #1
0
class DistributionFunctionUI(QtWidgets.QMainWindow):
    def __init__(self, argv):
        QtWidgets.QMainWindow.__init__(self)

        self.ui = distfunc_design.Ui_DistfuncUI()
        self.ui.setupUi(self)

        self.plotWindow = PlotWindow(width=800, height=600)
        self.ax = None
        self.radialAx = None
        self.colorbar = None

        self.radialPlotWindow = PlotWindow(width=700, height=400)

        self.anapitchWindow = DistributionFunctionAnalysePitch(self)

        self.maxF = None  # Maximum of distribution function
        self.maxP = None  # Maximum momentum on distribution grid

        self.currentPlotHandle = None
        self.plotHandles = []
        self.linestyles = []
        self.logarithmicPlot = False

        self.generateLineStyles()

        self.filename, self.disttype = self.parseCmdArgs(argv)

        self.loadDistribution(self.filename, typehint=self.disttype)
        self.setupFigure()

        self.bindEvents()

    def parseCmdArgs(self, argv):
        """
        Parse the command-line arguments that were passed to
        this program.

        argv: List of command-line arguments.
        """
        if len(argv) == 0:
            raise Exception(
                "At least the name of the distribution function to load must be specified on startup."
            )

        filename = None
        typehint = None

        def getarg(argv, i):
            i += 1
            if i >= len(argv):
                raise Exception("Expected argument after '--type'.")
            else:
                return argv[i], i

        i = 0
        L = len(argv)
        while i < L:
            if argv[i].startswith('--'):
                if argv[i] == '--type':
                    typehint, i = getarg(argv, i)
                else:
                    raise Exception(
                        "Unrecognized command-line argument: '{}'.".format(
                            argv[i]))
            else:
                if filename is None:
                    filename = argv[i]
                else:
                    raise Exception(
                        "Multiple input files specified on command-line.")

            i += 1

        return filename, typehint

    def analysePitchDistribution(self):
        r = self.ui.sliderRadius.value()
        self.anapitchWindow.setDistribution(r, self.distfunc)
        self.anapitchWindow.show()

    def bindEvents(self):
        self.ui.sliderRadius.valueChanged.connect(self.sliderRadiusChanged)

        self.ui.gbMoments.toggled.connect(self.plotMomentsChanged)
        self.ui.cbVolumeElement.toggled.connect(self.plotMomentsChanged)

        self.ui.rbDistParPerp.toggled.connect(self.plotTypeChanged)
        self.ui.rbDistPXi.toggled.connect(self.plotTypeChanged)
        self.ui.rbDist1D.toggled.connect(self.plotTypeChanged)
        self.ui.rbCumCurrent.toggled.connect(self.plotTypeChanged)
        self.ui.rbSynchrotron.toggled.connect(self.rbSynchrotronChanged)
        self.ui.rbRunaway.toggled.connect(self.rbRunawayChanged)

        self.ui.btnAnalysePitch.clicked.connect(self.analysePitchDistribution)
        self.ui.btnAutomaticY.clicked.connect(self.setAutomaticYLimit)
        self.ui.btnClearKeptDistributions.clicked.connect(
            self.keepDistributionClear)
        self.ui.btnKeepDistribution.clicked.connect(self.keepDistribution)
        self.ui.btnUpdateYAxis.clicked.connect(self.setYLimit)
        self.ui.btnPlotCurrent.clicked.connect(self.plotCurrentDensity)
        self.ui.btnPlotRadprof.clicked.connect(self.plotRadialDensity)
        self.ui.btnMomentProfile.clicked.connect(self.plotMomentProfile)
        self.ui.btnPlotNow.clicked.connect(self.plotNow)

        self.ui.tbMinY.returnPressed.connect(self.setYLimit)
        self.ui.tbMaxY.returnPressed.connect(self.setYLimit)
        self.ui.tbRunawayPc.returnPressed.connect(self.plotNow)

    def closeEvent(self, event):
        self.exit()

    def exit(self):
        self.plotWindow.close()
        self.radialPlotWindow.close()
        self.close()

    def generateLineStyles(self):
        """
        Generate a list of line styles that can be used
        when making 1D plots.
        """
        NCOLORS = 4
        ls = ['-', '--', ':']
        cmap = GeriMap.get()

        for i in range(0, len(ls) * NCOLORS):
            ci = i % NCOLORS
            li = i % len(ls)
            clr = cmap(float(ci / NCOLORS))
            self.linestyles.append((clr, ls[li]))

    def generateMoment(self, P, XI, F):
        """
        Calculate a moment of the distribution function according
        to the settings in the 'Moments' group box in the GUI.

        P:  Momentum grid on which the distribution function is defined.
        XI: Pitch grid on which the distribution function is defined.
        F:  Distribution function.
        """
        rF = F
        logged = False  # Should the returned function be plotted on a logarithmic scale?

        if self.ui.rbSynchrotron.isChecked():
            wavelength = float(self.ui.sbSynchWavelength.value()) * 1e-9
            magfield = self.ui.sbSynchMagneticField.value()
            synch = Bekefi.synchrotron(P, XI, wavelength, magfield)

            rF = synch * rF
        elif self.ui.rbRunaway.isChecked():
            try:
                pc = float(self.ui.tbRunawayPc.text())
                rF[np.where(P < pc)] = np.amin(rF)
                logged = True
            except ValueError:
                QMessageBox.critical(
                    self, 'Invalid format for critical momentum',
                    "The specified value for the critical momentum has an invalid format."
                )
        else:
            print(
                'WARNING: A moment which has not been implemented seems to have been selected. Ignoring...'
            )

        return rF, logged

    def getAngleAveragedDistribution(self, r):
        """
        Returns the 1D function to plot at the given radius.
        This may be just the distribution function, or it may
        be the distribution weighted with some quantity.
        """
        self.logarithmicPlot = False
        p, fp = self.distfunc.getAngleAveragedDistribution(r)

        # This is difficult for 1D distributions...
        if self.ui.gbMoments.isChecked():
            pass

        return p, fp

    def getParPerpDistribution(self, r):
        """
        Returns the 2D function to plot in ppar/pperp coordinates
        at the given radius. This may be just the distribution function,
        or it may be the distribution weighted with some quantity.
        """
        p = np.linspace(0, self.distfunc.getMaxP(), 1000)
        P, XI, F = self.distfunc.eval(r, p)

        PPAR = P * XI
        PPERP = np.sqrt(P**2 - PPAR**2)

        if self.ui.gbMoments.isChecked():
            F, self.logarithmicPlot = self.generateMoment(P, XI, F)

            if self.ui.cbVolumeElement.isChecked():
                F = F * P**2

            if self.logarithmicPlot:
                F = np.log10(np.abs(F))
        else:
            F = np.log10(np.abs(F))
            self.logarithmicPlot = True

        return PPAR, PPERP, F

    def getPXiDistribution(self, r):
        """
        Returns the 2D function to plot in p/xi coordinates at the
        given radius. This may be just the distribution function,
        or it may be the distribution weighted with some quantity.
        """
        p = np.linspace(0, self.distfunc.getMaxP(), 1000)
        P, XI, F = self.distfunc.eval(r, p)

        if self.ui.gbMoments.isChecked():
            F, self.logarithmicPlot = self.generateMoment(P, XI, F)

            if self.ui.cbVolumeElement.isChecked():
                F = F * P**2

            if self.logarithmicPlot:
                F = np.log10(np.abs(F))
        else:
            F = np.log10(np.abs(F))
            self.logarithmicPlot = True

        return P, XI, F

    def getRadius(self):
        """
        The returns the currently selected radius.
        """
        return self.distfunc.getRadius(self.ui.sliderRadius.value())

    def keepDistribution(self):
        self.plotHandles.append(self.currentPlotHandle)
        self.currentPlotHandle = None

    def keepDistributionClear(self):
        for h in self.plotHandles:
            h.remove()

        self.plotHandles = []
        self.currentPlotHandle.remove()
        self.currentPlotHandle = None

        self.plotSelection()

    def loadDistribution(self, filename, typehint=None):
        """
        Load the named distribution function.

        filename: Name of file to load.
        """
        self.ui.lblFileName.setText(os.path.basename(filename))

        # Try to open the distribution using different methods
        flist = [
            self.loadGOCODEDistribution, self.loadCODEDistribution,
            self.loadSOFTDistribution
        ]

        if typehint is not None:
            if typehint.lower() == 'code': flist = [self.loadCODEDistribution]
            elif typehint.lower() == 'gocode':
                flist = [self.loadGOCODEDistribution]
            elif typehint.lower() == 'soft':
                flist = [self.loadSOFTDistribution]
            else:
                print(
                    "WARNING: Unrecognized distribution function type '{}' specified. Ignoring..."
                    .format(typehint))

        found = False
        for f in flist:
            try:
                f(filename)
                found = True
                break
            except Exception as ex:
                if typehint is not None:
                    print(ex)
                    traceback.print_exc()

        if not found:
            QMessageBox.critical(
                self, 'Unrecognized type',
                "The specified file is either corrupt or not a SOFT compatible distribution function."
            )
            self.exit()
            return

        self.loadDistributionUI()

    def loadCODEDistribution(self, filename):
        self.distfunc = CODEDistribution(filename)

        self.ui.lblDistType.setText('CODE')

    def loadGOCODEDistribution(self, filename):
        self.distfunc = GOCODEDistribution(filename)

        self.ui.lblDistType.setText('GO+CODE')

    def loadSOFTDistribution(self, filename):
        self.distfunc = SOFTDistribution(filename)

        self.ui.lblDistType.setText('SOFT')

    def loadDistributionUI(self):
        """
        This function sets up certain parts of the UI relating
        to the distribution function, after the function has been
        loaded.
        """
        nr = self.distfunc.getNr()
        nmom = self.distfunc.getNmomentum()

        self.maxP = self.distfunc.getMaxP()

        if nr is not None: self.ui.lblNRadii.setText(str(nr))
        else:
            raise ValueError(
                "Internal error: Unable to determine the number of radial points in distribution."
            )

        if nmom is not None:
            self.ui.lblNMomentum.setText(str(self.distfunc.getNmomentum()))
        else:
            self.ui.lblNMomentum.setText('[Several]')

        if self.maxP is not None:
            self.ui.lblMaxP.setText(str(self.maxP) + 'mc')
        else:
            self.ui.lblMaxP.setText('[Several]')

        self.ui.sliderRadius.setMaximum(nr - 1)

    def plotSelection(self, vmin=None, vmax=None):
        first = False
        r = self.getRadius()

        # 2D Ppar/Pperp distribution
        if self.ui.rbDistParPerp.isChecked():
            if self.currentPlotHandle is not None:
                self.ax.clear()

            PPAR, PPERP, F = self.getParPerpDistribution(r)

            self.maxF = np.amax(F)
            if self.logarithmicPlot:
                self.maxF = np.power(10, self.maxF)
            if vmax is None:
                vmax = np.amax(F)

            levels = None
            if vmin is None:
                if self.logarithmicPlot:
                    levels = np.linspace(vmax - 20, vmax, 50)
                else:
                    levels = np.linspace(vmax * 1e-20, vmax, 50)
            else:
                if self.logarithmicPlot:
                    levels = np.linspace(vmin, vmax, 50)
                else:
                    levels = np.linspace(vmin, vmax, 50)

            self.currentPlotHandle = self.ax.contourf(PPAR,
                                                      PPERP,
                                                      F,
                                                      cmap='GeriMap',
                                                      levels=levels)

            self.ax.set_xlabel(r'$p_\parallel$')
            self.ax.set_ylabel(r'$p_\perp / mc$')

            self.ax.set_xlim([-self.maxP, self.maxP])
            self.ax.set_ylim([0, self.maxP])
        # D P/XI distribution
        elif self.ui.rbDistPXi.isChecked():
            if self.currentPlotHandle is not None:
                self.ax.clear()

            P, XI, F = self.getPXiDistribution(r)

            self.maxF = np.amax(F)
            if self.logarithmicPlot:
                self.maxF = np.power(10, self.maxF)
            if vmax is None:
                vmax = np.amax(F)

            levels = None
            if vmin is None:
                if self.logarithmicPlot:
                    levels = np.linspace(vmax - 20, vmax, 50)
                else:
                    levels = np.linspace(vmax * 1e-20, vmax, 50)
            else:
                if self.logarithmicPlot:
                    levels = np.linspace(vmin, vmax, 50)
                else:
                    levels = np.linspace(vmin, vmax, 50)

            self.currentPlotHandle = self.ax.contourf(P,
                                                      XI,
                                                      F,
                                                      cmap='GeriMap',
                                                      levels=levels)
            self.ax.set_xlim([0, self.maxP])
            self.ax.set_ylim([-1, 1])

            self.ax.set_xlabel(r'$p$')
            self.ax.set_ylabel(r'$\xi$')
        # 1D angle-averaged distribution
        elif self.ui.rbDist1D.isChecked():
            if self.currentPlotHandle is None:
                lsc = self.linestyles[len(self.plotHandles)]
                self.currentPlotHandle, = self.ax.semilogy([], [],
                                                           lsc[1],
                                                           color=lsc[0],
                                                           linewidth=3)
                first = True

            h = self.currentPlotHandle

            p, fp = self.getAngleAveragedDistribution(r)
            h.set_data(p, fp)
            self.ax.set_xlim([0, self.maxP])
            self.setYLimit()

            self.maxF = np.amax(fp)

            self.ax.set_xlabel(r'$p$')
            self.ax.set_ylabel(r'$f(p)$')
        elif self.ui.rbCumCurrent.isChecked():
            if self.currentPlotHandle is None:
                lsc = self.linestyles[len(self.plotHandles)]
                self.currentPlotHandle, = self.ax.plot([], [],
                                                       lsc[1],
                                                       color=lsc[0],
                                                       linewidth=3)
                first = True

            h = self.currentPlotHandle

            _, p, j = self.distfunc.getCurrentDensity(r=r, cumulative=True)
            if j[0, -1] < 0:
                j = -j

            h.set_data(p, j)
            self.ax.set_xlim([0, self.maxP])

            self.maxF = np.amax(j)
            self.ax.set_ylim([0, self.maxF * 1.1])

            self.ax.set_xlabel(r'$p$')
            self.ax.set_ylabel(
                r"$\int_0^p j(p')\,\mathrm{d}p'$ ($\mathrm{A}\cdot\mathrm{s}\cdot\mathrm{kg}^{-1}\cdot\mathrm{m}^{-3}$)"
            )
        else:
            raise Exception("Unrecognized or no plot type selected.")

        if first:
            self.setAutomaticYLimit()

        self.plotWindow.drawSafe()

    def rbRunawayChanged(self, checked):
        self.ui.lblRunawayPc.setEnabled(checked)
        self.ui.tbRunawayPc.setEnabled(checked)

        self.plotMomentsChanged(checked)

    def rbSynchrotronChanged(self, checked):
        self.ui.lblSynchB0.setEnabled(checked)
        self.ui.lblSynchWavelength.setEnabled(checked)
        self.ui.sbSynchMagneticField.setEnabled(checked)
        self.ui.sbSynchWavelength.setEnabled(checked)

        self.plotMomentsChanged(checked)

    def plotMomentsChanged(self, checked):
        if isinstance(self, QtWidgets.QRadioButton) and not checked:
            self.ui.btnMomentProfile.setEnabled(False)
            return

        self.ui.btnMomentProfile.setEnabled(True)

        self.plotSelection()

    def plotCurrentDensity(self):
        r, nr = self.distfunc.getCurrentDensity()
        self.plotRadialProfile(r, -nr, label=r'$j(r)$ (A/m$^2$)')

    def plotRadialDensity(self):
        r, nr = self.distfunc.getRadialDensity()
        self.plotRadialProfile(r, nr, label=r'$n_e(r)$ (m$^{-3}$)')

    def plotMomentProfile(self):
        mc = 9.109e-31 * 299792458.0
        r = np.copy(self.distfunc._radii)[:, 0]
        nr = np.zeros(r.shape)

        for i in range(0, r.size):
            print('Evaluating moment at r = {:.3f}...'.format(r[i]))

            p = np.linspace(0, self.distfunc.getMaxP(), 1000)
            P, XI, F = self.distfunc.eval(r[i], p)

            if self.ui.gbMoments.isChecked():
                F, _ = self.generateMoment(P, XI, F)

            dp = np.zeros(p.shape)
            dp[:-1] = np.diff(p)
            dp[-1] = dp[-2]

            xi = XI[:, 0]
            dxi = np.zeros(xi.shape)
            dxi[:-1] = np.diff(xi)
            dxi[-1] = dxi[-2]

            DP, DXI = np.meshgrid(dp, dxi)

            nr[i] = np.sum(F * P**2 * DP * DXI * mc**3)

        label = 'UNKNOWN'
        if self.ui.rbSynchrotron.isChecked():
            label = r'$P$ (W/m$^{3?}$)'
        elif self.ui.rbRunaway.isChecked():
            label = r'$n_{\rm RE}$ (m$^{-3}$)'

        self.plotRadialProfile(r, nr, label=label)

    def plotRadialProfile(self, r, nr, label):
        self.radialPlotAx = self.radialPlotWindow.figure.add_subplot(111)
        self.radialPlotAx.plot(r, nr, linewidth=2, color='k')
        self.radialPlotAx.set_xlabel(r'$r$ (m)')
        self.radialPlotAx.set_ylabel(label)
        self.radialPlotAx.set_xlim([0, np.amax(r)])
        self.radialPlotAx.set_ylim([0, np.amax(nr) * 1.1])
        self.radialPlotWindow.drawSafe()

        if not self.radialPlotWindow.isVisible():
            self.radialPlotWindow.show()

    def plotNow(self):
        self.plotTypeChanged(True)

    def plotTypeChanged(self, checked):
        if not checked: return

        # Enable/disable moments group box
        self.ui.gbMoments.setEnabled(not (
            self.ui.rbDist1D.isChecked() or self.ui.rbCumCurrent.isChecked()))

        self.setupFigure()

    def setAutomaticYLimit(self):
        if self.maxF is None: return
        else:
            if self.logarithmicPlot or self.ax.get_yaxis().get_scale(
            ) == 'log':
                self.ui.tbMinY.setText('{:.7e}'.format(self.maxF * 1e-30))
                self.ui.tbMaxY.setText('{:.7e}'.format(self.maxF * 10))
            else:
                self.ui.tbMinY.setText('0')
                self.ui.tbMaxY.setText('{:.7e}'.format(self.maxF * 1.1))

        self.setYLimit()

    def setYLimit(self):
        minY, maxY = 0, 0
        try:
            minY = float(self.ui.tbMinY.text())
            maxY = float(self.ui.tbMaxY.text())
        except Exception as ex:
            print(ex)
            QMessageBox.critical(
                self, 'Invalid Y-axis limits',
                "The Y-axis limits are specified in a invalid format.")
            return

        if self.ui.rbDist1D.isChecked() or self.ui.rbCumCurrent.isChecked():
            if self.logarithmicPlot:
                self.ax.set_ylim([np.log10(minY), np.log10(maxY)])
            else:
                self.ax.set_ylim([minY, maxY])
        else:
            if self.logarithmicPlot:
                self.plotSelection(vmin=np.log10(minY), vmax=np.log10(maxY))
            else:
                self.plotSelection(vmin=minY, vmax=maxY)

        self.plotWindow.drawSafe()

    def setupFigure(self):
        self.plotWindow.figure.clear()
        self.ax = self.plotWindow.figure.add_subplot(111)
        self.currentPlotHandle = None

        if not self.plotWindow.isVisible():
            self.plotWindow.show()

        self.plotSelection()

    def sliderRadiusChanged(self):
        """
        Function called when the value of the radius slider is changed.
        """
        self.ui.lblRadius.setText(str(self.getRadius()))
        self.plotSelection()
예제 #2
0
class BeamsizeMeasurement(QtWidgets.QMainWindow):
    def __init__(self, argv):
        QtWidgets.QMainWindow.__init__(self)

        self.ui = BeamsizeMeasurement_design.Ui_BeamsizeMeasurement()
        self.ui.setupUi(self)

        filename = None
        if len(argv) > 1:
            raise Exception("Too many input arguments given to 'beamsize'.")
        elif len(argv) == 1:
            filename = argv[0]

        self.plotWindow = PlotWindow()

        self.GF = None
        self.beamHandle = None
        self.radius = None

        self.imageContoursHandle = None
        self.imageHandle = None
        self.overlay = None
        self.overlayHandle = None

        self.toggleEnabled(False)
        self.bindEvents()

        if filename is not None and os.path.isfile(filename):
            self.loadFile(filename)

        self.setupImage()

    def bindEvents(self):
        self.ui.btnBrowse.clicked.connect(self.openFile)
        self.ui.btnBrowseOverlay.clicked.connect(self.openOverlay)
        self.ui.btnSaveImage.clicked.connect(self.saveImage)
        self.ui.btnSaveBoth.clicked.connect(self.saveBoth)
        self.ui.btnSaveProfile.clicked.connect(self.saveProfile)

        self.ui.sliderBeamsize.valueChanged.connect(self.sliderBeamsizeChanged)
        self.ui.sliderIntensity.valueChanged.connect(
            self.sliderIntensityChanged)
        self.ui.tbRadialProfile.textChanged.connect(self.radialProfileChanged)

        self.ui.gbRadialProfile.toggled.connect(self.fullProfileToggled)

        self.ui.sliderIntensity.valueChanged.connect(self.intensityChanged)
        self.ui.sliderOverlay.valueChanged.connect(self.updateOverlay)

        self.ui.cbContour.toggled.connect(self.contourToggled)

        self.ui.actionSave.triggered.connect(self.saveImage)
        self.ui.actionExit.triggered.connect(self.exit)

    def closeEvent(self, event):
        self.exit()

    def contourToggled(self):
        enabled = self.ui.cbContour.isChecked()

        self.ui.sliderIntensity.setEnabled(enabled)
        self.ui.lblIntensity_desc.setEnabled(enabled)
        self.ui.lblIntensity.setEnabled(enabled)
        self.ui.lblIntensity0.setEnabled(enabled)
        self.ui.lblIntensity20.setEnabled(enabled)
        self.ui.lblIntensity40.setEnabled(enabled)
        self.ui.lblIntensity60.setEnabled(enabled)
        self.ui.lblIntensity80.setEnabled(enabled)
        self.ui.lblIntensity100.setEnabled(enabled)

        self.updateImage()

    def exit(self):
        self.plotWindow.close()
        self.close()

    def toggleEnabled(self, enabled):
        self.ui.lblBeamRadius.setEnabled(enabled)

        self.ui.sliderBeamsize.setEnabled(enabled)
        self.ui.lblBeamsize.setEnabled(enabled)
        self.ui.lblBeamsize_desc.setEnabled(enabled)
        self.ui.lblBeamsize0.setEnabled(enabled)
        self.ui.lblBeamsize20.setEnabled(enabled)
        self.ui.lblBeamsize40.setEnabled(enabled)
        self.ui.lblBeamsize60.setEnabled(enabled)
        self.ui.lblBeamsize80.setEnabled(enabled)
        self.ui.lblBeamsize100.setEnabled(enabled)

        self.ui.sliderIntensity.setEnabled(enabled)
        self.ui.lblIntensity.setEnabled(enabled)
        self.ui.lblIntensity_desc.setEnabled(enabled)
        self.ui.lblIntensity0.setEnabled(enabled)
        self.ui.lblIntensity20.setEnabled(enabled)
        self.ui.lblIntensity40.setEnabled(enabled)
        self.ui.lblIntensity60.setEnabled(enabled)
        self.ui.lblIntensity80.setEnabled(enabled)
        self.ui.lblIntensity100.setEnabled(enabled)

        self.ui.sliderOverlay.setEnabled(enabled)
        self.ui.lblOverlay_desc.setEnabled(enabled)
        self.ui.tbOverlay.setEnabled(enabled)
        self.ui.btnBrowseOverlay.setEnabled(enabled)
        self.ui.lblOverlay0.setEnabled(enabled)
        self.ui.lblOverlay20.setEnabled(enabled)
        self.ui.lblOverlay40.setEnabled(enabled)
        self.ui.lblOverlay60.setEnabled(enabled)
        self.ui.lblOverlay80.setEnabled(enabled)
        self.ui.lblOverlay100.setEnabled(enabled)
        self.ui.gbRadialProfile.setEnabled(enabled)

    def loadFile(self, filename):
        self.ui.tbGreensFunction.setText(filename)

        self.GF = Green(filename)

        if not self.validateGreensFunction(self.GF):
            return

        # Store radii in centimeters
        self.radius = (self.GF._r - self.GF._r[0]) * 100.0

        self.toggleEnabled(True)
        self.ui.sliderBeamsize.setMaximum(self.GF.nr - 1)
        self.ui.sliderBeamsize.setSliderPosition(self.GF.nr - 1)

        self.updateBeamRadiusLabel()
        self.setupRadialProfile()

    def openFile(self):
        filename, _ = QFileDialog.getOpenFileName(
            parent=self,
            caption="Open SOFT Green's function file",
            filter="SOFT Green's function (*.mat *.h5 *.hdf5);;All files (*.*)"
        )

        if filename:
            self.loadFile(filename)

    def loadOverlay(self, filename):
        self.ui.tbOverlay.setText(filename)
        self.overlay = mpimg.imread(filename)

        if self.overlayHandle is not None:
            self.overlayHandle.remove()

        a = (self.ui.sliderOverlay.value()) / 100.0
        self.overlayHandle = self.imageAx.imshow(self.overlay,
                                                 alpha=a,
                                                 extent=[-1, 1, -1, 1])
        self.plotWindow.drawSafe()

    def openOverlay(self):
        filename, _ = QFileDialog.getOpenFileName(
            parent=self,
            caption="Open overlay image",
            filter="Portable Network Graphics (*.png)")

        if filename:
            self.loadOverlay(filename)

    def updateBeamRadiusLabel(self):
        v = self.ui.sliderBeamsize.value()
        r = self.radius[v]
        p = int(np.round((v / self.radius.size) * 100.0))

        self.ui.lblBeamRadius.setText('{0:.1f} cm'.format(r))
        self.ui.lblBeamsize.setText('{0}%'.format(p))

    def validateGreensFunction(self, gf):
        if gf.getFormat() != 'rij':
            QMessageBox.critical(
                self, 'Invalid input file',
                "The specified Green's function is not of the appropriate format. Expected 'rij', got {0}."
                .format(gf.getFormat()))
            return False

        return True

    def sliderBeamsizeChanged(self):
        self.updateBeamRadiusLabel()

        if self.imageHandle is not None:
            self.updateRadialProfile()

    def sliderIntensityChanged(self):
        v = self.ui.sliderIntensity.value()
        self.ui.lblIntensity.setText('{0}%'.format(v))

        i = float(v) / 100.0

    def setupRadialProfile(self):
        self.radialProfileLayout = QtWidgets.QVBoxLayout(
            self.ui.widgetRadialProfile)

        self.radialProfileCanvas = FigureCanvas(Figure())
        self.radialProfileLayout.addWidget(self.radialProfileCanvas)

        f = self.getRadialProfile()
        self.radialProfileAx = self.radialProfileCanvas.figure.subplots()
        self.radialProfileHandle, = self.radialProfileAx.plot(self.radius, f)

        self.radialProfileAx.set_xlim([0, self.radius[-1]])
        self.radialProfileAx.set_ylim([0, 1.2])

        self.radialProfileAx.set_xlabel(r'$r$ (cm)')
        self.radialProfileAx.set_ylabel(r'Radial density')

        self.radialProfileAx.figure.tight_layout(pad=4.5)

    def updateRadialProfile(self):
        f = self.getRadialProfile()
        self.radialProfileHandle.set_ydata(f)

        maxf = np.amax(f)
        if maxf == 0:
            self.radialProfileAx.set_ylim([0, 1])
        else:
            self.radialProfileAx.set_ylim([0, np.amax(f) * 1.2])
            self.updateImage()

        self.radialProfileCanvas.draw()

    def getRadialProfile(self):
        s = self.ui.tbRadialProfile.toPlainText().strip()
        x = self.radius / self.radius[-1]
        f0 = np.zeros(self.radius.shape)
        a = self.radius[self.ui.sliderBeamsize.value()] / self.radius[-1]

        if not s:
            f = np.ones(self.radius.shape)
        else:
            # Parse string
            f = None
            lcls = {'a': a}
            try:
                f = evaluateExpression(s, x, lcls=lcls)
            except Exception as ex:
                return np.zeros(self.radius.shape)

            # Set negative values to zero
            f = np.where(f < 0, f0, f)

        # Apply step function
        f = np.where(x < a, f, f0)

        return f

    def radialProfileChanged(self):
        self.updateRadialProfile()

    def setupImage(self):
        self.imageAx = self.plotWindow.figure.add_subplot(111)

        dummy = np.zeros(self.GF._pixels)
        self.imageHandle = self.imageAx.imshow(dummy,
                                               cmap='GeriMap',
                                               interpolation=None,
                                               clim=(0, 1),
                                               extent=[-1, 1, -1, 1])
        self.imageAx.get_xaxis().set_visible(False)
        self.imageAx.get_yaxis().set_visible(False)

        if not self.plotWindow.isVisible():
            self.plotWindow.show()

        self.updateImage()

    def updateImage(self):
        # Generate image
        f = self.getRadialProfile()
        img = np.zeros(self.GF._pixels)

        for i in range(0, len(self.radius)):
            img += self.GF[i, :, :] * f[i]

        img = img.T / np.amax(img)

        if self.ui.gbRadialProfile.isChecked():
            self.imageHandle.set_data(img)
        else:
            self.imageHandle.set_data(np.zeros(self.GF._pixels))

        if self.imageContoursHandle is not None:
            self.imageContoursHandle.remove()
            self.imageContoursHandle = None

        if self.ui.cbContour.isChecked():
            threshold = self.ui.sliderIntensity.value() / 100.0
            cntr = skimage.measure.find_contours(img.T, threshold)[0]

            ipix, jpix = img.shape
            i, j = cntr[:, 0], cntr[:, 1]
            i = (i - ipix / 2) / ipix * 2
            j = (-j + jpix / 2) / jpix * 2

            self.imageContoursHandle, = self.imageAx.plot(i, j, 'w--')

        self.plotWindow.drawSafe()

    def fullProfileToggled(self):
        self.updateImage()

    def intensityChanged(self):
        self.updateImage()

    def updateOverlay(self):
        if self.overlayHandle is not None:
            a = float(self.ui.sliderOverlay.value()) / 100.0
            self.overlayHandle.set_alpha(a)
            self.plotWindow.drawSafe()

    def saveImagePNG(self, filename=False):
        """
        Save the currently displayed SOFT image to a PNG file.
        """
        if filename is False:
            filename, _ = QFileDialog.getSaveFileName(
                self,
                caption='Save PNG image',
                filter='Portable Network Graphics (*.png)')

        if filename:
            f = self.getRadialProfile()
            img = np.zeros(self.GF._pixels)

            for i in range(0, len(self.radius)):
                img += self.GF[i, :, :] * f[i]

            img = img.T / np.amax(img)

            cmap = plt.get_cmap('GeriMap')
            im = Image.fromarray(np.uint8(cmap(img) * 255))
            im.save(filename)

    def saveImage(self, filename=False):
        """
        Save the currently displayed SOFT image to a PNG file.
        """
        if filename is False:
            filename, _ = QFileDialog.getSaveFileName(
                self,
                caption='Save image',
                filter=
                'Portable Document Format (*.pdf);;Portable Network Graphics (*.png)'
            )

        if not filename: return

        if filename.endswith('.png'):
            self.saveImagePNG(filename=filename)
            return

        self.imageAx.set_axis_off()
        self.plotWindow.figure.subplots_adjust(top=1,
                                               bottom=0,
                                               right=1,
                                               left=0,
                                               hspace=0,
                                               wspace=0)

        self.imageAx.get_xaxis().set_major_locator(
            matplotlib.ticker.NullLocator())
        self.imageAx.get_yaxis().set_major_locator(
            matplotlib.ticker.NullLocator())

        fcolor = self.plotWindow.figure.patch.get_facecolor()

        self.plotWindow.canvas.print_figure(filename,
                                            bbox_inches='tight',
                                            pad_inches=0,
                                            dpi=300)

    def saveProfile(self, filename=False):
        """
        Saves the current radial profile.
        """
        if filename is False:
            filename, _ = QFileDialog.getSaveFileName(
                self,
                caption='Save image',
                filter='Portable Network Graphics (*.png)')

        if not filename:
            return

        self.radialProfileCanvas.figure.canvas.print_figure(
            filename, bbox_inches='tight')

    def saveBoth(self):
        filename, _ = QFileDialog.getSaveFileName(
            self,
            caption='Save both figures',
            filter=
            'Portable Document Form (*.pdf);;Portable Network Graphics (*.png);;Encapsulated Post-Script (*.eps);;Scalable Vector Graphics (*.svg)'
        )

        if not filename:
            return

        f = filename.split('.')
        filename = str.join('.', f[:-1])
        ext = f[-1]

        if filename.endswith('_image') or filename.endswith('_super'):
            filename = filename[:-6]

        imgname = filename + '_image.' + ext
        supname = filename + '_profile.' + ext

        self.saveImage(filename=imgname)
        self.saveProfile(filename=supname)
예제 #3
0
class DetectorCalibration(QtWidgets.QMainWindow):
    def __init__(self, argv):
        global COLORS

        QtWidgets.QMainWindow.__init__(self)
        self.ui = detcal_design.Ui_DetectorCalibration()
        self.ui.setupUi(self)

        self.magfield = None
        self.image = None
        self.plotWindow = PlotWindow()
        self.toggleEnabled(False)

        if len(argv) > 2:
            QMessageBox.critical(
                self, 'Too many input arguments',
                'Too many input arguments were given. Expected at most 2 arguments.'
            )
            self.exit()

        imagefile = None
        meqfile = None
        for arg in argv:
            if arg.endswith('.png'):
                imagefile = arg
            elif arg.endswith('.h5') or arg.endswith('.mat') or args.endswith(
                    '.hdf5'):
                meqfile = arg
            else:
                QMessageBox.critical(
                    self, 'Unrecognized input file',
                    'The given input file is of an unrecognized type: {0}'.
                    format(arg))
                self.exit()

        i, selindex = 0, 0
        for clr, _ in COLORS.items():
            self.ui.cbColor.addItem(clr)

            if clr == 'white':
                selindex = i

            i += 1

        self.ui.cbColor.setCurrentIndex(selindex)

        if imagefile is not None:
            self.loadImage(imagefile)
        if meqfile is not None:
            self.loadEquilibrium(meqfile)

        self.bindEvents()

    def bindEvents(self):
        self.ui.btnBrowseEq.clicked.connect(self.openEquilibrium)
        self.ui.btnBrowseImage.clicked.connect(self.openImage)
        self.ui.btnRedraw.clicked.connect(self.updateWall)

        self.ui.cbColor.currentIndexChanged.connect(self.toroidalChanged)
        self.ui.sliderToroidal.valueChanged.connect(self.toroidalChanged)
        self.ui.dsbLinewidth.valueChanged.connect(self.toroidalChanged)

    def toggleEnabled(self, enabled=False):
        self.ui.gbDetector.setEnabled(enabled)

        self.ui.gbOverlay.setEnabled(enabled)
        """
        self.ui.sliderToroidal.setEnabled(enabled)
        self.ui.lblToroidal.setEnabled(enabled)
        self.ui.lblTor0.setEnabled(enabled)
        self.ui.lblTor90.setEnabled(enabled)
        self.ui.lblTor180.setEnabled(enabled)
        self.ui.lblTor270.setEnabled(enabled)
        self.ui.lblTor360.setEnabled(enabled)
        """

    def closeEvent(self, event):
        self.exit()

    def exit(self):
        self.plotWindow.close()
        self.close()

    def openEquilibrium(self):
        filename, _ = QFileDialog.getOpenFileName(
            parent=self,
            caption="Open SOFT magnetic equilibrium",
            filter="SOFT Equilibrium Data (*.h5 *.mat)")

        if filename:
            self.loadEquilibrium(filename)

    def loadEquilibrium(self, filename):
        self.ui.tbEquilibrium.setText(filename)
        self.magfield = MagneticField(filename)

        self.toggleEnabled(True)
        self.updateWall()

    def openImage(self):
        filename, _ = QFileDialog.getOpenFileName(
            parent=self,
            caption="Open SOFT magnetic equilibrium",
            filter="Image (*.png)")

        if filename:
            self.loadImage(filename)

    def loadImage(self, filename):
        self.ui.tbImage.setText(filename)
        self.image = mpimg.imread(filename)

        if not self.plotWindow.isVisible():
            self.plotWindow.show()

        self.setImage(self.image)

    def updateWall(self):
        if not self.plotWindow.isVisible():
            self.plotWindow.show()

        try:
            detpos = np.array([
                float(self.ui.tbPosX.text()),
                float(self.ui.tbPosY.text()),
                float(self.ui.tbPosZ.text())
            ])
            detdir = np.array([
                float(self.ui.tbDirX.text()),
                float(self.ui.tbDirY.text()),
                float(self.ui.tbDirZ.text())
            ])
            detdir = detdir / np.linalg.norm(detdir)
            visang = float(self.ui.tbVisang.text())
            tiltAngle = float(self.ui.tbTilt.text())
            self.setWall(detpos, detdir, visang, tiltAngle)
        except ValueError as e:
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setText(e.strerror)
            msg.setWindowTitle('Runtime Error')
            msg.setStandardButtons(QMessageBox.Ok)
            msg.exec_()

    def toroidalChanged(self):
        self.plotWindow.ax = self.gen()
        self.plotWindow.drawSafe()

    def gen(self):
        fig = self.plotWindow.figure

        fig.clear()
        ax = fig.add_subplot(111)
        pixelscale = 1

        if self.magfield is not None:
            pixelscale = np.tan(self.visang) / 2

        if self.image is not None:
            h = self.image.shape[0]
            w = self.image.shape[1]

            extent = []
            if h >= w:
                extent = [
                    -(w / h) * pixelscale, (w / h) * pixelscale, -pixelscale,
                    pixelscale
                ]
            else:
                extent = [
                    -pixelscale, pixelscale, -(h / w) * pixelscale,
                    (h / w) * pixelscale
                ]

            ax.imshow(self.image, extent=extent)

        if self.magfield is not None:
            toffset = self.ui.sliderToroidal.value()
            clr = self.ui.cbColor.currentText()
            linewidth = self.ui.dsbLinewidth.value()

            plotwall(ax,
                     self.magfield.wall,
                     self.detpos,
                     self.detdir,
                     degreesStart=[toffset - 30, toffset + 210],
                     degreesEnd=[toffset - 29, toffset + 211],
                     tiltAngle=self.tiltAngle,
                     color=COLORS[clr],
                     linewidth=linewidth)
            #plotwall(ax, self.magfield.wall, self.detpos, self.detdir, degreesStart=[toffset+190], degreesEnd=[toffset+350], rlim=0.46, spacing=5, tiltAngle=self.tiltAngle, color=COLORS[clr], linewidth=linewidth)
            #plotwall(ax, self.magfield.wall, self.detpos, self.detdir, degreesStart=[toffset+190], degreesEnd=[toffset+350], zuplim=-0.4, spacing=3, tiltAngle=self.tiltAngle, color=COLORS[clr], linewidth=linewidth)

            ax.set_xlim([-pixelscale, pixelscale])
            ax.set_ylim([-pixelscale, pixelscale])

        fig.subplots_adjust(top=1,
                            bottom=0,
                            right=1,
                            left=0,
                            hspace=0,
                            wspace=0)
        ax.set_axis_off()

        return ax

    def plot(self):
        self.plotWindow.ax = self.gen()
        self.plotWindow.drawSafe()

    def setImage(self, image):
        self.image = image
        self.plot()

    def setWall(self, detpos, detdir, visang, tiltAngle):
        self.detpos = detpos
        self.detdir = detdir
        self.visang = visang
        self.tiltAngle = tiltAngle

        self.plot()
예제 #4
0
class GreensFunctionR12(QtWidgets.QMainWindow):

    labels = {
        'gamma': r'$\gamma$',
        'p': r'$p / mc$',
        'ppar': r'$p_{\parallel} / mc$',
        'pperp': r'$p_{\perp} / mc$',
        'thetap': r'$\theta_{\rm p}$ (rad)',
        'ithetap': r'$\theta_{\rm p}$ (rad)',
        'xi': r'$\xi$'
    }

    poltypes = {
        'Intensity': lambda f: (f[0], 0, 1),
        'Horizontal polarization': lambda f: (0.5 * (f[0] + f[1]), 0, 1),
        'Vertical polarization': lambda f: (0.5 * (f[0] - f[1]), 0, 1),
        'Polarization angle': calcpolangle,
        'Polarization fraction': calcpolfrac,
        'Stokes I': lambda f: (f[0], 0, 1),
        'Stokes Q': lambda f: (f[1], -1, 1),
        'Stokes U': lambda f: (f[2], -1, 1),
        'Stokes V': lambda f: (f[3], -1, 1)
    }

    def __init__(self, argv):
        QtWidgets.QMainWindow.__init__(self)

        self.ui = r12_design.Ui_R12()
        self.ui.setupUi(self)

        if len(argv) != 1:
            raise Exception(
                "The Green's function must be specified at startup")

        self.filename = argv[0]
        self.plotWindow = PlotWindow(width=800, height=600)
        self.radialPlotWindow = PlotWindow(width=700, height=400)
        self.ax = None
        self.radialAx = None
        self.colorbar = None

        self.hasStokesParameters = False

        for key in self.poltypes:
            self.ui.cbRadiationType.addItem(key)

        self.loadGreensFunction(self.filename)
        self.setupFigure()

        self.bindEvents()

    def closeEvent(self, event):
        self.exit()

    def exit(self):
        self.plotWindow.close()
        self.radialPlotWindow.close()
        self.close()

    def bindEvents(self):
        self.ui.rbSingleR.toggled.connect(self.toggleSingleSum)
        self.ui.sliderRadius.valueChanged.connect(self.sliderRadiusChanged)
        self.ui.sliderWavelength.valueChanged.connect(
            self.sliderWavelengthChanged)
        self.ui.cbRadiationType.currentTextChanged.connect(
            self.cbRadiationTypeChanged)

        self.ui.btnMark.clicked.connect(self.markSuperParticle)
        self.ui.btnPlotRadialProfile.clicked.connect(self.plotRadialProfile)
        self.ui.btnSave.clicked.connect(self.saveFigure)

    def getGF(self):
        F = None

        FUNC = self.gf.FUNC
        if self.format[0] == 'w':
            idx = self.ui.sliderWavelength.value()
            FUNC = FUNC[idx, :]

        fmin, fmax = 0, 1
        if self.hasStokesParameters:
            FUNC, fmin, fmax = self.getPolFunction()

        if self.format in ['12', 'w12']:
            F = np.copy(FUNC).T
            mx = np.amax(np.abs(F))
            if mx != 0: F /= mx
        elif self.ui.rbSingleR.isChecked():
            idx = self.ui.sliderRadius.value()
            F = np.copy(FUNC[idx]).T
            mx = np.amax(np.abs(F))
            if mx != 0: F /= mx
        else:
            F = np.sum(FUNC, axis=0).T
            mx = np.amax(np.abs(F))
            if mx != 0: F /= mx

        # Locate super particle
        self.getSuperParticle(F)

        return F, fmin, fmax

    def getPolFunction(self):
        tp = self.ui.cbRadiationType.currentText()

        fun = self.poltypes[tp]
        return fun(self.gf.FUNC)

    def getSuperParticle(self, F):
        i, j = np.unravel_index(np.argmax(F), F.shape)
        param1max = self.gf._param1[j]
        param2max = self.gf._param2[i]

        self.ui.lblParam1.setText('{:.4}'.format(param1max))
        self.ui.lblParam2.setText('{:.4}'.format(param2max))
        self.ui.lblSuperEnergy.setText('{:.4} mc²'.format(self.gf.GAMMA[i, j]))
        self.ui.lblSuperPitch.setText('{:.4} rad'.format(self.gf.THETAP[i, j]))

        return param1max, param2max, i, j

    def loadGreensFunction(self, filename):
        self.gf = Green(filename=filename)

        fmt = self.gf.getFormat()
        if fmt == '12':
            self.ui.rbSingleR.setEnabled(False)
        elif fmt not in ['r12', 'wr12']:
            raise Exception(
                "The Green's function has an invalid format: '{}'. Expected '(s)12' or '(s)r12'."
                .format(fmt))

        self.format = fmt
        self.hasStokesParameters = self.gf.stokesparams
        self.ui.cbRadiationType.setEnabled(self.gf.stokesparams)

        self.ui.sliderRadius.setMaximum(self.gf.nr - 1)
        self.ui.sliderRadius.setTickInterval(
            max(1, int(np.round(self.gf.nr / 20))))

        if fmt == 'wr12':
            self.ui.sliderWavelength.setMaximum(self.gf._wavelengths.size - 1)

            self.sliderWavelengthChanged()
        else:
            self.ui.sliderWavelength.hide()
            self.ui.lblWavelengthLbl.hide()
            self.ui.lblWavelength.hide()

        self.ui.lblParam1Name.setText('Parameter 1 ({}):'.format(
            self.gf._param1name))
        self.ui.lblParam2Name.setText('Parameter 2 ({}):'.format(
            self.gf._param2name))

        self.sliderRadiusChanged()

    def markSuperParticle(self):
        F, _, _ = self.getGF()
        m1, m2, _, _ = self.getSuperParticle(F)
        self.ax.plot(m1,
                     m2,
                     'x',
                     color=(0, 1, 0),
                     markersize=10,
                     markeredgewidth=3)
        self.plotWindow.drawSafe()

    def plotRadialProfile(self):
        mc = 9.109e-31 * 299792458
        J = self.gf.getJacobian().T * mc**3
        r = self.gf._r
        fr = np.zeros((r.size, ))
        F = self.gf.FUNC

        for i in range(0, r.size):
            rrr = np.sum(F[i, :, :] * J)
            fr[i] = rrr

        self.radialAx = self.radialPlotWindow.figure.add_subplot(111)
        self.radialAx.plot(r, fr / (r - r[0])**2, linewidth=2)
        self.radialAx.set_xlabel(r'$\mathrm{Major\ radius}\ \rho$')
        self.radialAx.set_ylabel(
            r'$\mathrm{Radial\ intensity}\ \partial I/\partial\rho$')
        self.radialPlotWindow.drawSafe()

        if not self.radialPlotWindow.isVisible():
            self.radialPlotWindow.show()

    def redrawFigure(self):
        F, fmin, fmax = self.getGF()
        r = np.linspace(fmin, fmax, 20)
        self.ax.clear()

        cmap = 'GeriMap'
        if fmin < 0:
            cmap = 'RdBu'

        cntr = self.ax.contourf(self.gf._param1,
                                self.gf._param2,
                                F,
                                levels=r,
                                cmap=cmap,
                                vmin=fmin,
                                vmax=fmax)

        if self.colorbar is None:
            self.colorbar = self.plotWindow.figure.colorbar(cntr)
        else:
            self.colorbar.ax.clear()
            self.colorbar = self.plotWindow.figure.colorbar(
                cntr, cax=self.colorbar.ax)

        self.ax.set_xlabel(self.labels[self.gf._param1name])
        self.ax.set_ylabel(self.labels[self.gf._param2name])

        self.plotWindow.drawSafe()

    def saveFigure(self):
        filename, _ = QFileDialog.getSaveFileName(
            self,
            caption='Save figure',
            filter=
            'Portable Document Format (*.pdf);;Encapsulated PostScript (*.eps);;Portable Network Graphics (*.png);;All files (*.*)'
        )

        if filename:
            self.plotWindow.canvas.print_figure(filename, bbox_inches='tight')

    def setupFigure(self):
        self.ax = self.plotWindow.figure.add_subplot(111)

        if not self.plotWindow.isVisible():
            self.plotWindow.show()

        self.redrawFigure()

    def cbRadiationTypeChanged(self):
        self.redrawFigure()

    def sliderRadiusChanged(self):
        idx = self.ui.sliderRadius.value()
        self.ui.lblRIndex.setText(str(idx))
        self.ui.lblR.setText('r = {:.4}'.format(self.gf._r[idx]))

        if self.ax is not None:
            self.redrawFigure()

    def sliderWavelengthChanged(self):
        idx = self.ui.sliderWavelength.value()
        self.ui.lblWavelength.setText('{} nm'.format(
            self.gf._wavelengths[idx] / 1e-9))

        if self.ax is not None:
            self.redrawFigure()

    def toggleSingleSum(self):
        enbl = self.ui.rbSingleR.isChecked()
        self.ui.sliderRadius.setEnabled(enbl)
        self.ui.lblRIndex.setEnabled(enbl)
        self.ui.lblR.setEnabled(enbl)

        self.redrawFigure()
예제 #5
0
class SingleEnergyPitchIJ(QtWidgets.QMainWindow):
    def __init__(self, argv):
        QtWidgets.QMainWindow.__init__(self)

        self.ui = SingleEnergyPitchIJ_design.Ui_SingleEnergyPitchIJ()
        self.ui.setupUi(self)

        filename = None
        if len(argv) > 1:
            raise Exception("Too many input arguments given to 's12ij'.")
        elif len(argv) == 1:
            filename = argv[0]

        self.plotWindow = PlotWindow()

        self.GF = None
        self.GFintensity = None

        self.superPlotCanvas = None
        self.superPlotLayout = None
        self.superPlotAx = None
        self.superPlotHandle = None
        self.superPlotDomHandle = None
        self.pitchAngles = None
        self.overlay = None
        self.overlayHandle = None

        self.toggleEnabled(False)

        self.bindEvents()

        self.gerimap, _ = registerGeriMap(None)

        if filename is not None and os.path.isfile(filename):
            self.loadFile(filename)

    def bindEvents(self):
        self.ui.btnBrowse.clicked.connect(self.openFile)
        self.ui.btnBrowseOverlay.clicked.connect(self.openOverlay)
        self.ui.btnSaveImage.clicked.connect(self.saveImage)
        self.ui.btnSaveSuper.clicked.connect(self.saveSuper)
        self.ui.btnSaveBoth.clicked.connect(self.saveBoth)

        self.ui.sliderEnergy.valueChanged.connect(self.energyChanged)
        self.ui.sliderPitchAngle.valueChanged.connect(
            self.pitchAngleParameterChanged)
        self.ui.sliderOverlay.valueChanged.connect(self.overlaySliderChanged)

        self.ui.cbUnderlay.toggled.connect(self.toggleOverlayType)

    def closeEvent(self, event):
        self.exit()

    def exit(self):
        self.plotWindow.close()
        self.close()

    def setupSuperPlot(self):
        ymax = 1.1
        z = np.zeros(self.pitchAngles.shape)

        self.superPlotLayout = QtWidgets.QVBoxLayout(self.ui.widgetDistPlot)

        self.superPlotCanvas = FigureCanvas(Figure())
        self.superPlotLayout.addWidget(self.superPlotCanvas)

        self.superPlotAx = self.superPlotCanvas.figure.subplots()
        self.superPlotHandle, = self.superPlotAx.plot(self.pitchAngles,
                                                      z,
                                                      linewidth=2)
        self.superPlotDomHandle, = self.superPlotAx.plot([0, 0], [0, ymax],
                                                         'k--')

        self.superPlotAx.set_xlim([self.pitchAngles[0], self.pitchAngles[-1]])
        self.superPlotAx.set_ylim([0, ymax])
        self.superPlotAx.get_yaxis().set_ticks([])

        self.superPlotAx.set_xlabel(r'$\theta_{\rm p}\ \mathrm{(rad)}$')
        self.superPlotAx.set_ylabel(r'$f(\theta_{\rm p}) / f_{\rm max}$')

        self.superPlotCanvas.figure.tight_layout(pad=2.5)

    def updateSuperPlot(self, f=None):
        ei = self.getEnergyIndex()

        if f is None:
            f = self.getDistributionFunction()

        superParticle = self.GFintensity[ei, :] * f
        superParticle = superParticle / np.amax(superParticle)
        maxpitch = self.pitchAngles[np.argmax(superParticle)]

        self.ui.lblDomPitch.setText('{0:.3f} rad'.format(maxpitch))

        self.superPlotHandle.set_ydata(superParticle)
        self.superPlotDomHandle.set_xdata([maxpitch, maxpitch])

        self.superPlotCanvas.draw()
        self.superPlotCanvas.flush_events()

    def getDistributionFunction(self):
        C = self.ui.sliderPitchAngle.value()

        f = np.exp(C * self.cosPitchAngles)
        return f

    def energyChanged(self):
        ei = self.ui.sliderEnergy.value()
        if len(self.GF._param1.shape) == 2:
            self.ui.lblEnergy.setText('{0:.2f}'.format(self.GF._param1[0][ei]))
        else:
            self.ui.lblEnergy.setText('{0:.2f}'.format(self.GF._param1[ei]))

        f = self.getDistributionFunction()
        self.updateSuperPlot(f=f)
        self.updateImage(f=f)

    def pitchAngleParameterChanged(self):
        self.ui.lblPitchAngle.setText(str(self.ui.sliderPitchAngle.value()))

        f = self.getDistributionFunction()
        self.updateSuperPlot(f=f)
        self.updateImage(f=f)

    def toggleEnabled(self, enabled=False):
        self.ui.lblREEnergy.setEnabled(enabled)
        self.ui.lblREPitchAngle.setEnabled(enabled)

        self.ui.lblEnergy.setEnabled(enabled)
        self.ui.lblPitchAngle.setEnabled(enabled)

        self.ui.sliderEnergy.setEnabled(enabled)
        self.ui.sliderPitchAngle.setEnabled(enabled)

        self.ui.lblEnergyMin.setEnabled(enabled)
        self.ui.lblEnergyMax.setEnabled(enabled)
        self.ui.lblPitchAngleMin.setEnabled(enabled)
        self.ui.lblPitchAngleMax.setEnabled(enabled)

        self.ui.lblOverlay.setEnabled(enabled)
        self.ui.lblOverlayMin.setEnabled(enabled)
        self.ui.lblOverlayMax.setEnabled(enabled)
        self.ui.lblOverlay25.setEnabled(enabled)
        self.ui.lblOverlay50.setEnabled(enabled)
        self.ui.lblOverlay75.setEnabled(enabled)
        self.ui.btnBrowseOverlay.setEnabled(enabled)
        self.ui.tbOverlay.setEnabled(enabled)
        self.ui.sliderOverlay.setEnabled(enabled)

        self.ui.widgetDistPlot.setEnabled(enabled)

    def loadFile(self, filename):
        self.ui.tbFilename.setText(filename)

        self.GF = Green(filename)

        if not self.validateGreensFunction(self.GF):
            return

        self.toggleEnabled(True)
        if len(self.GF._param2.shape) == 2:
            self.pitchAngles = np.abs(self.GF._param2[0])
        else:
            self.pitchAngles = np.abs(self.GF._param2)

        self.cosPitchAngles = np.cos(self.pitchAngles)

        # Sum all pixels of each image
        self.GFintensity = np.sum(self.GF.FUNC, axis=(2, 3)) * np.sin(
            self.pitchAngles)

        self.setupEnergySlider()
        self.setupSuperPlot()
        self.setupImage()

        f = self.getDistributionFunction()
        self.updateSuperPlot(f=f)
        self.updateImage(f=f)

    def loadOverlay(self, filename):
        self.ui.tbOverlay.setText(filename)
        self.overlay = mpimg.imread(filename)

        self.setupOverlay()

    def openFile(self):
        filename, _ = QFileDialog.getOpenFileName(
            parent=self,
            caption="Open SOFT Green's function file",
            filter="SOFT Green's function (*.mat *.h5 *.hdf5);;All files (*.*)"
        )

        if filename:
            self.loadFile(filename)

    def openOverlay(self):
        filename, _ = QFileDialog.getOpenFileName(
            parent=self,
            caption="Open image overlay",
            filter="Portable Network Graphics (*.png)")

        if filename:
            self.loadOverlay(filename)

    def validateGreensFunction(self, gf):
        if gf.getFormat() != '12ij':
            QMessageBox.critical(
                self, 'Invalid input file',
                "The specified Green's function is not of the appropriate format. Expected '12ij', got '{0}'."
                .format(gf.getFormat()))
            return False

        pn = gf.getParameterName('1')
        if pn != 'gamma' and pn != 'p':
            QMessageBox.critical(
                self, 'Invalid input file',
                "The first momentum parameter has an invalid type: '{0}'. Expected either 'gamma' or 'p'."
                .format(pn))
            return False

        return True

    def saveImage(self):
        self.doSaveImage()

    def saveSuper(self):
        self.doSaveSuper()

    def doSaveImage(self, filename=None):
        if filename is None:
            filename, _ = QFileDialog.getSaveFileName(
                self,
                caption='Save synthetic image',
                filter=
                'Portable Document Form (*.pdf);;Portable Network Graphics (*.png);;Encapsulated Post-Script (*.eps);;Scalable Vector Graphics (*.svg)'
            )

        if not filename:
            return

        self.imageAx.set_axis_off()
        self.plotWindow.figure.subplots_adjust(top=1,
                                               bottom=0,
                                               right=1,
                                               left=0,
                                               hspace=0,
                                               wspace=0)

        self.imageAx.get_xaxis().set_major_locator(
            matplotlib.ticker.NullLocator())
        self.imageAx.get_yaxis().set_major_locator(
            matplotlib.ticker.NullLocator())

        fcolor = self.plotWindow.figure.patch.get_facecolor()

        self.plotWindow.canvas.print_figure(filename,
                                            bbox_inches='tight',
                                            pad_inches=0,
                                            dpi=300)

    def doSaveSuper(self, filename=None):
        if filename is None:
            filename, _ = QFileDialog.getSaveFileName(
                self,
                caption='Save super particle',
                filter=
                'Portable Document Form (*.pdf);;Portable Network Graphics (*.png);;Encapsulated Post-Script (*.eps);;Scalable Vector Graphics (*.svg)'
            )

        if not filename:
            return

        self.superPlotCanvas.print_figure(filename, bbox_inches='tight')

    def saveBoth(self):
        filename, _ = QFileDialog.getSaveFileName(
            self,
            caption='Save both figures',
            filter=
            'Portable Document Form (*.pdf);;Portable Network Graphics (*.png);;Encapsulated Post-Script (*.eps);;Scalable Vector Graphics (*.svg)'
        )

        if not filename:
            return

        f = filename.split('.')
        filename = str.join('.', f[:-1])
        ext = f[-1]

        if filename.endswith('_image') or filename.endswith('_super'):
            filename = filename[:-6]

        imgname = filename + '_image.' + ext
        supname = filename + '_super.' + ext

        self.doSaveImage(filename=imgname)
        self.doSaveSuper(filename=supname)

    def setupEnergySlider(self):
        if len(self.GF._param1.shape) == 2:
            vmin, vmax, vn = self.GF._param1[0][0], self.GF._param1[0][
                -1], self.GF._param1[0].size
        else:
            vmin, vmax, vn = self.GF._param1[0], self.GF._param1[
                -1], self.GF._param1.size

        if self.GF.getParameterName('1') == 'gamma':
            self.ui.lblREEnergy.setText('Runaway energy (mc²)')
        else:
            self.ui.lblREEnergy.setText('Runaway momentum (mc)')

        self.ui.lblEnergyMin.setText('{0:.2f}'.format(vmin))
        self.ui.lblEnergyMax.setText('{0:.2f}'.format(vmax))
        self.ui.lblEnergy.setText('{0:.2f}'.format(vmin))

        self.ui.sliderEnergy.setMinimum(0)
        self.ui.sliderEnergy.setMaximum(vn - 1)
        self.ui.sliderEnergy.setSingleStep(1)

    def getEnergyIndex(self):
        return self.ui.sliderEnergy.value()

    def setupImage(self):
        lbl = ''.join(random.choices(string.ascii_uppercase, k=4))
        self.imageAx = self.plotWindow.figure.add_subplot(111, label=lbl)

        a = 1
        if self.overlayHandle is not None:
            self.setupOverlay()
            if not self.ui.cbUnderlay.isChecked():
                a = 1 - (self.ui.sliderOverlay.value()) / 100.0

        dummy = np.zeros(self.GF._pixels)
        self.imageHandle = self.imageAx.imshow(dummy,
                                               cmap=self.gerimap,
                                               alpha=a,
                                               interpolation=None,
                                               clim=(0, 1),
                                               extent=[-1, 1, -1, 1],
                                               zorder=1)
        self.imageAx.get_xaxis().set_visible(False)
        self.imageAx.get_yaxis().set_visible(False)

        if not self.plotWindow.isVisible():
            self.plotWindow.show()

        self.plotWindow.drawSafe()

    def setupOverlay(self):
        if self.overlayHandle is not None:
            self.overlayHandle.remove()

        self.overlayHandle = self.imageAx.imshow(self.overlay,
                                                 extent=[-1, 1, -1, 1],
                                                 zorder=0)
        self.updateImage()
        self.plotWindow.drawSafe()

    def toggleOverlayType(self):
        ic = self.ui.cbUnderlay.isChecked()
        if not ic:
            self.gerimap, _ = registerGeriMap(None)
        else:
            self.gerimap, _ = registerGeriMap(transparencyThreshold=0.25)

        self.ui.sliderOverlay.setEnabled(not ic)
        self.ui.lblOverlayMin.setEnabled(not ic)
        self.ui.lblOverlay25.setEnabled(not ic)
        self.ui.lblOverlay50.setEnabled(not ic)
        self.ui.lblOverlay75.setEnabled(not ic)
        self.ui.lblOverlayMax.setEnabled(not ic)

        self.setupImage()

        f = self.getDistributionFunction()
        self.updateSuperPlot(f=f)
        self.updateImage(f=f)

    def overlaySliderChanged(self):
        self.updateImage()

    def updateImage(self, f=None):
        ei = self.getEnergyIndex()

        if f is None:
            f = self.getDistributionFunction()

        g = self.GF[ei, :, :, :]
        I = 0
        for i in range(0, f.size):
            I += g[i, :, :] * f[i]

        I = I.T / np.amax(I)

        self.imageHandle.set_data(I)

        if self.ui.cbUnderlay.isChecked() or self.overlayHandle is None:
            self.imageHandle.set_alpha(1)
        else:
            a = float(self.ui.sliderOverlay.value()) / 100.0
            self.imageHandle.set_alpha(1 - a)

        self.plotWindow.drawSafe()
예제 #6
0
class GreensFunctionIJ(QtWidgets.QMainWindow):

    WIDTH = 550
    HEIGHT = 450

    def __init__(self, argv):
        QtWidgets.QMainWindow.__init__(self)

        self.setupUi()

        if len(argv) != 1:
            raise Exception(
                "The Green's function must be specified at startup")

        self.filename = argv[0]

        # Create plot window
        self.plotWindow = PlotWindow()

        self.imageAx = None
        self.overlayHandle = None

        # Combobox used for select polarization quantity to plot
        self.stokesbox = None

        # Overlay controls
        self.tbOverlay = None
        self.btnOverlay = None
        self.lblOverlaySlider = None
        self.sliderOverlay = None

        # Load Green's function
        self.gf = Green(filename=self.filename)
        nparams, dims = self.classifyGreensFunction(self.gf)

        self.buildControls(dims, self.gf)
        self.setDetails()

        self.setWindowTitle("Green's function with image")
        self.setupImage()

    def closeEvent(self, event):
        self.exit()

    def exit(self):
        self.plotWindow.close()
        self.close()

    def buildControls(self, dims, gf):
        i = 0
        for d in dims:
            if d == '1':
                self.buildControl(index=i,
                                  coordname=self.getCoordinateName(
                                      gf._param1name),
                                  vals=gf._param1)
            elif d == '2':
                self.buildControl(index=i,
                                  coordname=self.getCoordinateName(
                                      gf._param2name),
                                  vals=gf._param2)
            elif d == 'r':
                self.buildControl(index=i, coordname='Radius', vals=gf._r)
            elif d == 's':
                self.buildStokes(index=i, coordname='Polarization quantity')
            elif d == 'w':
                self.buildControl(index=i,
                                  coordname='Wavelength',
                                  vals=gf._wavelengths)
            else:
                raise Exception(
                    "Unrecognized or unsupported Green's function format: '{}'."
                    .format(d))

            i += 1

        self.buildOverlay(index=i)

    def buildControl(self, index, coordname, vals):
        vmin = np.amin(vals)
        vmax = np.amax(vals)
        vn = vals.size
        self.paramValues.append(vals)

        gb = QtWidgets.QGroupBox(self.centralwidget)
        gb.setTitle(coordname)
        self.controlGroupboxes.append(gb)

        vl = QtWidgets.QVBoxLayout(gb)

        lbl = QtWidgets.QLabel(gb)
        font = QtGui.QFont()
        font.setPointSize(14)
        lbl.setFont(font)
        lbl.setText('{}'.format(vmin))
        lbl.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing
                         | QtCore.Qt.AlignVCenter)
        self.paramLabels.append(lbl)

        hs = QtWidgets.QSlider(gb)
        hs.setOrientation(QtCore.Qt.Horizontal)
        hs.setMinimum(0)
        hs.setMaximum(vn - 1)
        hs.setTickPosition(QtWidgets.QSlider.TicksBelow)
        hs.setTickInterval(1)
        self.paramSliders.append(hs)

        hs.valueChanged.connect(self.sliderChanged)

        vl.addWidget(lbl)
        vl.addWidget(hs)

        # Insert groupbox into window
        idx = self.verticalLayout.count() - 1
        self.verticalLayout.insertWidget(idx, gb)

        self.HEIGHT += gb.height()
        self.resize(self.WIDTH, self.HEIGHT)

    def buildOverlay(self, index):
        hl = QtWidgets.QHBoxLayout()

        lbl = QtWidgets.QLabel(self.centralwidget)
        lbl.setText("Overlay:")

        tb = QtWidgets.QLineEdit(self.centralwidget)
        tb.setReadOnly(True)
        self.tbOverlay = tb

        btn = QtWidgets.QPushButton(self.centralwidget)
        btn.setText('Browse...')
        self.btnOverlay = btn
        self.btnOverlay.clicked.connect(self.openOverlay)

        lblOverlaySlider = QtWidgets.QLabel(self.centralwidget)
        lblOverlaySlider.setText('50%')
        lblOverlaySlider.setAlignment(QtCore.Qt.AlignRight)
        self.lblOverlaySlider = lblOverlaySlider

        slider = QtWidgets.QSlider(self.centralwidget)
        slider.setOrientation(QtCore.Qt.Horizontal)
        slider.setMinimum(0)
        slider.setMaximum(100)
        slider.setValue(50)
        slider.setTickPosition(QtWidgets.QSlider.TicksBelow)
        slider.setTickInterval(5)
        self.sliderOverlay = slider

        slider.valueChanged.connect(self.sliderOverlayChanged)

        hl.addWidget(tb)
        hl.addWidget(btn)

        self.verticalLayout.addWidget(lbl)
        self.verticalLayout.addLayout(hl)
        self.verticalLayout.addWidget(lblOverlaySlider)
        self.verticalLayout.addWidget(slider)

        self.HEIGHT += tb.height() + lbl.height() + slider.height(
        ) + lblOverlaySlider.height()
        self.resize(self.WIDTH, self.HEIGHT)

    def buildStokes(self, index, coordname):
        cb = QtWidgets.QComboBox(self.centralwidget)
        self.stokesbox = cb

        cb.addItem("Polarization angle")
        cb.addItem("Polarization fraction")
        cb.addItem("Stokes I")
        cb.addItem("Stokes Q")
        cb.addItem("Stokes U")
        cb.addItem("Stokes V")

        cb.setCurrentIndex(2)
        cb.currentIndexChanged.connect(self.redrawFigure)

        self.verticalLayout.insertWidget(index, cb)

        self.HEIGHT += cb.height()
        self.resize(self.WIDTH, self.HEIGHT)

    def classifyGreensFunction(self, gf):
        dims = gf.format

        if dims[-2] != 'i' or dims[-1] != 'j':
            raise Exception(
                "Invalid format of Green's function. Format string must end in 'ij'."
            )

        # Just pick out the interesting dimensions
        dims = dims[:-2]
        nparams = len(dims)

        # Append Stoke's dimension?
        if gf.stokesparams:
            dims = 's' + dims
            nparams += 1

        return nparams, dims

    def getCoordinateName(self, s):
        """
        Converts a SOFT parameter name to a proper parameter label.
        """
        if s == "gamma": return "Energy (γ)"
        elif s == "p": return "Momentum (p)"
        elif s == "ppar": return "Parallel momentum"
        elif s == "pperp": return "Perpendicular momentum"
        elif s == "thetap": return "Pitch angle (θ)"
        elif s == "ithetap": return "Pitch angle (θ)"
        elif s == "xi": return "Pitch (ξ)"
        else: return "<UNKNOWN>"

    def getSelectedGreensFunction(self):
        """
        Returns the appropriate image to draw based on
        how the GUI controls are set.
        """
        Fmax, Fmin = None, 0
        F = self.gf.FUNC
        colormap = 'GeriMap'

        # Compute select polarization quantity
        if self.stokesbox is not None:
            val = self.stokesbox.currentText()

            if val == "Polarization angle":
                F = 0.5 * np.arctan2(F[2], F[1]) * 180 / np.pi
                F[np.where(F < -45)] += 180
                Fmax, Fmin = 135, -45
                colormap = 'RdBu'
            elif val == "Polarization fraction":
                F = np.sqrt(F[1]**2 + F[2]**2) / F[0]
                F[np.where(np.isnan(F))] = 0
                Fmax = 1
            elif val == "Stokes I":
                F = F[0]
            elif val == "Stokes Q":
                F = F[1]
                fmax = max(abs(np.amax(F)), abs(np.amin(F)))
                Fmax, Fmin = fmax, -fmax
                colormap = 'RdBu'
            elif val == "Stokes U":
                F = F[2]
                fmax = max(abs(np.amax(F)), abs(np.amin(F)))
                Fmax, Fmin = fmax, -fmax
                colormap = 'RdBu'
            elif val == "Stokes V":
                F = F[3]
                fmax = max(abs(np.amax(F)), abs(np.amin(F)))
                Fmax, Fmin = fmax, -fmax
                colormap = 'RdBu'
            else:
                raise Exception(
                    "INTERNAL ERROR: Unrecognized polarization quantity select: '{}'."
                    .format(val))

        if self.wfgb.isChecked():  # Draw with weight function
            print('Not supported yet...')
            return np.zeros(self.gf._pixels)
        else:
            indices = list()
            for s in self.paramSliders:
                F = F[s.value()]

            if len(F.shape) != 2:
                raise Exception(
                    'INTERNAL ERROR: F does not have the expected shape.')

            if Fmax is None:
                F /= np.amax(F)
                Fmax = 1

            return F.T, Fmax, Fmin, colormap

    def loadOverlay(self, filename):
        self.tbOverlay.setText(filename)
        self.overlay = mpimg.imread(filename)

        self.setupOverlay()

    def openOverlay(self):
        filename, _ = QFileDialog.getOpenFileName(
            parent=self,
            caption="Open image overlay",
            filter="Portable Network Graphics (*.png)")

        if filename:
            self.loadOverlay(filename)

    def redrawFigure(self):
        F, Fmax, Fmin, cmap = self.getSelectedGreensFunction()
        self.imageHandle.set_data(F)
        self.imageHandle.set_clim(vmin=Fmin, vmax=Fmax)
        self.imageHandle.set_cmap(cmap)
        self.plotWindow.drawSafe()

    def setDetails(self):
        self.lblFilename.setText(self.filename)

        # Green's function size
        totsize = self.gf.FUNC.size * 8
        fsize = totsize
        prefixes = ['ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi']
        pfi = -1
        while fsize > 1024:
            fsize /= 1024.0
            pfi += 1

        prefix = ''
        if pfi >= 0:
            prefix = prefixes[pfi]

        self.lblGFSize.setText('{:.1f} {:s}B ({:d} bytes)'.format(
            fsize, prefix, totsize))

        # Format
        self.lblFormat.setText(self.gf.format)

        # Number of pixels
        hpix, vpix = self.gf._pixels[0], self.gf._pixels[1]
        self.lblPixels.setText('{} × {} pixels'.format(hpix, vpix))

    def setupImage(self):
        self.imageAx = self.plotWindow.figure.add_subplot(111)

        F, Fmax, Fmin, cmap = self.getSelectedGreensFunction()
        self.imageHandle = self.imageAx.imshow(F,
                                               cmap=cmap,
                                               interpolation=None,
                                               clim=(Fmin, Fmax),
                                               extent=[-1, 1, -1, 1])
        self.imageAx.get_xaxis().set_visible(False)
        self.imageAx.get_yaxis().set_visible(False)

        self.colorbar = self.plotWindow.figure.colorbar(self.imageHandle,
                                                        ax=self.imageAx)

        if not self.plotWindow.isVisible():
            self.plotWindow.show()

        self.plotWindow.drawSafe()

    def setupOverlay(self):
        if self.overlayHandle is not None:
            self.overlayHandle.remove()

        val = self.sliderOverlay.value() / 100.0
        self.overlayHandle = self.imageAx.imshow(self.overlay,
                                                 alpha=val,
                                                 extent=[-1, 1, -1, 1],
                                                 zorder=100)
        self.plotWindow.drawSafe()

    def setupUi(self):
        self.resize(self.WIDTH, self.HEIGHT)

        self.centralwidget = QtWidgets.QWidget(self)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)

        # Control groupboxes
        self.controlGroupboxes = list()
        self.paramLabels = list()
        self.paramSliders = list()
        self.paramValues = list()

        # Information groupbox
        gb = QtWidgets.QGroupBox(self.centralwidget)
        gb.setTitle("Green's function details")

        # (declare labels)
        self.lblFilename = QtWidgets.QLabel(gb)
        self.lblGFSize = QtWidgets.QLabel(gb)
        self.lblFormat = QtWidgets.QLabel(gb)
        self.lblPixels = QtWidgets.QLabel(gb)

        vli = QtWidgets.QVBoxLayout(gb)

        h1 = QtWidgets.QHBoxLayout()
        h1.addWidget(QtWidgets.QLabel('File name:', gb))
        h1.addWidget(self.lblFilename)

        h2 = QtWidgets.QHBoxLayout()
        h2.addWidget(QtWidgets.QLabel('Function size: ', gb))
        h2.addWidget(self.lblGFSize)

        h3 = QtWidgets.QHBoxLayout()
        h3.addWidget(QtWidgets.QLabel('Pixels:', gb))
        h3.addWidget(self.lblPixels)

        h4 = QtWidgets.QHBoxLayout()
        h4.addWidget(QtWidgets.QLabel('Format: ', gb))
        h4.addWidget(self.lblFormat)

        vli.addLayout(h1)
        vli.addLayout(h2)
        vli.addLayout(h3)
        vli.addLayout(h4)

        # Distribution function window
        self.wfgb = QtWidgets.QGroupBox(self.centralwidget)
        self.wfgb.setCheckable(True)
        self.wfgb.setChecked(False)
        self.wfgb.setTitle('Weight function')

        vl = QtWidgets.QVBoxLayout(self.wfgb)

        font = QtGui.QFont()
        font.setFamily("Droid Sans Mono")

        txt = QtWidgets.QPlainTextEdit(self.wfgb)
        txt.setFont(font)
        txt.setMaximumSize(QtCore.QSize(16777215, 300))

        frm = QtWidgets.QFrame(self.wfgb)
        frm.setMinimumSize(QtCore.QSize(0, 200))
        frm.setFrameShape(QtWidgets.QFrame.StyledPanel)
        frm.setFrameShadow(QtWidgets.QFrame.Raised)

        vl.addWidget(txt)
        vl.addWidget(frm)

        self.verticalLayout.addWidget(gb)
        self.verticalLayout.addWidget(self.wfgb)
        self.setCentralWidget(self.centralwidget)

        self.wfgb.toggled.connect(self.toggleWeightFunction)

    def sliderChanged(self):
        for i in range(0, len(self.paramSliders)):
            idx = self.paramSliders[i].value()
            val = self.paramValues[i][idx]
            self.paramLabels[i].setText('{}'.format(val))

        self.redrawFigure()

    def sliderOverlayChanged(self):
        val = self.sliderOverlay.value() / 100.0
        self.lblOverlaySlider.setText('{}%'.format(self.sliderOverlay.value()))

        if self.overlayHandle is not None:
            self.overlayHandle.set_alpha(val)

        self.plotWindow.drawSafe()

    def toggleWeightFunction(self):
        for gb in self.controlGroupboxes:
            gb.setEnabled(not self.wfgb.isChecked())