class MatplotlibWidget(QWidget): def __init__(self, parent = None): QWidget.__init__(self, parent) self.CanvasWindow = parent self.canvas = FigureCanvas(Figure(dpi=DPI)) # attach matplotlib canvas to layout vertical_layout = QVBoxLayout() vertical_layout.addWidget(self.canvas) self.setLayout(vertical_layout) # canvas setup self.canvas.axis = self.canvas.figure.add_subplot(111) self.canvas.figure.subplots_adjust(left=0, right=1, top=1, bottom=0, wspace=0, hspace=0) self.canvas.axis.spines['top'].set_visible(False) self.canvas.axis.spines['left'].set_visible(False) self.canvas.axis.set_xlim(AXIS_LIMIT) self.canvas.axis.set_ylim(AXIS_LIMIT) # variables self.pressed = False self.plotted = False self.pen = False self.N = 0 self.arr_drawing_complex = [] self.time = [] self.arr_coordinates = [] self.line_draw, = self.canvas.axis.plot([], [], c='#eb4034', linewidth=1) self.arr_drawing = np.empty((1,2)) # listeners self.canvas.mpl_connect('button_press_event', self.on_press) self.canvas.mpl_connect('motion_notify_event', self.on_motion) self.canvas.mpl_connect('button_release_event', self.on_release) def on_press(self, event): if len(self.arr_drawing[1:]) > 0: self.animation._stop() if self.plotted: # reset variables self.plotted = False self.arr_drawing = np.empty((1,2)) self.N = 0 # clear and reload subplot self.canvas.axis.clear() self.canvas.axis.set_xlim(AXIS_LIMIT) self.canvas.axis.set_ylim(AXIS_LIMIT) self.canvas.draw() self.pressed = True self.pen = True def on_motion(self, event): if self.pressed: self.plotted = True # plot drawing points coordinates = [event.xdata, event.ydata] self.arr_drawing = np.append(self.arr_drawing, [coordinates], axis=0) self.line_draw.set_data(self.arr_drawing[1:,0],self.arr_drawing[1:,1]) self.canvas.axis.draw_artist(self.line_draw) self.canvas.blit(self.canvas.axis.bbox) self.canvas.flush_events() def on_release(self, event): self.pressed = False self.line_draw.set_data([],[]) if len(self.arr_drawing) > 0: self.runAnimation() def getSizes(self): arr_radius = np.array([item['amplitude'] for item in self.ft.arr_epicycles]) rr_pix = (self.canvas.axis.transData.transform(np.vstack([arr_radius, arr_radius]).T) - self.canvas.axis.transData.transform(np.vstack([np.zeros(self.N), np.zeros(self.N)]).T)) rpix, _ = rr_pix.T size_pt = (2*rpix/DPI*72)**2 return size_pt def runAnimation(self): self.animation = animation.FuncAnimation( self.canvas.figure, self.animate, init_func=self.init, interval=ANIMATION_INTERVAL, blit=True) def init(self): # set new values if self.pen: self.arr_drawing_complex = [complex(coordinates[0], coordinates[1]) for coordinates in self.arr_drawing[1:]] else: self.arr_drawing_complex = [complex(coordinates[0], coordinates[1]) for coordinates in self.arr_drawing[:]] self.N = len(self.arr_drawing_complex) self.CanvasWindow.horizontalSlider.setMaximum(self.N) self.CanvasWindow.horizontalSlider.setValue(self.N) # calculate fourier transform via FFT algorithm self.ft = FourierTransform(self.arr_drawing_complex) self.ft.toEpicycles() # calculate all points in time range from 0 to 2pi self.time = np.linspace(0,2*pi,endpoint = False, num=self.N) self.arr_coordinates = np.array([self.ft.getPoint(dt) for dt in self.time]) # create all components responsible for showing animation self.circle = self.canvas.axis.scatter([],[], fc='None', ec='#9ac7e4', lw=1) self.circle.set_sizes(self.getSizes()) self.line_connect, = self.canvas.axis.plot([], [], c='#9ac7e4', lw=1) self.line_plot, = self.canvas.axis.plot([], [], c='#4c6bd5', lw=2) self.line_plot_all, = self.canvas.axis.plot([], [], c='#4c6bd5', lw=0.5) return [self.circle, self.line_connect, self.line_plot, self.line_plot_all] def animate(self,i): s = self.CanvasWindow.horizontalSlider.value()+1 # update components self.circle.set_offsets(self.arr_coordinates[:,:s][i%self.N,:-1]) self.line_connect.set_data(self.arr_coordinates[:,:s][i%self.N,:,0],self.arr_coordinates[:,:s][i%self.N,:,1]) self.line_plot.set_data(self.arr_coordinates[:,:s][:i%self.N+1,-1,0],self.arr_coordinates[:,:s][:i%self.N+1,-1,1]) self.line_plot_all.set_data(self.arr_coordinates[:,:s][:,-1,0],self.arr_coordinates[:,:s][:,-1,1]) return [self.circle, self.line_connect, self.line_plot, self.line_plot_all]
class FDTD(QMainWindow, Ui_MainWindow): """ FDTD object. """ def __init__(self): super(self.__class__, self).__init__() self.setupUi(self) # Connect to the button self.start_fdtd.clicked.connect(self._start_fdtd) # Set up a figure for the plotting canvas fig = Figure() self.fig = fig self.axes1 = fig.add_subplot(111) self.my_canvas = FigureCanvas(fig) # Add the canvas to the vertical layout self.verticalLayout.addWidget(self.my_canvas) self.addToolBar(QtCore.Qt.TopToolBarArea, NavigationToolbar(self.my_canvas, self)) # TE or TM self.mode = None # Angle of the incident field (degrees) self.incident_angle = None # Number of time steps self.number_of_time_steps = None # Geometry file to use self.geometry_file = None # Width of the Gaussian pulse in time steps self.gaussian_pulse_width = None # Amplitude of the Gaussian pulse self.gaussian_pulse_amplitude = None # Number of PML layers self.number_of_pml = None # Others that will be set during initialization self.nx = None self.ny = None self.dx = None self.dy = None self.dt = None self.mu_r = None self.eps_r = None self.sigma = None self.exi = None self.eyi = None self.dexi = None self.deyi = None self.exs = None self.eys = None self.hzs = None self.dhzi = None self.hxs = None self.hys = None self.dhxi = None self.dhyi = None self.ezs = None self.ezi = None self.dezi = None self.esctc = None self.eincc = None self.edevcn = None self.ecrlx = None self.ecrly = None self.dtmdx = None self.dtmdy = None self.hdhvcn = None self.amplitude_y = None self.amplitude_x = None self.geometry_file = '../Libs/rcs/fdtd.cell' def _start_fdtd(self): """ Update the figure when the user changes an input value. :return: """ # Get the parameters from the form self.mode = self._mode.currentText() self.incident_angle = float(self._incident_angle.text()) self.number_of_time_steps = int(self._number_of_time_steps.text()) self.gaussian_pulse_width = int(self._gaussian_pulse_width.text()) self.gaussian_pulse_amplitude = float( self._gaussian_pulse_amplitude.text()) self.number_of_pml = int(self._number_of_pml.text()) # Read the geometry file and calculate material parameters self.initialize() def initialize(self): """ Initialize the variables for FDTD calculations. :return: """ # Read the geometry file self.read_geometry_file() # Initialize the fields if self.mode == 'TE': self.exi = zeros([self.nx, self.ny]) self.eyi = zeros([self.nx, self.ny]) self.dexi = zeros([self.nx, self.ny]) self.deyi = zeros([self.nx, self.ny]) self.exs = zeros([self.nx, self.ny]) self.eys = zeros([self.nx, self.ny]) self.hzs = zeros([self.nx, self.ny]) self.dhzi = zeros([self.nx, self.ny]) else: self.hxs = zeros([self.nx, self.ny]) self.hys = zeros([self.nx, self.ny]) self.dhxi = zeros([self.nx, self.ny]) self.dhyi = zeros([self.nx, self.ny]) self.ezs = zeros([self.nx, self.ny]) self.ezi = zeros([self.nx, self.ny]) self.dezi = zeros([self.nx, self.ny]) # Initialize the other values self.esctc = zeros([self.nx, self.ny]) self.eincc = zeros([self.nx, self.ny]) self.edevcn = zeros([self.nx, self.ny]) self.ecrlx = zeros([self.nx, self.ny]) self.ecrly = zeros([self.nx, self.ny]) self.dtmdx = zeros([self.nx, self.ny]) self.dtmdy = zeros([self.nx, self.ny]) self.hdhvcn = zeros([self.nx, self.ny]) # Calculate the maximum time step allowed by the Courant stability condition self.dt = 1.0 / (c * (sqrt(1.0 / (self.dx**2) + 1.0 / (self.dy**2)))) for i in range(self.nx): for j in range(self.ny): eps = epsilon_0 * self.eps_r[i][j] mu = mu_0 * self.mu_r[i][j] self.esctc[i][j] = eps / (eps + self.sigma[i][j] * self.dt) self.eincc[i][j] = self.sigma[i][j] * self.dt / ( eps + self.sigma[i][j] * self.dt) self.edevcn[i][j] = self.dt * (eps - epsilon_0) / ( eps + self.sigma[i][j] * self.dt) self.ecrlx[i][j] = self.dt / ( (eps + self.sigma[i][j] * self.dt) * self.dx) self.ecrly[i][j] = self.dt / ( (eps + self.sigma[i][j] * self.dt) * self.dy) self.dtmdx[i][j] = self.dt / (mu * self.dx) self.dtmdy[i][j] = self.dt / (mu * self.dy) self.hdhvcn[i][j] = self.dt * (mu - mu_0) / mu # Amplitude of incident field components self.amplitude_x = -self.gaussian_pulse_amplitude * sin( radians(self.incident_angle)) self.amplitude_y = self.gaussian_pulse_amplitude * cos( radians(self.incident_angle)) # Run the selected mode if self.mode == 'TE': self.te() else: self.tm() def read_geometry_file(self): """ Read the FDTD geometry file. :return: """ # Get the base path for the file base_path = Path(__file__).parent with open((base_path / self.geometry_file).resolve(), 'r') as file: # Header Line 1: Comment _ = file.readline() # Header Line 2: nx ny line = file.readline() line_list = line.split() self.nx = int(line_list[0]) self.ny = int(line_list[1]) # Header Line 3: Comment _ = file.readline() # Header Line 4: dx dy line = file.readline() line_list = line.split() self.dx = float(line_list[0]) self.dy = float(line_list[1]) # Set up the PML areas first self.nx += 2 * self.number_of_pml self.ny += 2 * self.number_of_pml self.mu_r = zeros([self.nx, self.ny]) self.eps_r = zeros([self.nx, self.ny]) self.sigma = zeros([self.nx, self.ny]) # Set up the maximum conductivities sigma_max_x = -3.0 * epsilon_0 * c * log(1e-5) / ( 2.0 * self.dx * self.number_of_pml) sigma_max_y = -3.0 * epsilon_0 * c * log(1e-5) / ( 2.0 * self.dy * self.number_of_pml) # Create the conductivity profile sigma_v = [((m + 0.5) / (self.number_of_pml + 0.5))**2 for m in range(self.number_of_pml)] # Back region for i in range(self.nx): for j in range(self.number_of_pml): self.mu_r[i][j] = 1.0 self.eps_r[i][j] = 1.0 self.sigma[i][j] = sigma_max_y * sigma_v[self.number_of_pml - 1 - j] # Front region for i in range(self.nx): for j in range(self.ny - self.number_of_pml, self.ny): self.mu_r[i][j] = 1.0 self.eps_r[i][j] = 1.0 self.sigma[i][j] = sigma_max_y * sigma_v[ j - (self.ny - self.number_of_pml)] # Left region for i in range(self.number_of_pml): for j in range(self.ny): self.mu_r[i][j] = 1.0 self.eps_r[i][j] = 1.0 self.sigma[i][j] += sigma_max_x * sigma_v[ self.number_of_pml - 1 - i] # Right region for i in range(self.nx - self.number_of_pml, self.nx): for j in range(self.ny): self.mu_r[i][j] = 1.0 self.eps_r[i][j] = 1.0 self.sigma[i][j] += sigma_max_x * sigma_v[ i - (self.nx - self.number_of_pml)] # Read the geometry for i in range(self.number_of_pml, self.nx - self.number_of_pml): for j in range(self.number_of_pml, self.ny - self.number_of_pml): line = file.readline() line_list = line.split() # Relative permeability first self.mu_r[i][j] = float(line_list[0]) # Relative permittivity next self.eps_r[i][j] = float(line_list[1]) # Finally the conductivity self.sigma[i][j] = float(line_list[2]) file.close() def te(self): """ TE mode. :return: """ # Start at time = 0 t = 0.0 # Loop over the time steps for n in range(self.number_of_time_steps): # Update the scattered electric field self.escattered_te(t) # Advance the time by 1/2 time step t += 0.5 * self.dt # Update the scattered magnetic field self.hscattered_te(t) # Advance the time by 1/2 time step t += 0.5 * self.dt # Update the canvas self._update_canvas() # Progress print('{} of {} time steps'.format(n + 1, self.number_of_time_steps)) def tm(self): """ TM mode. :return: """ # Start at time = 0 t = 0.0 # Loop over the time steps for n in range(self.number_of_time_steps): # Update the scattered electric field self.escattered_tm(t) # Advance the time by 1/2 time step t += 0.5 * self.dt # Update the scattered magnetic field self.hscattered_tm(t) # Advance the time by 1/2 time step t += 0.5 * self.dt # Update the canvas self._update_canvas() # Progress print('{} of {} time steps'.format(n + 1, self.number_of_time_steps)) def eincident_te(self, t): """ Calculate the incident electric field for TE mode. :param t: Time (s). :return: """ # Calculate the incident electric field and derivative delay = 0 # Calculate the decay rate determined by Gaussian pulse width alpha = (1.0 / (self.dt * self.gaussian_pulse_width / 4.0))**2 # Calculate the period period = 2.0 * self.dt * self.gaussian_pulse_width # Spatial delay of each cell x_disp = -cos(radians(self.incident_angle)) y_disp = -sin(radians(self.incident_angle)) if x_disp < 0: delay -= x_disp * (self.nx - 2.0) * self.dx if y_disp < 0: delay -= y_disp * (self.ny - 2.0) * self.dy for i in range(self.number_of_pml, self.nx - self.number_of_pml): for j in range(self.number_of_pml, self.ny - self.number_of_pml): distance = i * self.dx * x_disp + j * self.dy * y_disp + delay a = 0 a_prime = 0 tau = t - distance / c if 0 <= tau <= period: a = exp(-alpha * (tau - self.gaussian_pulse_width * self.dt)**2) a_prime = exp(-alpha * (tau - self.gaussian_pulse_width * self.dt) ** 2) \ * (-2.0 * alpha * (tau - self.gaussian_pulse_width * self.dt)) self.exi[i][j] = self.amplitude_x * a self.dexi[i][j] = self.amplitude_x * a_prime self.eyi[i][j] = self.amplitude_y * a self.deyi[i][j] = self.amplitude_y * a_prime def escattered_te(self, t): """ Calculate the scattered electric field for TE mode. :param t: Time (s). :return: """ # Calculate the incident electric field self.eincident_te(t) # Update the x-component electric scattered field for i in range(self.nx - 1): for j in range(self.ny - 1): self.exs[i][j] = self.exs[i][j] * self.esctc[i][j] - self.eincc[i][j] * self.exi[i][j] \ - self.edevcn[i][j] * self.dexi[i][j] + (self.hzs[i][j] - self.hzs[i][j - 1]) \ * self.ecrly[i][j] # Update the y-component electric scattered field for i in range(1, self.nx - 1): for j in range(self.ny - 1): self.eys[i][j] = self.eys[i][j] * self.esctc[i][j] - self.eincc[i][j] * self.eyi[i][j] \ - self.edevcn[i][j] * self.deyi[i][j] - (self.hzs[i][j] - self.hzs[i - 1][j]) \ * self.ecrlx[i][j] def hincident_te(self, t): """ Calculate the incident magnetic field for TE mode. :param t: Time (s). :return: """ # Calculate the incident magnetic field and time derivative delay = 0.0 eta = sqrt(mu_0 / epsilon_0) # Calculate the decay rate determined by Gaussian pulse width alpha = (1.0 / (self.gaussian_pulse_width * self.dt / 4.0))**2 # Calculate the period period = 2.0 * self.gaussian_pulse_width * self.dt # Spatial delay of each cell x_disp = -cos(radians(self.incident_angle)) y_disp = -sin(radians(self.incident_angle)) if x_disp < 0: delay -= x_disp * (self.nx - 2.0) * self.dx if y_disp < 0: delay -= y_disp * (self.ny - 2.0) * self.dy for i in range(self.number_of_pml, self.nx - self.number_of_pml): for j in range(self.number_of_pml, self.ny - self.number_of_pml): distance = i * self.dx * x_disp + j * self.dy * y_disp + delay a_prime = 0 tau = t - distance / c if 0 <= tau <= period: a_prime = exp(-alpha * (tau - self.gaussian_pulse_width * self.dt) ** 2) \ * (-2.0 * alpha * (tau - self.gaussian_pulse_width * self.dt)) self.dhzi[i][j] = self.gaussian_pulse_amplitude * a_prime / eta def hscattered_te(self, t): """ Calculate the scattered magnetic field for TE mode. :param t: Time (s). :return: """ # Calculate the incident magnetic field self.hincident_te(t) # Update the scattered magnetic field for i in range(self.nx - 1): for j in range(self.ny - 1): self.hzs[i][j] = self.hzs[i][j] - (self.eys[i + 1][j] - self.eys[i][j]) * self.dtmdx[i][j] \ + (self.exs[i][j + 1] - self.exs[i][j]) * self.dtmdy[i][j] - self.hdhvcn[i][j] \ * self.dhzi[i][j] def eincident_tm(self, t): """ Calculate the incident electric field for TM mode. :param t: Time (s). :return: """ # Calculate the incident electric field and derivative delay = 0 # Calculate the decay rate determined by Gaussian pulse width alpha = (1.0 / (self.dt * self.gaussian_pulse_width / 4.0))**2 # Calculate the period period = 2.0 * self.dt * self.gaussian_pulse_width # Spatial delay of each cell x_disp = -cos(radians(self.incident_angle)) y_disp = -sin(radians(self.incident_angle)) if x_disp < 0: delay -= x_disp * (self.nx - 2.0) * self.dx if y_disp < 0: delay -= y_disp * (self.ny - 2.0) * self.dy for i in range(self.number_of_pml, self.nx - self.number_of_pml): for j in range(self.number_of_pml, self.ny - self.number_of_pml): distance = i * self.dx * x_disp + j * self.dy * y_disp + delay a = 0 a_prime = 0 tau = t - distance / c if 0 <= tau <= period: a = exp(-alpha * (tau - self.gaussian_pulse_width * self.dt)**2) a_prime = exp(-alpha * (tau - self.gaussian_pulse_width * self.dt) ** 2) \ * (-2.0 * alpha * (tau - self.gaussian_pulse_width * self.dt)) self.ezi[i][j] = self.gaussian_pulse_amplitude * a self.dezi[i][j] = self.gaussian_pulse_amplitude * a_prime def escattered_tm(self, t): """ Calculate the scattered electric field for TM mode. :param t: Time (s). :return: """ # Calculate the incident electric field self.eincident_tm(t) # Update the z-component electric scattered field for i in range(1, self.nx - 1): for j in range(1, self.ny - 1): self.ezs[i][j] = self.ezs[i][j] * self.esctc[i][j] - self.eincc[i][j] * self.ezi[i][j] \ - self.edevcn[i][j] * self.dezi[i][j] + (self.hys[i][j] - self.hys[i - 1][j]) \ * self.ecrlx[i][j] - (self.hxs[i][j] - self.hxs[i][j - 1]) * self.ecrly[i][j] def hincident_tm(self, t): """ Calculate the incident magnetic field for TM mode. :param t: Time (s). :return: """ # Calculate the incident magnetic field and derivative delay = 0 eta = sqrt(mu_0 / epsilon_0) # Calculate the decay rate determined by Gaussian pulse width alpha = (1.0 / (self.dt * self.gaussian_pulse_width / 4.0))**2 # Calculate the period period = 2.0 * self.dt * self.gaussian_pulse_width # Spatial delay of each cell x_disp = -cos(radians(self.incident_angle)) y_disp = -sin(radians(self.incident_angle)) if x_disp < 0: delay -= x_disp * (self.nx - 2.0) * self.dx if y_disp < 0: delay -= y_disp * (self.ny - 2.0) * self.dy for i in range(self.number_of_pml, self.nx - self.number_of_pml): for j in range(self.number_of_pml, self.ny - self.number_of_pml): distance = i * self.dx * x_disp + j * self.dy * y_disp + delay a = 0 a_prime = 0 tau = t - distance / c if 0 <= tau <= period: a = exp(-alpha * (tau - self.gaussian_pulse_width * self.dt)**2) a_prime = exp(-alpha * (tau - self.gaussian_pulse_width * self.dt) ** 2) \ * (-2.0 * alpha * (tau - self.gaussian_pulse_width * self.dt)) self.dhxi[i][j] = self.gaussian_pulse_amplitude * a_prime / eta self.dhyi[i][j] = self.gaussian_pulse_amplitude * a_prime / eta def hscattered_tm(self, t): """ Calculate the scattered magnetic field for TM mode. :param t: :return: """ # Calculate the incident magnetic field self.hincident_tm(t) # Update the X component of the magnetic scattered field for i in range(1, self.nx - 1): for j in range(self.ny - 1): self.hxs[i][j] = self.hxs[i][j] - ( self.ezs[i][j + 1] - self.ezs[i][j]) * self.dtmdx[i][j] for i in range(self.nx - 1): for j in range(1, self.ny - 1): self.hys[i][j] = self.hys[i][j] + ( self.ezs[i + 1][j] - self.ezs[i][j]) * self.dtmdx[i][j] def _update_canvas(self): # Remove the color bar try: self.cbar.remove() except: print('Initial Plot') # Clear the axes for the updated plot self.axes1.clear() # Calculate the total field for plotting etotal = zeros([self.nx, self.ny]) if self.mode == 'TM': for i in range(self.nx): for j in range(self.ny): etotal[i][j] = self.ezi[i][j] + self.ezs[i][j] else: for i in range(self.nx): for j in range(self.ny): extotal = self.exi[i][j] + self.exs[i][j] eytotal = self.eyi[i][j] + self.eys[i][j] etotal[i][j] = sqrt(extotal**2 + eytotal**2) # x and y grid for plotting x = linspace(0, self.nx * self.dx, self.nx) y = linspace(0, self.ny * self.dy, self.ny) x_grid, y_grid = meshgrid(x, y) # Display the results im = self.axes1.pcolor(x_grid, y_grid, abs(etotal), cmap="jet", vmin=0, vmax=self.gaussian_pulse_amplitude) self.cbar = self.fig.colorbar(im, ax=self.axes1, orientation='vertical') self.cbar.set_label('Electric Field (V/m)', size=10) # Set the tick label size self.axes1.tick_params(labelsize=12) # Update the canvas self.my_canvas.draw() self.my_canvas.flush_events()
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()