diff --git a/demonstration scripts/fixing_things.py b/demonstration scripts/fixing_things.py new file mode 100644 index 0000000..86dd445 --- /dev/null +++ b/demonstration scripts/fixing_things.py @@ -0,0 +1,23 @@ +import numpy as np +import sys +sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Documents/Published/FourierOptics GitHub release/') +# sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') +from fourierOptics import * + +if __name__ == '__main__': + # Initiate MICADO paraxial model + mcd = ParaxialMICADO() + # mcd.plot_ray_trace() + + print(mcd.platescale) + # Initiate WFE object + wfe = WavefrontError(mcd.n, mcd.m) + + # Generate PSD surfaces + psd = wfe.generate_height_map(200e-6, 2, 1) + + # Add it to ADC-P1 + mcd.ELT_ADC.p1.add_wavefront_error(psd) + + # Generate the PSF and plot it + E, x, y = mcd.propagate(l=1.35e-3,surface=-1, zoom=True, show=True) # image_output=[-.4,.4,-.4,.4] \ No newline at end of file diff --git a/demonstration scripts/thesis_propagation_figures.py b/demonstration scripts/thesis_propagation_figures.py index 0c88bb7..5a2715c 100644 --- a/demonstration scripts/thesis_propagation_figures.py +++ b/demonstration scripts/thesis_propagation_figures.py @@ -1,6 +1,7 @@ import numpy as np import sys -sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') +# sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') +sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Documents/Published/FourierOptics GitHub release/') from fourierOptics import * import matplotlib.pyplot as plt from pdb import set_trace @@ -18,7 +19,8 @@ if __name__ == '__main__': confList = [ ( 1, [-0.4, 0.4, -0.4, 0.4], '02_fourier_ELT-IFP.pdf'), ( 2, [-50, 50, -50, 50], '03_fourier_ELT-COL.pdf'), ( 4, [-50, 50, -50, 50], '04_fourier_ELT-MCD-ADC-P2.pdf'), - (-1, [-1.2, 1.2, -1.2, 1.2], '05_fourier_ELT-MCD-DET.pdf')] + # (-1, [-1.2, 1.2, -1.2, 1.2], '05_fourier_ELT-MCD-DET.pdf')] + (-1, [-1.2/2.669, 1.2/2.669, -1.2/2.669, 1.2/2.669], '05_fourier_ELT-MCD-DET.pdf')] for i in range(len(confList)): @@ -26,7 +28,7 @@ if __name__ == '__main__': coordinates = confList[i][1] savename = confList[i][2] E, x, y = mcd.propagate(l=1.35e-3, surface=s, image_output=coordinates, - zoom=False, show=False, save=savename) + zoom=False, show=True, save=False) Eout, x, y = mcd.propagate(l=1.35e-3, surface=0, zoom=False, show=False) x /= 1e3 # convert to m @@ -47,5 +49,5 @@ if __name__ == '__main__': _ax2.set_ylabel('y [m]') _fig.colorbar(_im2, ax=_ax2, label='Phase') plt.tight_layout() - plt.savefig('01_fourier_ELT-M1.pdf', bbox_inches='tight', transparent=True) + # plt.savefig('01_fourier_ELT-M1.pdf', bbox_inches='tight', transparent=True) plt.close()#show() \ No newline at end of file diff --git a/fourierOptics/__init__.py b/fourierOptics/__init__.py index 14e3a7d..1058796 100644 --- a/fourierOptics/__init__.py +++ b/fourierOptics/__init__.py @@ -3,9 +3,9 @@ from .fourier import * from .gaussian import * from .opticalTrain import * from .optics import * +from .micado import * from .profiles import * from .psfFitter import * from .wavefrontErrors import * from .misc import * -from .micado import * from .atmosphere import * \ No newline at end of file diff --git a/fourierOptics/__pycache__/__init__.cpython-38.pyc b/fourierOptics/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..8d93eef Binary files /dev/null and b/fourierOptics/__pycache__/__init__.cpython-38.pyc differ diff --git a/fourierOptics/__pycache__/__init__.cpython-39.pyc b/fourierOptics/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..109ad39 Binary files /dev/null and b/fourierOptics/__pycache__/__init__.cpython-39.pyc differ diff --git a/fourierOptics/__pycache__/atmosphere.cpython-38.pyc b/fourierOptics/__pycache__/atmosphere.cpython-38.pyc new file mode 100644 index 0000000..c1ba06f Binary files /dev/null and b/fourierOptics/__pycache__/atmosphere.cpython-38.pyc differ diff --git a/fourierOptics/__pycache__/atmosphere.cpython-39.pyc b/fourierOptics/__pycache__/atmosphere.cpython-39.pyc new file mode 100644 index 0000000..f38e2f0 Binary files /dev/null and b/fourierOptics/__pycache__/atmosphere.cpython-39.pyc differ diff --git a/fourierOptics/__pycache__/fourier.cpython-38.pyc b/fourierOptics/__pycache__/fourier.cpython-38.pyc new file mode 100644 index 0000000..67167a4 Binary files /dev/null and b/fourierOptics/__pycache__/fourier.cpython-38.pyc differ diff --git a/fourierOptics/__pycache__/fourier.cpython-39.pyc b/fourierOptics/__pycache__/fourier.cpython-39.pyc new file mode 100644 index 0000000..1074b18 Binary files /dev/null and b/fourierOptics/__pycache__/fourier.cpython-39.pyc differ diff --git a/fourierOptics/__pycache__/gaussian.cpython-38.pyc b/fourierOptics/__pycache__/gaussian.cpython-38.pyc new file mode 100644 index 0000000..b674bb5 Binary files /dev/null and b/fourierOptics/__pycache__/gaussian.cpython-38.pyc differ diff --git a/fourierOptics/__pycache__/gaussian.cpython-39.pyc b/fourierOptics/__pycache__/gaussian.cpython-39.pyc new file mode 100644 index 0000000..846d980 Binary files /dev/null and b/fourierOptics/__pycache__/gaussian.cpython-39.pyc differ diff --git a/fourierOptics/__pycache__/micado.cpython-38.pyc b/fourierOptics/__pycache__/micado.cpython-38.pyc new file mode 100644 index 0000000..b9879b0 Binary files /dev/null and b/fourierOptics/__pycache__/micado.cpython-38.pyc differ diff --git a/fourierOptics/__pycache__/micado.cpython-39.pyc b/fourierOptics/__pycache__/micado.cpython-39.pyc new file mode 100644 index 0000000..45c3a4c Binary files /dev/null and b/fourierOptics/__pycache__/micado.cpython-39.pyc differ diff --git a/fourierOptics/__pycache__/misc.cpython-38.pyc b/fourierOptics/__pycache__/misc.cpython-38.pyc new file mode 100644 index 0000000..d80488e Binary files /dev/null and b/fourierOptics/__pycache__/misc.cpython-38.pyc differ diff --git a/fourierOptics/__pycache__/misc.cpython-39.pyc b/fourierOptics/__pycache__/misc.cpython-39.pyc new file mode 100644 index 0000000..efbc57b Binary files /dev/null and b/fourierOptics/__pycache__/misc.cpython-39.pyc differ diff --git a/fourierOptics/__pycache__/opticalTrain.cpython-38.pyc b/fourierOptics/__pycache__/opticalTrain.cpython-38.pyc new file mode 100644 index 0000000..36d3337 Binary files /dev/null and b/fourierOptics/__pycache__/opticalTrain.cpython-38.pyc differ diff --git a/fourierOptics/__pycache__/opticalTrain.cpython-39.pyc b/fourierOptics/__pycache__/opticalTrain.cpython-39.pyc new file mode 100644 index 0000000..340150f Binary files /dev/null and b/fourierOptics/__pycache__/opticalTrain.cpython-39.pyc differ diff --git a/fourierOptics/__pycache__/optics.cpython-38.pyc b/fourierOptics/__pycache__/optics.cpython-38.pyc new file mode 100644 index 0000000..15681f7 Binary files /dev/null and b/fourierOptics/__pycache__/optics.cpython-38.pyc differ diff --git a/fourierOptics/__pycache__/optics.cpython-39.pyc b/fourierOptics/__pycache__/optics.cpython-39.pyc new file mode 100644 index 0000000..9299800 Binary files /dev/null and b/fourierOptics/__pycache__/optics.cpython-39.pyc differ diff --git a/fourierOptics/__pycache__/profiles.cpython-38.pyc b/fourierOptics/__pycache__/profiles.cpython-38.pyc new file mode 100644 index 0000000..a09b122 Binary files /dev/null and b/fourierOptics/__pycache__/profiles.cpython-38.pyc differ diff --git a/fourierOptics/__pycache__/profiles.cpython-39.pyc b/fourierOptics/__pycache__/profiles.cpython-39.pyc new file mode 100644 index 0000000..9caa77b Binary files /dev/null and b/fourierOptics/__pycache__/profiles.cpython-39.pyc differ diff --git a/fourierOptics/__pycache__/psfFitter.cpython-38.pyc b/fourierOptics/__pycache__/psfFitter.cpython-38.pyc new file mode 100644 index 0000000..e0d0d80 Binary files /dev/null and b/fourierOptics/__pycache__/psfFitter.cpython-38.pyc differ diff --git a/fourierOptics/__pycache__/psfFitter.cpython-39.pyc b/fourierOptics/__pycache__/psfFitter.cpython-39.pyc new file mode 100644 index 0000000..d4a578a Binary files /dev/null and b/fourierOptics/__pycache__/psfFitter.cpython-39.pyc differ diff --git a/fourierOptics/__pycache__/wavefrontErrors.cpython-38.pyc b/fourierOptics/__pycache__/wavefrontErrors.cpython-38.pyc new file mode 100644 index 0000000..93a2685 Binary files /dev/null and b/fourierOptics/__pycache__/wavefrontErrors.cpython-38.pyc differ diff --git a/fourierOptics/__pycache__/wavefrontErrors.cpython-39.pyc b/fourierOptics/__pycache__/wavefrontErrors.cpython-39.pyc new file mode 100644 index 0000000..5ee68f9 Binary files /dev/null and b/fourierOptics/__pycache__/wavefrontErrors.cpython-39.pyc differ diff --git a/fourierOptics/atmosphere.py b/fourierOptics/atmosphere.py index ba74c9f..4ee368a 100644 --- a/fourierOptics/atmosphere.py +++ b/fourierOptics/atmosphere.py @@ -1,6 +1,8 @@ import numpy as np import scipy.special as spc from .misc import * +import pyfftw +import os ''' Various functions to simulate a seeing limited PSF. These functions are @@ -53,6 +55,45 @@ def fried_r0(wvl, seeing): return r0 +def r0converter(r0, lambda1, lambda2): + ''' + Equivalent to anisocado.psf_utils.r0Converter() + + Converts a r0 defined at some wavelength lambda1, + into a r0' at lambda2. + Lambda1 and 2 shall be in the SAME unit. + Returned r0 will be in the SAME unit than the input one. + Example:: + r0_K = r0Converter(0.12, 500, 2200) + ''' + return r0 * ( lambda2 / lambda1 )**( 6/5.) + + +def r0_at_observed_zenith_distance(r0_at_zenith, zenith_distance): + ''' + The seeing/r0 actually perceived by a telescope depends on the zenith + distance. This function converts a r0 given at zenith into the real r0 + actually observed by the telescope. + + Parameters: + ----------- + r0_at_zenith - Float or Array + Fried length r0 at zenith (degrees) in the wavelength of + interest. See also fried_r0() and r0converter(). + zenith_distance - Float or Array. + Observed zenith distance (degrees) + + Returns: + r0 - Float or Array + Observed r0 at the observed zenith distance + + ''' + z = zenith_distance * np.pi / 180 # the same, in radians + r0 = r0_at_zenith * np.cos(z)**(3./5) + return r0 + + + def airmass(z): ''' Converts zenith angle in degrees to airmass @@ -83,7 +124,7 @@ def spectrum(k, r0, L0=25): return spectrum -def add_seeing(wvl, sng, field_tuple): +def add_seeing(wvl, sng, field_tuple, focal_length=3200): ''' Computes the seeing limited PSF for a supplied amplitude distribution. @@ -96,6 +137,8 @@ def add_seeing(wvl, sng, field_tuple): Atmospheric seeing in arcseconds. field_tuple - Tuple Contains the PSF information (amplitude array, x coordinates, y coordinates) + focal_length - float + Focal length in mm of the telescope used. Returns: psf_seeing - Array @@ -105,8 +148,8 @@ def add_seeing(wvl, sng, field_tuple): r01 = fried_r0(wvl, seeing=sng) * 1e3 A_psf, x_psf, y_psf = field_tuple n,m = np.shape(x_psf) - fx = x_psf / (3200*wvl) - fy = y_psf / (3200*wvl) + fx = x_psf / (focal_length*wvl) + fy = y_psf / (focal_length*wvl) dk = find_pixel_scale(fx) # von-Karman spectrum of the atmosphere for the given frequencies in k-space @@ -133,3 +176,398 @@ def add_seeing(wvl, sng, field_tuple): return psf_seeing + + + +def add_scao(wvl, sng, field_tuple, focal_length=2059940, perfect_scao=False, + dactu=540.3, offx=0, offy=0): + ''' + After adding seeing, we would like to add Adaptive Optics to the system. + Based on the 'anisocado' package, I will try to reproduce an AO corrected + PSF for MICADO, with a nice seeing halo around it. + + Some of the functions from anisocado will be reused or modified to make it + work for me. + + Parameters + ---------- + wvl - Float + Wavelength in mm corresponding to the supplied PSF + sng - Float + Atmospheric seeing in arcseconds. + field_tuple - Tuple + Contains the PSF information (amplitude array, x coordinates, y coordinates) + focal_length - float + Focal length in mm of the telescope used. + perfect_scao - bool + Whether or not SCAO errors should be considered. If true, time delay, + anisoplanatism and others are added. + dactu - float + Distance between actuators on the DM in millimeters. + offx, offy - float + Angular offset in arcseconds from the SCAO guide star relative to the + observed target. This will simulate anisoplanatism. + + Returns: + psf_seeing - Array + 2D array containing the new seeing limited PSF information. + + ''' + L0 = 25*1e3 # outer scale parameter of the turbulence cells (in mm) + rms_wfe_mm = 100. * 1e-6 # Overall system wavefront error + + r01 = fried_r0(wvl, seeing=sng) * 1e3 + A_psf, x_psf, y_psf = field_tuple + n,m = np.shape(x_psf) + fx = x_psf / (focal_length*wvl) + fy = y_psf / (focal_length*wvl) + dk = find_pixel_scale(fx) + + + # von-Karman spectrum of the atmosphere for the given frequencies in k-space + W_atm = spectrum(np.hypot(fx.real, fy.real), r0=r01, L0=L0) + W_atm[int(n/2),int(m/2)] = 0 + + # M4 is the ELT deformable mirror that is used for SCAO + M4 = define_DM_FrequencyArea(fy, fx, dactu=dactu) + + # [SKIPPED] W_aniso = anisoplanaticSpectrum(Cn2h, layerAltitude, L0, offx, offy, wvl, fx, fy, W_atm, M4) + + # Assuming the correction is perfect within the controlled frequencies + W_fit = W_atm.copy() + W_fit[M4] = 0.0 + + if perfect_scao: + # Effect of isoplanatic errors + layerAltitude, Cn2h = get_Cn2h() + W_aniso = get_anisoplanatic_spectrum(Cn2h, layerAltitude, offx, offy, fx, fy, W_atm, M4) + + W_combined = W_fit + W_aniso + + + else: + # Alias effects, primarily at the edges of the DM + W_alias = get_aliasing_spectrum(fy, fx, r0=r01, L0=L0, M4=M4) + + # Effect of isoplanatic errors + layerAltitude, Cn2h = get_Cn2h() + W_aniso = get_anisoplanatic_spectrum(Cn2h, layerAltitude, offx, offy, fx, fy, W_atm, M4) + + # Effect of something... + W_bp = get_Bp_spectrum(fy, fx, W_atm=W_atm, DM=M4) + + # Effect of overall wavefront error contribution. Static. + W_other = get_wfe_spectrum(rms_wfe_mm, M4, dk, wvl) + + # W_aniso is so much smaller than all the other contributors! + W_combined = W_fit + W_aniso + W_alias + W_bp + W_other + + # Compute the atmospheric OTF + # According to Roddier 1981, the atmospheric OTF can be derived from the phase structure function + # assuming that the phase is stationary accross the pupil and behaves as a Gaussian variable + # Bphi = np.fft.fft2(np.fft.ifftshift(W_combined))*dk**2 + Bphi = fftw_forward(np.fft.ifftshift(W_combined))*dk**2 + Dphi = np.fft.ifftshift((2 * (Bphi[0,0] - Bphi))) # ifftshift in Bphi en Dphi! + otf_ao = np.exp(-0.5*Dphi) + + # Compute the OTF of the supplied PSF + # We can derive the OTF from the inverse Fourier transform of the PSF + psf = np.abs(A_psf)**2 + otf_psf = np.fft.fftshift(fftw_inverse(np.fft.fftshift(psf))) + otf_psf = otf_psf/otf_psf.max() + + # Compute the seeing-limited PSF + # Multiplication of OTF and then Fourier transform + otf = otf_psf * otf_ao + psf_seeing = np.fft.fftshift(fftw_forward(np.fft.ifftshift(otf))) + return psf_seeing + + +def define_DM_FrequencyArea(kx, ky, rotdegree=0, dactu=540.3): + ''' + From anisocado.psf_utils.defineDmFrequencyArea() + + : spatial frequency produced by the + function computeSpatialFreqArrays() (metres^-1) + : idem kx + : rotation of M4 (degrees) + : value of actuator pitch of M4 (metres) + + The function returns the 2D domain of spatial frequencies which can be + impacted by M4, i.e. the M4 compensation domain. + Underlying assumtions are than M4 is of an hexagonal pattern, with an + inter-actuator distance set by default to 54.03 cm (=540.3 mm). + Result is returned with a frequency corner-centred representation. + ''' + + # cut-off frequency + fc = (1./np.sqrt(3)) / dactu + + # mask frequency definition + A = np.pi/3 # 60 degrees + A0 = rotdegree * np.pi / 180 + msk = np.abs(np.cos(A0)*ky+np.sin(A0)*kx) : list of normalised (np.sum(Cn2h)==1.0) strengh of + turbulence of each layer (no unit) + : list of altitudes of the turbulence layers (metres) + : value of the outer scale (metres) + : off-axis distance of the star along X axis (arcsec) + : idem along Y axis (arcsec) + : wavelength (metres) + : spatial frequency produced by + the function computeSpatialFreqArrays() (metres^-1) + : idem kx + : turbulent spectrum from function computeWiener() + : frequency domain of M4 (boolean) computed by the + function M4 = defineDmFrequencyArea(kx, ky, 0) + Computes the phase spectrum (spatial frequencies) contribution due to the + anisoplanatism in the AO compensation for a source located off-axis from + the SCAO guide star by some amount (offx, offy). + In input, the distribution of the turbulence in altitude is given. + Result is returned with a frequency corner-centred representation. + ''' + + # number of turbulent layers involved in that computation + nlayers = len(Cn2h) + + # conversion arcsec to radians + RASC = 206264.8062471 + + # prepare memory alloc for transfer function of anisoplanatism + Haniso = np.zeros(kx.shape) + + # loop over turbulent layers, summing transfer function of each layer + for i in range(nlayers): + dx = layerAltitude[i] * offx / RASC # shift in millimetres on the layer in X + dy = layerAltitude[i] * offy / RASC # idem, in Y + tmp = (2j*np.pi*dx)*kx + (2j*np.pi*dy)*ky + Haniso += Cn2h[i] * np.abs(1 - np.exp( tmp ))**2 + + # now applying the transfer function on the Wiener spectrum, only in the + # spatial frequency range of M4 + Waniso = np.zeros(Haniso.shape) + Waniso[DM] = Haniso[DM] * atm_psd[DM] + + return Waniso + + +def get_Bp_spectrum(kx, ky, W_atm, DM, V=10, Fe=500, tret=4e-3, gain=0.3): + ''' + From anisocado.psf_utils.computeBpSpectrum() + + V = 10. - Wind speed in m/s + Fe = 500. Hz - response rate + tret = 0.004 - time delay in seconds + gain = 0.3 - gain of the control loop + f = computeBpSpectrum(kx, ky, V, Fe, tret, gain, W, M4) + ''' + k = np.sqrt(kx*kx + ky*ky) + nu = k * V / np.sqrt(2) + Wbp = hcor(nu, Fe, tret, gain, 500) * W_atm + Wbp[np.logical_not(DM)] = 0. + return Wbp + + +def hcor(freq, Fe, tret, G, BP, an=True): + ''' + From anisocado.psf_utils.hcor() + + ***** Fonction extraite de STYC le 9 Jan 2013 ***** + The option an=1 sets the integrator to "analog". Doing this, an + extra 1/2 frame delay is added compared to case of the numeric + integrator an=0. + is the delay expressed as a *time in seconds*, between the + end of the integration and the start of the command. + ''' + + Te = 1. / Fe + p = 1j * 2 * np.pi * freq + 1e-12 + + Hint = 1./(1-np.exp(-p*Te)) # numeric integrator + Hccd = (1.-np.exp(-p*Te))/(p*Te) # echant bloqueur avec retard 1/2 trame + Hdac = Hccd # echant bloqueur avec retard 1/2 trame + Hret = np.exp(-p*tret) + Hmir = 1./(1. + 1j*freq/BP) + Hbo = Hint * Hccd * Hdac * Hret * Hmir + Hcor = 1./abs(1 + Hbo*G)**2 + return Hcor + + +def get_wfe_spectrum(wfe_rms, M4, uk, wavelength): + ''' + : number of nm rms + : frequency domain of M4 (boolean) computed by the + function M4 = defineDmFrequencyArea(kx, ky, 0) + : # size of the 'spatial frequency pixel' in m^-1 + : wavelength (metres) + Example:: + N = 512 + pixelSize = 4.2 + L0 = 25.0 + offx, offy = (34., 12.) + wavelength = 1.65e-6 + kx, ky, uk = computeSpatialFreqArrays(N, pixelSize, wavelength) + M4 = defineDmFrequencyArea(kx, ky, 0) + nmRms = 250. + f = otherSpectrum(nmRms, M4, uk, wavelength) + ''' + fact = 2 * np.pi * wfe_rms / uk / wavelength + fact = fact**2 + tot = np.sum(M4) + Wothers = np.zeros(M4.shape) + Wothers[M4] = fact / tot + + return Wothers + + + +def get_Cn2h(myProfile='EsoMedian'): + ''' + From anisocado.psf_utils.get_atmospheric_turbulence() + + Returns the relative level of turbulence at a given height + Note: The np.sum(Cn2h) = 1.0 + Turbulence profile have been taken from ESO-258292: "Relevant Atmospheric + Parameters for E-ELT AO Analysis and Simulations". + The following 3 turbulence profile are currently available: + * ``EsoQ1`` + Best atmospheric conditions - First Quartile Armazones atmospheric + turbulence profile + * ``EsoMedian`` [also ``officialEsoMedian``] + Median atmospheric conditions - Median Armazones atmospheric turbulence + profile + * ``EsoQ4`` + Worst atmospheric conditions - First Quartile Armazones atmospheric + turbulence profile + Additional turbulence profiles include: + * ``oldEso`` + The old ESO profile from before 2010. Cn2h paramateres are taken from + ref. E-SPE-ESO-276-0206_atmosphericparameters + * ``gendron`` + The Gendron profile. Short, fast. Saves CPU. Carbon efficient. + The np.sum(Cn2h) = 1.0 is hyper-guaranteed here. + + Parameters + ---------- + myProfile : str, optional + Profile name: ['EsoQ1', 'EsoMedian', 'EsoQ4', 'oldEso', 'gendron'] + + Returns + ------- + layerAltitude : list of floats + [m] height of layer above ground + Cn2h : list of floats + Relative strength of turbulence + ''' + + layerAltitude, Cn2h = [], [] + + if myProfile == 'oldEso': + layerAltitude = [47., 140, 281, 562, 1125, 2250, 4500, 9000, 18000.] + Cn2h = [0.5224, 0.026, 0.0444, 0.116, 0.0989, + 0.0295, 0.0598, 0.043, 0.06] + + elif myProfile == 'officialEsoMedian' or myProfile == 'EsoMedian' : + layerAltitude = [30, 90, 150, 200, 245, 300, 390, 600, 1130, 1880, 2630, + 3500, 4500, 5500, 6500, 7500, 8500, 9500, 10500, 11500, + 12500, 13500, 14500, 15500, 16500, 17500, 18500, 19500, + 20500, 21500, 22500, 23500, 24500, 25500, 26500] + Cn2h = [24.2, 12, 9.68, 5.9, 4.73, 4.73, 4.73, 4.73, 3.99, 3.24, 1.62, + 2.6, 1.56, 1.04, 1, 1.2, 0.4, 1.4, 1.3, 0.7, 1.6, 2.59, 1.9, + 0.99, 0.62, 0.4, 0.25, 0.22, 0.19, 0.14, 0.11, 0.06, 0.09, 0.05, + 0.04] + Cn2h = np.array(Cn2h) + Cn2h /= np.sum(Cn2h) + + elif myProfile == 'EsoQ1': + layerAltitude = [30, 90, 150, 200, 245, 300, 390, 600, 1130, 1880, 2630, + 3500, 4500, 5500, 6500, 7500, 8500, 9500, 10500, 11500, + 12500, 13500, 14500, 15500, 16500, 17500, 18500, 19500, + 20500, 21500, 22500, 23500, 24500, 25500, 26500] + Cn2h = [22.6, 11.2, 10.1, 6.4, 4.15, 4.15, 4.15, 4.15, 3.1, 2.26, 1.13, + 2.21, 1.33, 0.88, 1.47, 1.77, 0.59, 2.06, 1.92, 1.03, 2.3, 3.75, + 2.76, 1.43, 0.89, 0.58, 0.36, 0.31, 0.27, 0.2, 0.16, 0.09, 0.12, + 0.07, 0.06] + Cn2h = np.array(Cn2h) + Cn2h /= np.sum(Cn2h) + + elif myProfile == 'EsoQ4': + layerAltitude = [30, 90, 150, 200, 245, 300, 390, 600, 1130, 1880, 2630, + 3500, 4500, 5500, 6500, 7500, 8500, 9500, 10500, 11500, + 12500, 13500, 14500, 15500, 16500, 17500, 18500, 19500, + 20500, 21500, 22500, 23500, 24500, 25500, 26500] + Cn2h = [23.6, 13.1, 9.81, 5.77, 6.58, 6.58, 6.58, 6.58, 5.4, 3.2, 1.6, + 2.18, 1.31, 0.87, 0.37, 0.45, 0.15, 0.52, 0.49, 0.26, 0.8, 1.29, + 0.95, 0.49, 0.31, 0.2, 0.12, 0.1, 0.09, 0.07, 0.06, 0.03, 0.05, + 0.02, 0.02] + Cn2h = np.array(Cn2h) + Cn2h /= np.sum(Cn2h) + + elif myProfile == 'gendron': + Cn2h = [1.0] + layerAltitude = [4414.] + + layerAltitude = (np.asarray(layerAltitude) * 1e3).tolist() + return layerAltitude, Cn2h + + +def fftw_forward(Ain): + # Initialization of input array + _A_in = pyfftw.empty_aligned(Ain.shape, dtype='complex128') + _A_in[:] = Ain + + # Initialization of output array + _A_out = pyfftw.empty_aligned(Ain.shape, dtype='complex128') + + # Invoking Pythors FFTW + _FFTW_Obj = pyfftw.FFTW( _A_in, _A_out, axes=(0,1), direction='FFTW_FORWARD', threads=os.cpu_count(), flags=('FFTW_ESTIMATE',)) + _FFTW_Obj() + return _A_out + +def fftw_inverse(Ain): + # Initialization of input array + _A_in = pyfftw.empty_aligned(Ain.shape, dtype='complex128') + _A_in[:] = Ain + + # Initialization of output array + _A_out = pyfftw.empty_aligned(Ain.shape, dtype='complex128') + + # Invoking Pythors FFTW + _FFTW_Obj = pyfftw.FFTW( _A_in, _A_out, axes=(0,1), direction='FFTW_BACKWARD', threads=os.cpu_count(), flags=('FFTW_ESTIMATE',)) + _FFTW_Obj() + return _A_out + diff --git a/fourierOptics/data/__pycache__/__init__.cpython-37.pyc b/fourierOptics/data/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..7308dd8 Binary files /dev/null and b/fourierOptics/data/__pycache__/__init__.cpython-37.pyc differ diff --git a/fourierOptics/data/__pycache__/__init__.cpython-38.pyc b/fourierOptics/data/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..28b7765 Binary files /dev/null and b/fourierOptics/data/__pycache__/__init__.cpython-38.pyc differ diff --git a/fourierOptics/data/__pycache__/__init__.cpython-39.pyc b/fourierOptics/data/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..7915c90 Binary files /dev/null and b/fourierOptics/data/__pycache__/__init__.cpython-39.pyc differ diff --git a/fourierOptics/data/__pycache__/glassCatalog.cpython-37.pyc b/fourierOptics/data/__pycache__/glassCatalog.cpython-37.pyc new file mode 100644 index 0000000..fe63863 Binary files /dev/null and b/fourierOptics/data/__pycache__/glassCatalog.cpython-37.pyc differ diff --git a/fourierOptics/data/__pycache__/glassCatalog.cpython-38.pyc b/fourierOptics/data/__pycache__/glassCatalog.cpython-38.pyc new file mode 100644 index 0000000..87163bd Binary files /dev/null and b/fourierOptics/data/__pycache__/glassCatalog.cpython-38.pyc differ diff --git a/fourierOptics/data/__pycache__/glassCatalog.cpython-39.pyc b/fourierOptics/data/__pycache__/glassCatalog.cpython-39.pyc new file mode 100644 index 0000000..21b9a04 Binary files /dev/null and b/fourierOptics/data/__pycache__/glassCatalog.cpython-39.pyc differ diff --git a/fourierOptics/micado.py b/fourierOptics/micado.py index 312990d..002af97 100644 --- a/fourierOptics/micado.py +++ b/fourierOptics/micado.py @@ -9,7 +9,16 @@ from .wavefrontErrors import * from .misc import * +''' +Changelog: +Changes w.r.t. the old MICADO paraxial model: +- Changed focal length of collimator lens from 4278.32701 to 1601.688 mm +- Changed distance from ADC to collimator lens from 50 mm to 74 mm. +- Changed thickness of ADC prisms from 25 to 15 mm +- Changed working F-number (WFNO) from 53.4466 to 20.0269 +- This results in a change of the plate scale from 0.1001 arcsec/mm to 0.2672 arcsec/mm +''' class ParaxialMICADO: def __init__(self, l=1.35e-3, n=1001, m=1001, dpix=160, entrance_pupil='ELT'): @@ -32,7 +41,7 @@ class ParaxialMICADO: self.ELT_FP = OpticalSurface(name='MCD-IFP') self.ELT_COL = ParaxialLens(CA=154.54, f=1418.94, name='MCD-COL') self.ELT_ADC = ADCObject() - self.ELT_CAM = ParaxialLens(CA=89.2, f=4278.32701, name='MCD-CAM') + self.ELT_CAM = ParaxialLens(CA=89.2, f=1601.688, name='MCD-CAM') self.ELT_DET = OpticalSurface(name='MCD-DET') # Built optical train @@ -41,11 +50,12 @@ class ParaxialMICADO: self.MCD.add_object(self.ELT_FP, dist=self.ELT_MIRROR.f) self.MCD.add_object(self.ELT_COL, dist=self.ELT_COL.f) self.ELT_ADC.add_to_optical_train(self.MCD, 1209.4718) - self.MCD.add_object(self.ELT_CAM, dist=50) + self.MCD.add_object(self.ELT_CAM, dist=74) self.MCD.add_object(self.ELT_DET, dist=self.ELT_CAM.f) self.last_PSF = None self.clean_PSF = None + self.pixelscale = 4e-3 # arcseconds/pixel self.platescale = self.set_plate_scale() print ('Your resolution is {}x{} with a pixel size of {}'.format(self.n,self.m,self.dpix)) print('This means your grid is {} mm by {} mm, with each pixel {} mm.'.format( @@ -233,6 +243,8 @@ class ParaxialMICADO: elif surfaces[s] == self.ELT_FP: if print_progress: print('Propagating from ELT-IFP to MCD-COL') Ein = Eout + if self.ELT_FP.wavefrontError: + Ein *= self.ELT_FP.phase_delay(x, y, l, ip_type='surface') Eout, x, y = fou.fresnel_propagation(Ein, x, y, dz=self.ELT_COL.f, oversample=None, show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], apply_interpolation=apply_ip[s], tx=tilt_x[s], ty=tilt_y[s]) @@ -332,7 +344,7 @@ class ParaxialMICADO: [-120, 120, -120, 120], [-120, 120, -120, 120], [-120, 120, -120, 120], - [-2.5, 2.5, -2.5, 2.5] + [-2.5/2.669, 2.5/2.669, -2.5/2.669, 2.5/2.669] ] elif coordinates is not None: _coordinatesList = coordinates @@ -384,12 +396,12 @@ class ParaxialMICADO: _apply_ip[surf] = True return _apply_ip - def set_plate_scale(self): + def set_plate_scale(self, wfno=20.0269): ''' Returns the platescale of this paraxial MICADO model in arcseconds/mm. ''' - _ps = (3600*180/np.pi) / (self.ELT_MIRROR.CA * 53.4466) + _ps = (3600*180/np.pi) / (self.ELT_MIRROR.CA * wfno) self.platescale = _ps return _ps @@ -522,7 +534,7 @@ class ParaxialMICADO: a2y=np.deg2rad(self.ELT_ADC.p4.tilt2y), n_glass=self.ELT_ADC.p4.find_refractive_index(self.l, unit='millimeter')) positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) - xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,50) + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,74) # ELT-MCD-CAM: xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_CAM.f, self.ELT_CAM.f) @@ -546,8 +558,8 @@ class ParaxialMICADO: 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15, # ADC p3 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1, # Between p3 and p4 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15, # ADC p4 - 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 50, # MCD-CAM - 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 50 + 4278.32701 # Focal plane + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 74, # MCD-CAM + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 74 + 1601.688 # Focal plane ]) return x_out, y_out, tx_out, ty_out, z_out @@ -640,7 +652,7 @@ class ParaxialMICADO: # Fourth prism xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,self.ELT_ADC.p4.thickness) positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) - xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,50) + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,74) # ELT-MCD-CAM: xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_CAM.f, self.ELT_CAM.f) @@ -664,8 +676,8 @@ class ParaxialMICADO: 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15, # ADC p3 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1, # Between p3 and p4 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15, # ADC p4 - 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 50, # MCD-CAM - 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 50 + 4278.32701 # Focal plane + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 74, # MCD-CAM + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 74 + 1601.688 # Focal plane ]) return x_out, y_out, tx_out, ty_out, z_out diff --git a/fourierOptics/micado_old.py b/fourierOptics/micado_old.py new file mode 100644 index 0000000..8a695be --- /dev/null +++ b/fourierOptics/micado_old.py @@ -0,0 +1,798 @@ +import numpy as np +from .fourier import * +from .gaussian import * +from .opticalTrain import * +from .optics import * +from .profiles import * +from .psfFitter import * +from .wavefrontErrors import * +from .misc import * + + + + +class ParaxialMICADO: + def __init__(self, l=1.35e-3, n=1001, m=1001, dpix=160, entrance_pupil='ELT'): + self.l = l + self.dpix = dpix + self.n, self.m = n, m + self.x, self.y = np.meshgrid(np.linspace(-n/2*dpix, n/2*dpix, n), np.linspace(-m/2*dpix, m/2*dpix, m)) + if entrance_pupil == 'ELT': + self.ELT_MASK = Profiles().ELT_pupil(n,m,dpix).astype('complex128') + elif entrance_pupil == 'obscured': + r1 = 38542/2 + r2 = 10700/2 + self.ELT_MASK = Profiles().obscured_top_hat(r1, r2, n, m, dpix).astype('complex128') + elif entrance_pupil == 'open': + r1 = 38542/2 + self.ELT_MASK = Profiles().top_hat(r1, n, m, dpix).astype('complex128') + + # Built optical system elements + self.ELT_MIRROR = ParabolicMirror(dx=0, dy=0, name='ELT-paraxial', CA=38542, f=6.84022e5) + self.ELT_FP = OpticalSurface(name='MCD-IFP') + self.ELT_COL = ParaxialLens(CA=154.54, f=1418.94, name='MCD-COL') + self.ELT_ADC = ADCObject() + self.ELT_CAM = ParaxialLens(CA=89.2, f=4278.32701, name='MCD-CAM') + self.ELT_DET = OpticalSurface(name='MCD-DET') + + # Built optical train + self.MCD = OpticalTrain() + self.MCD.add_object(self.ELT_MIRROR, dist=0) + self.MCD.add_object(self.ELT_FP, dist=self.ELT_MIRROR.f) + self.MCD.add_object(self.ELT_COL, dist=self.ELT_COL.f) + self.ELT_ADC.add_to_optical_train(self.MCD, 1209.4718) + self.MCD.add_object(self.ELT_CAM, dist=50) + self.MCD.add_object(self.ELT_DET, dist=self.ELT_CAM.f) + + self.last_PSF = None + self.clean_PSF = None + self.pixelscale = 4e-3 # arcseconds/pixel + self.platescale = self.set_plate_scale() + print ('Your resolution is {}x{} with a pixel size of {}'.format(self.n,self.m,self.dpix)) + print('This means your grid is {} mm by {} mm, with each pixel {} mm.'.format( + self.x.max()-self.x.min(), self.y.max()-self.y.min(), self.dpix)) + + + def apply_mask(self, mask, replace=True): + ''' + Set a mask to the ELT entrance pupil. + ''' + if replace: + self.ELT_MASK = mask.astype('complex128') + elif not replace: + self.ELT_MASK *= mask + + + def set_PSF(self, PSF, x, y, perfect=True): + ''' + If calculating another PSF takes to long, + one can set a custom PSF using this + function. + + Can be set as the 'clean' PSF or as another + 'last' PSF, with the perfect flag. + ''' + if perfect: + self.clean_PSF = (PSF, x, y) + elif not perfect: + self.last_PSF = (PSF, x, y) + + + def centroid_shift(self, method='Coupling', reference_PSF=None, **kwargs): + ''' + Do a centroid shift calculation for the given method. + This is a quick way of doing it. It only returns the + centroid shift (and uncertainties) and no other parameters. + ''' + assert (self.last_PSF is not None), 'PSF necessary for fitting.' + if 'initialGuess' in kwargs: + guess_value = kwargs.get('initialGuess') + + if reference_PSF is None: + PSF, x, y = self.last_PSF + elif reference_PSF is not None: + PSF, x, y = reference_PSF + fitter = PSFFitter(x.real,y.real,np.abs(PSF)**2) + + if method == 'Coupling': + assert (self.clean_PSF is not None), 'Perfect PSF necessary for comparison.' + Eclean, xclean, yclean = self.clean_PSF + fitted_PSF, popt, var, coupling = fitter.fit_custom_field(Eclean, PSF, return_coupling=True, **kwargs) + x0, y0 = np.asarray(popt).T + sx0, sy0 = np.asarray(np.sqrt(var)).T + return x0, y0, sx0, sy0, coupling + + elif method == 'Airy': + if 'initialGuess' not in kwargs: + guess_value = (0.03**2, 0.0, 0.0, 0.0, 0.03) + fitted_PSF, popt, pcov = fitter.fit_airy_PSF(initialGuess=guess_value) + _, x0, y0, _, _ = popt + pcov_list = [ np.sqrt(pcov[i][i]) for i in range(len(pcov)) ] + _, sx0, sy0, _, _ = np.asarray(pcov_list).T + + elif method == 'Obscured Airy': + if 'initialGuess' not in kwargs: + guess_value = (0.03**2, 0.0, 0.0, 0.0, 0.03) + fitted_PSF, popt, pcov = fitter.fit_fixed_obscuration_airy_PSF(initialGuess=guess_value) + _, x0, y0, _, _ = popt + pcov_list = [ np.sqrt(pcov[i][i]) for i in range(len(pcov)) ] + _, sx0, sy0, _, _ = np.asarray(pcov_list).T + + elif method == 'Gaussian': + if 'initialGuess' not in kwargs: + guess_value = (0.04, 0, 0, 0.01, 0.01, 0, 0) + fitted_PSF, popt, pcov = fitter.fit_gaussian_PSF(initialGuess=guess_value) + _, x0, y0, _, _, _, _ = popt + pcov_list = [ np.sqrt(pcov[i][i]) for i in range(len(pcov)) ] + _, sx0, sy0, _, _, _, _ = np.asarray(pcov_list).T + + elif method == 'Moffat': + if 'initialGuess' not in kwargs: + guess_value = (0.04, 0, 0, 0.01, 0.01, 0, 0, 4) + fitted_PSF, popt, pcov = fitter.fit_moffat_PSF(initialGuess=guess_value) + _, x0, y0, _, _, _, _, _ = popt + pcov_list = [ np.sqrt(pcov[i][i]) for i in range(len(pcov)) ] + _, sx0, sy0, _, _, _, _, _ = np.asarray(pcov_list).T + + elif method == 'Barycenter': + x0, y0, sx0, sy0 = fitter.fit_barycenter(amplitude=False) + + return x0, y0, sx0, sy0 + + + def propagate(self, l=None, surface=-1, zoom=False, hx=0, hy=0, load_up_to_adc=None, + coordinates=None, show=False, image_output=None, save=None, apply_interpolation=True, + print_progress=True, defocus=None): + ''' + Propagate the input wavefront through the MICADO paraxial + model. Optionally the propagation starts at the ADC, if + a data file with the wavefront at that point is defined. + + Parameters: + l - Desired wavelength of the propagation + surface - Surface to propagate to. Default: -1 = Detector plane + zoom - Zoom in on the PSF on the detector? + load_up_to_adc - Use a .pkl file to load in the wavefront just before the adc. + coordinates - User defined coordinates that define the propagation sampling. + show_final - Plots the final amplitude and phase if True. + + Returns: + Exyz - Complex field with shape n,m describing the amplitude and phase + of the wavefront at the output surface. + x, y - Corresponding x and y coordinates, in mm. + ''' + # Set up wavelength + l = self.l if not l else l + + # Setting up the arguments for each surface propgation + surfaces = self.get_surfaces() + surface = len(surfaces)-1 if surface == -1 else surface + coordinatesList = self.get_coordinates(coordinates) + show,show_final_zoom= self.get_show_image(show, zoom, len(surfaces), surface) + output_coordinates = self.get_image_coordinates(image_output, coordinatesList, len(surfaces), surface) + saveList = self.get_save_image(save, len(surfaces), surface) + apply_ip = self.get_image_interpolation(apply_interpolation, zoom, len(surfaces), surface) + tilt_x, tilt_y = self.get_tilt_angles(hx, hy) + + # Set up the FourierOptics object for propagation + fou = FourierOptics(l=l) + x, y = self.x, self.y + p = 1 # Used as counter for the ADC prisms. + + # Load the data up to the ADC (useful for many iterations on the same + # calculation, where differences only appear at or after the ADC) + if load_up_to_adc is not None: + Eout, x, y = load_variable(load_up_to_adc) + x = x.real + y = y.real + s = 4 + elif load_up_to_adc is None: + s = 0 + + # Start propgation + if print_progress: print('Starting propagation...') + while s <= surface: + if (surfaces[s] == self.ELT_MASK).all(): + Ein = surfaces[s] + Eout = surfaces[s] + if show[s] or saveList[s]: + _fig = plt.figure(figsize=(8,3)) + _ax = _fig.add_subplot(121) + _im = _ax.imshow(np.abs(Eout), cmap='hot', extent=[x.min(), x.max(), y.min(), y.max()]) + _ax.set_xlim([x.min(), x.max()]) + _ax.set_ylim([y.min(), y.max()]) + _ax.set_xlabel('x [mm]') + _ax.set_ylabel('y [mm]') + _fig.colorbar(_im, ax=_ax, label='Amplitude') + _ax2 = _fig.add_subplot(122) + _im2 = _ax2.imshow(np.angle(Eout), cmap='hot', extent=[x.min(), x.max(), y.min(), y.max()]) + _ax2.set_xlim([x.min(), x.max()]) + _ax2.set_ylim([y.min(), y.max()]) + _ax2.set_xlabel('x [mm]') + _ax2.set_ylabel('y [mm]') + _fig.colorbar(_im2, ax=_ax2, label='Phase') + plt.tight_layout() + if saveList[s]: + plt.savefig(saveList[s], bbox_inches='tight', transparent=True) + if show[s]: + plt.show() + else: + plt.close() + + + elif surfaces[s] == self.ELT_MIRROR: + if print_progress: print('Propagating from ELT-M1 to ELT-IFP') + Ein = Eout * self.ELT_MIRROR.phase_delay(x,y,l,paraxial=True) + if s == surface and zoom: + xzoom, yzoom = x, y + Einzoom = Ein + fzoom = self.ELT_MIRROR.f + Eout, x, y = fou.fresnel_propagation(Ein, x, y, dz=self.ELT_MIRROR.f, oversample=None, + show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], + apply_interpolation=apply_ip[s], tx=tilt_x[s], ty=tilt_y[s]) + + elif surfaces[s] == self.ELT_FP: + if print_progress: print('Propagating from ELT-IFP to MCD-COL') + Ein = Eout + Eout, x, y = fou.fresnel_propagation(Ein, x, y, dz=self.ELT_COL.f, oversample=None, + show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], + apply_interpolation=apply_ip[s], tx=tilt_x[s], ty=tilt_y[s]) + + elif surfaces[s] == self.ELT_COL: + if print_progress: print('Propagating from MCD-COL to MCD-ADC-P1') + x = x.real + y = y.real + Ein = Eout * self.ELT_COL.phase_delay(x,y,l, paraxial=True) + Eout, x, y = fou.plane_wave_propagation(Ein, x, y, dz=self.MCD.sequentialDistance[3], oversample=None, + show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], tx=tilt_x[s], ty=tilt_y[s]) + + elif surfaces[s] == self.ELT_ADC and p == 1: + if print_progress: print('Propagating from MCD-ADC-P1 to MCD-ADC-P2') + Ein = Eout * self.ELT_ADC.get_phase_delay(x,y,l, theta=None, paraxial=True, prism=1) + Eout, x, y = fou.plane_wave_propagation(Ein, x, y, dz=self.MCD.sequentialDistance[4], oversample=None, + show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], tx=tilt_x[s], ty=tilt_y[s]) + p += 1 + + elif surfaces[s] == self.ELT_ADC and p == 2: + if print_progress: print('Propagating from MCD-ADC-P2 to MCD-ADC-P3') + Ein = Eout * self.ELT_ADC.get_phase_delay(x,y,l, theta=None, paraxial=True, prism=2) + Eout, x, y = fou.plane_wave_propagation(Ein, x, y, dz=self.MCD.sequentialDistance[5], oversample=None, + show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], tx=tilt_x[s], ty=tilt_y[s]) + p += 1 + + elif surfaces[s] == self.ELT_ADC and p == 3: + if print_progress: print('Propagating from MCD-ADC-P3 to MCD-ADC-P4') + Ein = Eout * self.ELT_ADC.get_phase_delay(x,y,l, theta=None, paraxial=True, prism=3) + Eout, x, y = fou.plane_wave_propagation(Ein, x, y, dz=self.MCD.sequentialDistance[6], oversample=None, + show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], tx=tilt_x[s], ty=tilt_y[s]) + p += 1 + + elif surfaces[s] == self.ELT_ADC and p == 4: + if print_progress: print('Propagating from MCD-ADC-P4 to MCD-CAM') + Ein = Eout * self.ELT_ADC.get_phase_delay(x,y,l, theta=None, paraxial=True, prism=4) + Eout, x, y = fou.plane_wave_propagation(Ein, x, y, dz=self.MCD.sequentialDistance[7], oversample=None, + show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], tx=tilt_x[s], ty=tilt_y[s]) + p += 1 + + elif surfaces[s] == self.ELT_DET: + if print_progress: print('Propagating from MCD-CAM to MCD-DET') + Ein = Eout * self.ELT_CAM.phase_delay(x,y,l, paraxial=True) + if defocus is None: + defocus = 0 + if s == surface and zoom: + xzoom, yzoom = x.real, y.real + Einzoom = Ein + fzoom = self.ELT_CAM.f + defocus + Eout, x, y = fou.fresnel_propagation(Ein, x, y, dz=self.ELT_CAM.f+defocus, oversample=None, + show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], + apply_interpolation=apply_ip[s], tx=tilt_x[s], ty=tilt_y[s]) + s += 1 + + if zoom: + if print_progress: print('Zooming in on the PSF location...') + imax_1, imax_2 = find_maximum(np.abs(Eout)) + xmax = x[imax_1, imax_2] + ymax = y[imax_1, imax_2] + ind = s-3 if (s-3)>0 else 0 + newCoordinates = np.asarray(coordinatesList[ind]) + np.array([xmax, xmax, ymax, ymax]) + newCoordinates = newCoordinates.real.tolist() + Eout, x, y = fou.fresnel_propagation(Einzoom, xzoom, yzoom, dz=fzoom, oversample=None, + show=show_final_zoom, save=saveList[s-1], outputCoordinates=newCoordinates, + apply_interpolation=True, interpol_type='linear', tx=tilt_x[s-1], ty=tilt_y[s-1]) + + if print_progress: print('Propagation finished!') + self.last_PSF = (Eout, x, y) + return Eout, x, y + + + + def get_surfaces(self): + _surfaces = [ + self.ELT_MASK, + self.ELT_MIRROR, + self.ELT_FP, + self.ELT_COL, + self.ELT_ADC, + self.ELT_ADC, + self.ELT_ADC, + self.ELT_ADC, + self.ELT_CAM, + self.ELT_DET + ] + return _surfaces + + + def get_coordinates(self, coordinates): + # Output coordinates: + if coordinates is None: + _coordinatesList = [ + [-1.0, 1.0, -1.0, 1.0], + [-200, 200, -200, 200], + [-120, 120, -120, 120], + [-120, 120, -120, 120], + [-120, 120, -120, 120], + [-120, 120, -120, 120], + [-120, 120, -120, 120], + [-2.5, 2.5, -2.5, 2.5] + ] + elif coordinates is not None: + _coordinatesList = coordinates + return _coordinatesList + + def get_show_image(self, show, zoom, N, surf): + # Set show values. Will only show the image plane. + _showlist = [False] * N + _showlist[surf] = show + _show_final_zoom = False + if zoom: + _show_final_zoom = show + _showlist[surf] = False + _show = _showlist + return _show, _show_final_zoom + + def get_image_coordinates(self, image_output, coordinates, N, surf): + # Set the image coordinates + _output_coordinates = [None] * N + _output_coordinates[2] = coordinates[1] + if image_output is not None: + _output_coordinates[surf] = image_output + return _output_coordinates + + def get_save_image(self, save, N, surf): + # Set the save values + _savelist = [False] * N + if save == 'all': + _savelist = [ + '01_ELT_MASK_output.png', + '02_ELT_FP_output.png', + '03_ELT_MCD_COL_output.png', + '04_ELT_MCD_ADC_P1_output.png', + '05_ELT_MCD_ADC_P2_output.png', + '06_ELT_MCD_ADC_P3_output.png', + '07_ELT_MCD_ADC_P4_output.png', + '08_ELT_MCD_CAM_output.png', + '09_ELT_MCD_DET_output.png', + '10_ELT_MCD_DET_output_zoomed.png' + ] + elif save is not None: + _savelist[surf] = save + return _savelist + + def get_image_interpolation(self, apply_interpolation, zoom, N, surf): + # Apply interpolation to the output? + _apply_ip = [False] * N + if not zoom and apply_interpolation and surf!=2: + _apply_ip[surf] = True + return _apply_ip + + def set_plate_scale(self): + ''' + Returns the platescale of this paraxial MICADO + model in arcseconds/mm. + ''' + _ps = (3600*180/np.pi) / (self.ELT_MIRROR.CA * 53.4466) + self.platescale = _ps + return _ps + + def get_field_angle(self,hx,hy): + fx = hx * 0.0075 + fy = hy * 0.0075 + return fx, fy + + def get_tilt_angles(self, hx, hy): + ''' + Returns the propagation angles at each surface + in degrees. E.g. near the ADC for hx=1, tx=3.61 + degrees. + ''' + _x,_y,_tx,_ty,_z = self.perform_ray_trace_without_adc(hx,hy,0,0) + _tx[0] = _tx[1] + _ty[0] = _ty[1] + tx = np.asarray([_tx[i] for i in [0,1,2,3,5,7,9,11,12,13,13]]).flatten() + ty = np.asarray([_ty[i] for i in [0,1,2,3,5,7,9,11,12,13,13]]).flatten() + return tx, ty + + + def perform_ray_trace(self,hx,hy,px,py): + ''' + Ray trace the paraxial model of ELT-MICADO + + Parameters + ---------- + hx,hy - float or array + Normalized field angles + px,py - float or array + Normalized pupil angles + + Returns + ------- + xout - float or array + x-coordinates on all surfaces + yout - float or array + y-coordinates on all surfaces + zout + z-coordinates of all surfaces + + ''' + xout = [] + yout = [] + + # Show simplified 2D ray trace model + if type(px)==int or type(px)==float: + px = np.array([px]) + if type(py)==int or type(py)==float: + py = np.array([py]) + if type(hx)==int or type(hx)==float: + hx = np.array([hx]) + if type(hy)==int or type(hy)==float: + hy = np.array([hy]) + + xp0 = np.asarray(px) * self.ELT_MIRROR.CA/2 + yp0 = np.asarray(py) * self.ELT_MIRROR.CA/2 + + # for xp in range(len(xp0)): + positions_and_angles = [] + + # Calculate the field angles + txin, tyin = self.get_field_angle(hx, hy) + txin = np.deg2rad(txin) + tyin = np.deg2rad(tyin) + + #Start of system (perhaps at ELT_MASK?) + xp, yp, tx, ty = xp0, yp0, txin, tyin + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # Mask + + # ELT M1 + xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_MIRROR.f, self.ELT_MIRROR.f) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # ELT M1 + xp, yp, tx, ty = get_free_space_propagation_rtm(xp, yp, tx, ty, self.ELT_MIRROR.f) + + # ELT-FP + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # ELT FP + xp, yp, tx, ty = get_free_space_propagation_rtm(xp, yp, tx, ty, self.ELT_COL.f) + + # ELT-MCD-COL + xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_COL.f, self.ELT_COL.f) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # ELT MCD COL + xp, yp, tx, ty = get_free_space_propagation_rtm(xp, yp, tx, ty, 1209.4718) + + # ELT-MCD-ADC + # First prism: + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + xp, yp, tx, ty = get_prism_rtm( xp,yp,tx,ty, + thickness=self.ELT_ADC.p1.thickness, + a1x=np.deg2rad(self.ELT_ADC.p1.tilt1x), + a2x=np.deg2rad(self.ELT_ADC.p1.tilt2x), + a1y=np.deg2rad(self.ELT_ADC.p1.tilt1y), + a2y=np.deg2rad(self.ELT_ADC.p1.tilt2y), + n_glass=self.ELT_ADC.p1.find_refractive_index(self.l, unit='millimeter')) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,1) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + + # Second prism: + xp, yp, tx,ty = get_prism_rtm( xp,yp,tx,ty, + thickness=self.ELT_ADC.p2.thickness, + a1x=np.deg2rad(self.ELT_ADC.p2.tilt1x), + a2x=np.deg2rad(self.ELT_ADC.p2.tilt2x), + a1y=np.deg2rad(self.ELT_ADC.p2.tilt1y), + a2y=np.deg2rad(self.ELT_ADC.p2.tilt2y), + n_glass=self.ELT_ADC.p2.find_refractive_index(self.l, unit='millimeter')) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,8) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + + # Third prism + xp, yp, tx,ty = get_prism_rtm( xp,yp,tx,ty, + thickness=self.ELT_ADC.p3.thickness, + a1x=np.deg2rad(self.ELT_ADC.p3.tilt1x), + a2x=np.deg2rad(self.ELT_ADC.p3.tilt2x), + a1y=np.deg2rad(self.ELT_ADC.p3.tilt1y), + a2y=np.deg2rad(self.ELT_ADC.p3.tilt2y), + n_glass=self.ELT_ADC.p3.find_refractive_index(self.l, unit='millimeter')) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,1) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + + # Fourth prism + xp, yp, tx,ty = get_prism_rtm( xp,yp,tx,ty, + thickness=self.ELT_ADC.p4.thickness, + a1x=np.deg2rad(self.ELT_ADC.p4.tilt1x), + a2x=np.deg2rad(self.ELT_ADC.p4.tilt2x), + a1y=np.deg2rad(self.ELT_ADC.p4.tilt1y), + a2y=np.deg2rad(self.ELT_ADC.p4.tilt2y), + n_glass=self.ELT_ADC.p4.find_refractive_index(self.l, unit='millimeter')) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,50) + + # ELT-MCD-CAM: + xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_CAM.f, self.ELT_CAM.f) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # After ELT-CAM + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,self.ELT_CAM.f) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # Image + + x_out = np.asarray(positions_and_angles)[:,0] + y_out = np.asarray(positions_and_angles)[:,1] + tx_out = np.asarray(positions_and_angles)[:,2] + ty_out = np.asarray(positions_and_angles)[:,3] + z_out = np.asarray([0, # ELT Mask + 0, # ELT M1 + 6.84022e5, # ELT-FP + 6.84022e5+1418.94, # ELT-MCD-COL + 6.84022e5+1418.94+1209.4718, # ADC + 6.84022e5+1418.94+1209.4718 + 15, # ADC p1 + 6.84022e5+1418.94+1209.4718 + 15 + 1, # Between p1 and p2 + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15, # ADC p2 + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8, # Between the prisms + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15, # ADC p3 + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1, # Between p3 and p4 + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15, # ADC p4 + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 50, # MCD-CAM + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 50 + 4278.32701 # Focal plane + ]) + return x_out, y_out, tx_out, ty_out, z_out + + + def perform_ray_trace_without_adc(self,hx,hy,px,py): + ''' + Ray trace the paraxial model of ELT-MICADO + without an ADC in the optical path. + + Parameters + ---------- + hx,hy - float or array + Normalized field angles + px,py - float or array + Normalized pupil angles + + Returns + ------- + xout - float or array + x-coordinates on all surfaces + yout - float or array + y-coordinates on all surfaces + zout + z-coordinates of all surfaces + + ''' + xout = [] + yout = [] + + # Show simplified 2D ray trace model + if type(px)==int or type(px)==float: + px = np.array([px]) + if type(py)==int or type(py)==float: + py = np.array([py]) + if type(hx)==int or type(hx)==float: + hx = np.array([hx]) + if type(hy)==int or type(hy)==float: + hy = np.array([hy]) + + xp0 = np.asarray(px) * self.ELT_MIRROR.CA/2 + yp0 = np.asarray(py) * self.ELT_MIRROR.CA/2 + + # for xp in range(len(xp0)): + positions_and_angles = [] + # l_ind = self.l * 1e3 # wavelength from mm in microns + + # Calculate the field angles + txin, tyin = self.get_field_angle(hx, hy) + txin = np.deg2rad(txin) + tyin = np.deg2rad(tyin) + + #Start of system (perhaps at ELT_MASK?) + xp, yp, tx, ty = xp0, yp0, txin, tyin + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # Mask + + # ELT M1 + xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_MIRROR.f, self.ELT_MIRROR.f) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # ELT M1 + xp, yp, tx, ty = get_free_space_propagation_rtm(xp, yp, tx, ty, self.ELT_MIRROR.f) + + # ELT-FP + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # ELT FP + xp, yp, tx, ty = get_free_space_propagation_rtm(xp, yp, tx, ty, self.ELT_COL.f) + + # ELT-MCD-COL + xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_COL.f, self.ELT_COL.f) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # ELT MCD COL + xp, yp, tx, ty = get_free_space_propagation_rtm(xp, yp, tx, ty, 1209.4718) + + # ELT-MCD-ADC + # First prism: + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,self.ELT_ADC.p1.thickness) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,1) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + + # Second prism: + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,self.ELT_ADC.p2.thickness) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,8) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + + # Third prism + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,self.ELT_ADC.p3.thickness) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,1) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + + # Fourth prism + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,self.ELT_ADC.p4.thickness) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,50) + + # ELT-MCD-CAM: + xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_CAM.f, self.ELT_CAM.f) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # After ELT-CAM + xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,self.ELT_CAM.f) + positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # Image + + x_out = np.asarray(positions_and_angles)[:,0] + y_out = np.asarray(positions_and_angles)[:,1] + tx_out = np.asarray(positions_and_angles)[:,2] + ty_out = np.asarray(positions_and_angles)[:,3] + z_out = np.asarray([0, # ELT Mask + 0, # ELT M1 + 6.84022e5, # ELT-FP + 6.84022e5+1418.94, # ELT-MCD-COL + 6.84022e5+1418.94+1209.4718, # ADC + 6.84022e5+1418.94+1209.4718 + 15, # ADC p1 + 6.84022e5+1418.94+1209.4718 + 15 + 1, # Between p1 and p2 + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15, # ADC p2 + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8, # Between the prisms + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15, # ADC p3 + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1, # Between p3 and p4 + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15, # ADC p4 + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 50, # MCD-CAM + 6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 50 + 4278.32701 # Focal plane + ]) + return x_out, y_out, tx_out, ty_out, z_out + + + def plot_ray_trace(self, no_adc=False): + ''' + Function that plots a diagram of the ray traced paraxial + model of the MICADO instrument. Note that the prisms of + the ADC do not show their true shape. + ''' + + px = np.linspace(-1,1,21) + py = np.zeros(px.shape) + hx = np.asarray([0]*21) + hy = np.zeros(hx.shape) + if no_adc: + x0, y0, _, _, z0 = self.perform_ray_trace_without_adc(hx,hy,px,py) + hx = np.asarray([1]*21) + x1, y1, _, _, z1 = self.perform_ray_trace_without_adc(hx,hy,px,py) + hx = np.asarray([-1]*21) + x2, y2, _, _, z2 = self.perform_ray_trace_without_adc(hx,hy,px,py) + else: + x0, y0, _, _, z0 = self.perform_ray_trace(hx,hy,px,py) + hx = np.asarray([1]*21) + x1, y1, _, _, z1 = self.perform_ray_trace(hx,hy,px,py) + hx = np.asarray([-1]*21) + x2, y2, _, _, z2 = self.perform_ray_trace(hx,hy,px,py) + + fig = plt.figure(figsize=(15,6)) + ax = fig.add_subplot(111) + for i in range(len(px)): + ax.plot(z0, x0[:,i], 'ro') + ax.plot(z0, x0[:,i], 'r-') + ax.plot(z1, x1[:,i], 'bo') + ax.plot(z1, x1[:,i], 'b-') + ax.plot(z2, x2[:,i], 'go') + ax.plot(z2, x2[:,i], 'g-') + ax.set_xlim([6.84e5,6.915e5]) + ax.set_ylim([-290,290]) + plt.grid() + plt.show() + + + + +class ADC_surfaces: + ''' + Very specific class to generate and scale wavefront errors on + the MICADO ADC prisms. + ''' + def __init__(self, x,y, clearAperture=125): + self.n, self.m = x.shape + self.clearAperture = clearAperture + self.radius = self.clearAperture / 2 + self.yz,self.xz = np.mgrid[x.min()/self.radius:x.max()/self.radius:self.n*1j, + x.min()/self.radius:x.max()/self.radius:self.m*1j] + self.zern_coefficients = None + self.zern_surface = None + self.psd_surface = None + self.latest_surface = None + + + def generate_ADC_wfe(self, zernRMS=20e-6, psdRMS=15e-6, zernModes=19): + ''' + We apply the wavefront error an each of the ADC prisms + as a phase transformation, corresponding to a + transmitted wavefront error. + + This wfe is separated into a zernike and psd component + with a total rms of 25 nm + zernike component - RMS: 20 nm + psd component - RMS: 15 nm + Total - RMS: 25 nm + ''' + print('Generating new ADC WFE...') + wfe = WavefrontError(self.n,self.m) + zern_rms = zernRMS # RMS of the zernike component of the WFE + midspat_rms = psdRMS # RMS of the PSD component of the WFE + + self.psd_surface = wfe.generate_height_map(midspat_rms, 2, 1) + zern_surface = wfe.generate_height_map(zern_rms, 2, 1) + zern_coefficients = wfe.fit_zernike_coefficients(zern_surface, modes=zernModes) + zern_coefficients[:3] = 0 # No defocus, tip or tilt + self.zern_coefficients = wfe.scale_zernike_coefficients(zern_coefficients, zern_rms) + + # Scale the zernike modes to the clear aperture of the ADC + self.zern_surface = wfe.zernike_cartesian(self.zern_coefficients, self.xz, self.yz, rescale=False) + + zern_surface = self.mask_surface(self.zern_surface)#, self.xz, self.yz) + psd_surface = self.mask_surface(self.psd_surface)#, self.xz, self.yz) + + self.latest_surface = zern_surface + psd_surface + return self.latest_surface + + + def mask_surface(self, A): + ''' + Set the array values to 0 where + the radius is > 1, without changing + the original array. + ''' + # Rescale x and y + _mask = np.sqrt(self.xz**2 + self.yz**2) > 1 + B = A.copy() + B[_mask] = 0 + return B + + + def scale_wfe(self, zernRMS=20e-5, psdRMS=15e-5): + ''' + Scaling of the original wfe, given the new + zernike and psd RMS values that are desired. + ''' + print('Scaling ADC WFE...') + _old_psd_rms = np.sqrt(np.mean(self.psd_surface**2)) + _factor_psd = psdRMS / _old_psd_rms + wfe = WavefrontError(self.n,self.m) + new_psd_surface = wfe.scale_rms(_factor_psd, data=self.psd_surface) + + new_zern_coefficients = wfe.scale_zernike_coefficients(self.zern_coefficients, zernRMS) + + # Scale the zernike modes to the clear aperture of the ADC + new_zern_surface = wfe.zernike_cartesian(new_zern_coefficients, self.xz, self.yz, rescale=False) + + new_zern_surface = self.mask_surface(new_zern_surface)#, _xz, _yz) + new_psd_surface = self.mask_surface(new_psd_surface)#, _xz, _yz) + + self.latest_surface = new_zern_surface + new_psd_surface + return self.latest_surface \ No newline at end of file diff --git a/fourierOptics/optics.py b/fourierOptics/optics.py index 10bcfea..a0de6d4 100644 --- a/fourierOptics/optics.py +++ b/fourierOptics/optics.py @@ -2,6 +2,10 @@ import numpy as np from scipy.optimize import fsolve from scipy import ndimage from .data import glassCatalog +from .misc import * +from pdb import set_trace +from scipy.interpolate import interp2d +from copy import deepcopy class LensObject: def __init__(self, CA, name=None): @@ -442,17 +446,61 @@ class OpticalSurface: assert type(name)==str, 'No string for a name.' self.name = name self.mask = None + self.wavefrontError = False def add_mask(self, mask): self.mask = mask - def phase_delay(self, x, y, l, **kwargs): - _t_l = np.ones(np.shape(x)) + def phase_delay(self, x, y, l, ip_type='phase', kind='cubic', **kwargs): + ''' + Calculate the phase delay introduced by the WFE. + The tricky part is that we want the phase delay only + in the small calculated area of the full surface. + + ip_type - 'phase' or 'surface' + Defines what method to use to obtain the interpolated surface + height map. 'surface' does a normal interpolation on the height + map. This does not conserve the WFE rms value. Then, 'phase' + interpolates on the phase information in the PSD algorithm. This + does conserve the WFE rms within the window. + If 'phase', then the WavefrontError object has to be supplied, + containing the generated wavefront error. + ''' + _t_l = np.ones(np.shape(x)).astype('complex128') + _k = 2 * np.pi / l + + if self.wavefrontError: + if ip_type == 'surface': + ximg, yimg = self.sag_coordinates + wfeMap = interp2d(ximg[0,:], yimg[:,0], self.sag, kind=kind, **kwargs) + wavefrontErrorMap = wfeMap(x[0,:].real, y[:,0].real) + _t_l *= np.exp(1j * _k * wavefrontErrorMap ) + elif ip_type == 'phase': + try: + xy = deepcopy((x,y)) + xy_img = deepcopy(self.sag_coordinates) + wavefrontErrorMap = self.wfe.generate_height_map_window( + xy, xy_img, ip_type='phase', kind=kind) + _t_l *= np.exp(1j * _k * wavefrontErrorMap ) + except AttributeError: + print('No WavefrontError object detected. Continuing without!') if self.mask is not None: _t_l *= self.mask return _t_l + def add_wavefront_error(self, z, x, y, wfe=None, **kwargs): + assert len(z.shape)==2, 'Wavefront data does not have the proper shape. Cannot continue' + self.sag = z + # self.sag = interp2d(x, y, z, kind='cubic', **kwargs) + self.sag_coordinates = (x.real,y.real) + self.wavefrontError = True + if wfe is not None: + self.wfe = wfe + + + + diff --git a/fourierOptics/profiles.py b/fourierOptics/profiles.py index 0fae11d..95c7249 100644 --- a/fourierOptics/profiles.py +++ b/fourierOptics/profiles.py @@ -10,6 +10,32 @@ class Profiles: def __init__(self): pass + + def get_telescope_pupil(self, n, m, dpix, r1, r2, x0=0, y0=0, rotdegree=0, + oversample=1, aperture_type='hexagon', + obscuration_type='hexagon', undersize=False): + + _A_obscuration = np.ones((n,m)).astype(bool) + + if aperture_type=='hexagon': + _A_outer = self.hexagon(n, m, dpix, r1, x0, y0, rotdegree, oversample) + elif aperture_type == 'circular': + _A_outer = self.top_hat(r1, n, m, dpix, oversample, dx=x0, dy=y0) + elif aperture_type == 'ELT': + pup = self.ELT_pupil(n, m, dpix) + if undersize: + _A_obscuration = self.obscured_top_hat(r1, r2, n, m, dpix, oversample).astype(bool) + + if not aperture_type == 'ELT': + if obscuration_type=='hexagon': + _A_inner = self.hexagon(n, m, dpix, r2, x0, y0, rotdegree, oversample) + elif obscuration_type=='circular': + _A_inner = self.top_hat(r2, n, m, dpix, oversample, dx=x0, dy=y0) + pup = _A_outer.astype(int) - _A_inner.astype(int) + + return pup, _A_obscuration + + def top_hat(self, r, n, m, dpix, oversample=1, dx=0, dy=0): j,i = np.mgrid[-n/2*dpix : n/2*dpix : 1j*n*oversample, -m/2*dpix : m/2*dpix : 1j*m*oversample] dist_from_center = np.sqrt((i-dx)**2 + (j-dy)**2) @@ -17,22 +43,24 @@ class Profiles: return _Anew - def obscured_top_hat(self, r1, r2, n, m, dpix, oversample=1): + def obscured_top_hat(self, r1, r2, n, m, dpix, oversample=1, dx=0, dy=0): ''' Creates a grid of n x m points, following a top hat function where all points inside a radius r is 1 and 0 otherwise. ''' assert r1 > r2, "r2 < r1, change input" j,i = np.mgrid[-n/2*dpix : n/2*dpix : 1j*n*oversample, -m/2*dpix : m/2*dpix : 1j*m*oversample] - dist_from_center = np.sqrt(i**2 + j**2) + dist_from_center = np.sqrt((i-dx)**2 + (j-dy)**2) _Anew = (dist_from_center <= (r1)) & (dist_from_center >= (r2)) return _Anew.astype(int) - def spider_top_hat(self, r1, r2, n, m, dpix, oversample=1): + def spider_top_hat(self, r1, r2, n, m, dpix, oversample=1, thickness_fraction=0.05): ''' Creates a grid of n x m points, following a top hat function where all points inside a radius r is 1 and 0 otherwise. + + thickness_fraction - fraction of radius that makes the thickness of the spider arms ''' assert r1 > r2, "r2 < r1, change input" j,i = np.mgrid[-n/2*dpix : n/2*dpix : 1j*n*oversample, -m/2*dpix : m/2*dpix : 1j*m*oversample] @@ -40,7 +68,7 @@ class Profiles: m *= oversample dist_from_center = np.sqrt(i**2 + j**2) _Anew = (dist_from_center <= (r1)) & (dist_from_center >= (r2)) - _Aspider = (np.abs(i) < (0.05*r1)) | (np.abs(j) < (0.05*r1)) + _Aspider = (np.abs(i) < (thickness_fraction*r1)) | (np.abs(j) < (thickness_fraction*r1)) _Anew = _Anew & ~_Aspider return _Anew @@ -55,6 +83,28 @@ class Profiles: return np.exp(-((j-dx)**2 + (i-dy)**2) / r**2) + def gaussian_apodization(self, r1, r2, n, m, dpix, sigma_f=0.1, + oversample=1, x0=0, y0=0, amplitude=1): + j,i = np.mgrid[-n/2*dpix : n/2*dpix : 1j*n*oversample, -m/2*dpix : m/2*dpix : 1j*m*oversample] + _r = np.sqrt((i - x0)**2 + (j - y0)**2) + sigma = sigma_f * r1 + + # obscuration + _r_obsc = _r.copy() + _r_obsc = np.abs(_r - r2) + g1 = np.exp(-(_r_obsc**2) / (sigma**2)) + g1 = 1-g1 + g1[(_r < r2)] = 0 + + # outer radius + _r_out = np.sqrt((i - x0)**2 + (j - y0)**2) + _r_out = np.abs(_r_out - r1) + g2 = 1-np.exp(-(_r_out**2) / (sigma)**2) + g2[(_r > r1)] = 0 + out = g1 * g2 * amplitude + return out + + def ELT_pupil(self, n, m, dpix): # Find data directory __pkg_dir__ = os.path.dirname(os.path.realpath(__file__)) @@ -73,7 +123,8 @@ class Profiles: return _ELT_pupil - def bahtinov_mask(self, r1, r2, n, m, dpix, N, oversample=1, half_width_spiders=None): + def bahtinov_mask(self, r1, r2, n, m, dpix, N, oversample=1, half_width_spiders=None, + ELT_obscuration=False, thickness_fraction=0.02, rotdegree=0): ''' Creates a Bahtinov mask profile. This profile is a popular mask to focus a telescope in the @@ -82,7 +133,7 @@ class Profiles: assert r1 > r2, "r2 < r1, change input" if half_width_spiders is None: - half_width_spiders = 0.03 * r1 + half_width_spiders = 0.02 * r1 # Obscured top hat j,i = np.mgrid[-n/2*dpix : n/2*dpix : 1j*n*oversample, -m/2*dpix : m/2*dpix : 1j*m*oversample] @@ -91,6 +142,10 @@ class Profiles: _Anew = _Anew.astype(int) # Three quadrants of Bahtinov mask + if ELT_obscuration: + half_width_spiders = 0 + _Aspider = get_spider_structure(j,i,rotdegree, (thickness_fraction*r1) ) + _Anew = (_Anew & ~_Aspider).astype(int) _M1 = (i < -half_width_spiders) _M2 = (i > half_width_spiders) & (j > half_width_spiders) _M3 = (i > half_width_spiders) & (j < -half_width_spiders) @@ -101,20 +156,459 @@ class Profiles: _M4 = step(j, i, period=_d, rotation=0) _M5 = step(j, i, period=_d, rotation=30) _M6 = step(j, i, period=_d, rotation=-30) - _MC = (_M1 & _M4) | (_M2 & _M5) | (_M3 & _M6) + _MC = (_M1 & _M4) | (_M2 & _M5) | (_M3 & ~_M6) # Combining with the obscured top hat _Anew = _Anew * _MC return _Anew.astype(int) -def step(x, y, period=1, rotation=0): - ''' + def line_superposition_mask(self, r1, r2, n, m, dpix, N, oversample=1, rot_angle=60): + assert r1 > r2, "r2 < r1, change input" + + # Obscured top hat + j,i = np.mgrid[-n/2*dpix : n/2*dpix : 1j*n*oversample, -m/2*dpix : m/2*dpix : 1j*m*oversample] + dist_from_center = np.sqrt(i**2 + j**2) + _Anew = (dist_from_center <= (r1)) & (dist_from_center >= (r2)) + _Anew = _Anew.astype(int) + + # Generating the lines + _d = 2 * r1 / int(N) + _M4 = step(j-_d/2, i, period=_d, rotation=0) + _M5 = step(j, i, period=_d, rotation=rot_angle) + _M6 = step(j, i, period=_d, rotation=-rot_angle) + _MC = (_M4) * (_M5) * (_M6) + + # Combining with the obscured top hat + _Anew = _Anew * _MC + return _Anew.astype(int) + + + def hexagon(self, n,m, dpix, radius, x0=0, y0=0, rotdegree=30, oversample=1): + ''' + Create a hexagonal aperture shape with offset x0, y0. + ''' + j,i = np.mgrid[-n/2*dpix : n/2*dpix : 1j*n*oversample, -m/2*dpix : m/2*dpix : 1j*m*oversample] + j -= y0 # Offset the hexagon from the center + i -= x0 # Offset the hexagon from the center + + # mask frequency definition + A = np.pi/3 # 60 degrees + A0 = rotdegree * np.pi / 180 + msk = np.abs( np.cos(A0)*j + np.sin(A0)*i) < radius + msk = np.logical_and(msk, np.abs( np.cos(A + A0)*j + np.sin(A + A0)*i) < radius) + msk = np.logical_and(msk, np.abs( np.cos( 2 * A + A0) * j + np.sin(2*A + A0)*i)< radius) + k = np.sqrt(i**2 + j**2) + return msk + + + def hexagonal_bahtinov(self,n,m, dpix, r1, r2, lines=10, x0=0, y0=0, rotdegree=0, + thickness_fraction=0.01, oversample=1, aperture_type='hexagon', + obscuration_type='hexagon', undersize=False): + ''' + Creates a Bahtinov mask profile, but specially designed for an ELT like aperture + with six segments. + This might be used for the calibration of the ADC. + + Parameters: + ----------- + n, m - int + Integers describing the resolution of the array + dpix - int or float + Size in length units of each array element + r1 - float + Outer radius of the telescope entrance pupil + r2 - float + Inner radius of the telscope entrance pupil, e.g. the radius + of the central obscuration + lines - int + Number of lines that the mask should have over the diameter + of the telescope + x0, y0 - float + Lateral shift of the pupil mask + rotdegree - float + Rotation of the pupil mask in degrees + thickness_fraction - float + Fraction of the r1 describing the thickness of the spider arms + of the telescope + oversample - float + Oversampling of the array. Not tested. Not sure if it works. + aperture_type - str + Options: 'hexagon', 'circular' or 'ELT' + Gives a hexagonal, circular or official ELT like shape to the + pupil mask. + obscuration type - str + Options: 'hexagon', 'circular' + Gives a hexagonal or circular shape to the central obscuration. + This keyword is ignored if aperture_type == 'ELT'. + undersize - bool + If True, and if aperture_type == 'ELT', the values for r1 and r2 + will be used to create an undersized pupil mask. + + + Returns: + -------- + pup - Array + The pupil mask + ''' + + assert r1 > r2, "r2 < r1, change input" + + j,i = np.mgrid[-n/2*dpix : n/2*dpix : 1j*n*oversample, -m/2*dpix : m/2*dpix : 1j*m*oversample] + j -= y0 # Offset the hexagon from the center + i -= x0 # Offset the hexagon from the center + + pup, _A_obscuration = self.get_telescope_pupil( + n, m, dpix, r1, r2, x0, y0, rotdegree, + oversample, aperture_type, obscuration_type, undersize) + _Aspider = get_spider_structure(i,j,rotdegree, (thickness_fraction*r1) ) + + r_range = np.linspace(0, r1, int(lines+2)) + M_comb = np.zeros((n,m)).astype(int) + for i,r in enumerate(r_range[:-1:2]): + M_line_out = self.hexagon(n,m, dpix, r_range[2*i], x0, y0, rotdegree+30, oversample) + M_line_in = self.hexagon(n,m, dpix, r_range[2*i+1], x0, y0, rotdegree+30, oversample) + M_comb = M_comb | (M_line_out ^ M_line_in) + + mask = ((_Aspider | M_comb)).astype(bool) + pup[mask] = 0 + pup[~_A_obscuration] = 0 + + return pup + + + def triangular_apertures(self, n, m, dpix, r1, r2, lines=28, x0=0, y0=0, rotdegree=0, + thickness_fraction=0.01, oversample=1, aperture_type='hexagon', + obscuration_type='hexagon', undersize=False, rotate30=True): + ''' + Creates a mask with a triangular pattern, similar to line_superposition_mask() + but with a better filled pupil plane. + + Parameters: + ----------- + n, m - int + Integers describing the resolution of the array + dpix - int or float + Size in length units of each array element + r1 - float + Outer radius of the telescope entrance pupil + r2 - float + Inner radius of the telscope entrance pupil, e.g. the radius + of the central obscuration + lines - int + Number of lines that the mask should have over the diameter + of the telescope + x0, y0 - float + Lateral shift of the pupil mask + rotdegree - float + Rotation of the hexagonal pupil mask in degrees + thickness_fraction - float + Fraction of the r1 describing the thickness of the spider arms + of the telescope + oversample - float + Oversampling of the array. Not tested. Not sure if it works. + aperture_type - str + Options: 'hexagon', 'circular' or 'ELT' + Gives a hexagonal, circular or official ELT like shape to the + pupil mask. + obscuration type - str + Options: 'hexagon', 'circular' + Gives a hexagonal or circular shape to the central obscuration. + This keyword is ignored if aperture_type == 'ELT'. + undersize - bool + If True, and if aperture_type == 'ELT', the values for r1 and r2 + will be used to create an undersized pupil mask. + rotate30 - bool + If True, the output triangular mask will be rotated by 30 degrees + using a Transpose on the array. This can be useful to offset the + diffraction lines from the ELT spiders. Default: True + + + Returns: + -------- + pup - Array + The pupil mask + ''' + assert r1 > r2, "r2 < r1, change input" + + j,i = np.mgrid[-n/2*dpix : n/2*dpix : 1j*n*oversample, -m/2*dpix : m/2*dpix : 1j*m*oversample] + j -= y0 # Offset the hexagon from the center + i -= x0 # Offset the hexagon from the center + + pup, _A_obscuration = self.get_telescope_pupil( + n, m, dpix, r1, r2, x0, y0, rotdegree, + oversample, aperture_type, obscuration_type, undersize) + _Aspider = get_spider_structure(i,j,rotdegree, (thickness_fraction*r1) ) + pup[~_A_obscuration | _Aspider] = 0 + + # Generating the lines + rot_angle = 60 + _d = 2 * r1 / int(lines) * 2 + _f = np.tan(np.deg2rad(rot_angle)) + + _M1 = step(j, i, period=_d, rotation=0) + _M2 = step(j, i, period=_d, rotation= rot_angle) + _M3 = step(j, i, period=_d, rotation=-rot_angle) + + _M4 = step(j, i, period=_d, rotation=0, shiftx=0, shifty=_d*_f) + _M5 = step(j, i, period=_d, rotation= rot_angle) + _M6 = step(j, i, period=_d, rotation=-rot_angle) + + _M7 = step(j, i, period=_d, rotation=0, shiftx=_d/2, shifty=_d/2*_f) + _M8 = step(j, i, period=_d, rotation= rot_angle) + _M9 = step(j, i, period=_d, rotation=-rot_angle) + + _M10 = step(j, i, period=_d, rotation=0, shiftx=_d, shifty=0) + _M11 = step(j, i, period=_d, rotation=rot_angle) + _M12 = step(j, i, period=_d, rotation=-rot_angle) + + _MC1 = (_M1 | _M2 | _M3 ) + _MC2 = (_M4 | _M5 | _M6 ) + _MC3 = (_M7 | _M8 | _M9 ) + _MC4 = (_M10 | _M11 | _M12) + + if rotate30: + pup *= (_MC1 & _MC2 & _MC3 & _MC4).T + else: + pup *= (_MC1 & _MC2 & _MC3 & _MC4) + return pup + + + def triangular_bahtinov(self, n, m, dpix, r1, r2, lines=28, thickness_fraction=0.03, + x0=0, y0=0, rotdegree=0, oversample=1, aperture_type='hexagon', + obscuration_type='hexagon', undersize=False, rotate30=True): + ''' + Creates a Bahtinov mask profile, but specially designed for an ELT like aperture + with six segments. The pupil is still divided in three segments. + This might be used for the calibration of the ADC. + + Parameters: + ----------- + n, m - int + Integers describing the resolution of the array + dpix - int or float + Size in length units of each array element + r1 - float + Outer radius of the telescope entrance pupil + r2 - float + Inner radius of the telscope entrance pupil, e.g. the radius + of the central obscuration + lines - int + Number of lines that the mask should have over the diameter + of the telescope + thickness_fraction - float + Fraction of the pupil diameter that is used as the thickness + of the spider arms. For example, this can be used to block + the thermal radiation of the spider arms. + x0, y0 - float + Lateral shift of the pupil mask + rotdegree - float + Rotation of the pupil mask in degrees + thickness_fraction - float + Fraction of the r1 describing the thickness of the spider arms + of the telescope + oversample - float + Oversampling of the array. Not tested. Not sure if it works. + aperture_type - str + Options: 'hexagon', 'circular' or 'ELT' + Gives a hexagonal, circular or official ELT like shape to the + pupil mask. + obscuration type - str + Options: 'hexagon', 'circular' + Gives a hexagonal or circular shape to the central obscuration. + This keyword is ignored if aperture_type == 'ELT'. + undersize - bool + If True, and if aperture_type == 'ELT', the values for r1 and r2 + will be used to create an undersized pupil mask. + rotate30 - bool + Rotates the grating pattern by 30 degrees if True. So that the + diffraction speckles are not a the same angle as the diffraction + spikes from the M2 structure. + + + Returns: + -------- + pup - Array + The pupil mask + ''' + assert r1 > r2, "r2 < r1, change input" + + j,i = np.mgrid[-n/2*dpix : n/2*dpix : 1j*n*oversample, -m/2*dpix : m/2*dpix : 1j*m*oversample] + j -= y0 # Offset the hexagon from the center + i -= x0 # Offset the hexagon from the center + + pup, _A_obscuration = self.get_telescope_pupil( + n, m, dpix, r1, r2, x0, y0, rotdegree, + oversample, aperture_type, obscuration_type, undersize) + _Aspider = get_spider_structure(i,j,rotdegree, (thickness_fraction*r1) ) + + + # Three quadrants of the classic Bahtinov mask + A = np.pi/3 + A0 = rotdegree * np.pi / 180 + Aoff = np.pi/6 if rotate30 else 0 + _M1 = np.sin(1 * A + A0 + Aoff) * i + np.cos(1 * A + A0 + Aoff) * j + _M2 = np.sin(2 * A + A0 + Aoff) * i + np.cos(2 * A + A0 + Aoff) * j + _M3 = np.sin(3 * A + A0 + Aoff) * i + np.cos(3 * A + A0 + Aoff) * j + _M1m = ~((_M1 < 0) | (_M2 < 0)) + _M2m = ~((_M2 > 0) | (_M3 > 0)) + _M3m = ~((_M3 < 0) | (_M1 > 0)) + + # Generating the lines + _d = 2 * r1 / int(lines) + _M4 = step(j, i, period=_d, rotation= 60+rotdegree+90) + _M5 = step(j, i, period=_d, rotation= 0+rotdegree+90) + _M6 = step(j, i, period=_d, rotation=-60+rotdegree+90) + _MC = (_M1m & ~_M4) | (_M2m & _M5) | (_M3m & ~_M6) + + # Combining with the telescope pupil + mask = ((~_Aspider & ~_MC)).astype(bool) + pup[~mask] = 0 + pup[~_A_obscuration] = 0 + return pup + + + def square_apertures(self, n, m, dpix, r1, r2, lines=28, thickness_fraction=0.03, + x0=0, y0=0, rotdegree=0, oversample=1, aperture_type='hexagon', + obscuration_type='hexagon', undersize=False): + ''' + Creates a Bahtinov mask profile, but specially designed for an ELT like aperture + with six segments. The pupil is still divided in three segments. + This might be used for the calibration of the ADC. + + Parameters: + ----------- + n, m - int + Integers describing the resolution of the array + dpix - int or float + Size in length units of each array element + r1 - float + Outer radius of the telescope entrance pupil + r2 - float + Inner radius of the telscope entrance pupil, e.g. the radius + of the central obscuration + lines - int + Number of lines that the mask should have over the diameter + of the telescope + thickness_fraction - float + Fraction of the pupil diameter that is used as the thickness + of the spider arms. For example, this can be used to block + the thermal radiation of the spider arms. + x0, y0 - float + Lateral shift of the pupil mask + rotdegree - float + Rotation of the pupil mask in degrees + thickness_fraction - float + Fraction of the r1 describing the thickness of the spider arms + of the telescope + oversample - float + Oversampling of the array. Not tested. Not sure if it works. + aperture_type - str + Options: 'hexagon', 'circular' or 'ELT' + Gives a hexagonal, circular or official ELT like shape to the + pupil mask. + obscuration type - str + Options: 'hexagon', 'circular' + Gives a hexagonal or circular shape to the central obscuration. + This keyword is ignored if aperture_type == 'ELT'. + undersize - bool + If True, and if aperture_type == 'ELT', the values for r1 and r2 + will be used to create an undersized pupil mask. + + + Returns: + -------- + pup - Array + The pupil mask + ''' + + assert r1 > r2, "r2 < r1, change input" + + j,i = np.mgrid[-n/2*dpix : n/2*dpix : 1j*n*oversample, -m/2*dpix : m/2*dpix : 1j*m*oversample] + j -= y0 # Offset the hexagon from the center + i -= x0 # Offset the hexagon from the center + + pup, _A_obscuration = self.get_telescope_pupil( + n, m, dpix, r1, r2, x0, y0, rotdegree, + oversample, aperture_type, obscuration_type, undersize) + _Aspider = get_spider_structure(i,j,rotdegree, (thickness_fraction*r1) ) + + # Generating the lines + _d = 4 / np.sqrt(2) * r1 / int(lines) + _M1 = step(j, i, period=_d, rotation= 45+rotdegree) + _M2 = step(j, i, period=_d, rotation= -45+rotdegree) + _M4 = step(j, i, period=_d, rotation= -45+rotdegree) + _M5 = step(j, i, period=_d, rotation= 45+rotdegree) + _MC = (_M1 & _M2) | (~_M4 & ~_M5) + + mask = ((~_Aspider & ~_MC)).astype(bool) + pup[~mask] = 0 + pup[~_A_obscuration] = 0 + return pup + + + def circular_apertures(self, n, m, dpix, r1, r2, lines=28, thickness_fraction=0.03, + x0=0, y0=0, rotdegree=0, oversample=1, aperture_type='hexagon', + obscuration_type='hexagon', undersize=False, margin=0.05): + + assert r1 > r2, "r2 < r1, change input" + + j,i = np.mgrid[-n/2*dpix : n/2*dpix : 1j*n*oversample, -m/2*dpix : m/2*dpix : 1j*m*oversample] + j -= y0 # Offset the hexagon from the center + i -= x0 # Offset the hexagon from the center + + pup, _A_obscuration = self.get_telescope_pupil( + n, m, dpix, r1, r2, x0, y0, rotdegree, + oversample, aperture_type, obscuration_type, undersize) + _Aspider = get_spider_structure(i,j,rotdegree, (thickness_fraction*r1) ) + + # Defining the radius and coordinates of the circular apertures + _rm = r1 / int(lines) # Radius of small circular apertures + _ic = np.arange(-n/2*dpix, n/2*dpix, np.sqrt(3)*_rm) + _jc = np.arange(-m/2*dpix, m/2*dpix, 2*_rm) + + Amask = np.zeros((n,m)) + for index_i, ic in enumerate(_ic): + for index_j, jc in enumerate(_jc): + if np.sqrt(ic**2 + jc**2) > (r1 + 2*_rm): + continue + if index_i % 2 == 1: + jc += _rm + dist_from_center = np.sqrt((i-ic)**2 + (j-jc)**2) + mask = (dist_from_center < (_rm * (1-margin))) + Amask[mask] = 1 + + mask = Amask.astype(bool) & ~_Aspider + pup[~mask] = 0 + pup[~_A_obscuration] = 0 + return pup + + # Aout = _Anew & Amask.astype(bool) & ~_Aspider + # return Aout + + +def step(x, y, period=1, rotation=0, shiftx=0, shifty=0): + ''' Function that returns a 2D square wave with period and rotation. - ''' - theta = np.deg2rad(rotation) - xn = x * np.cos(theta) - y * np.sin(theta) - yn = x * np.sin(theta) + y * np.cos(theta) - a = ((2/period * xn ).astype(int) % 2) & (xn>= 0) - b = ((2/period * xn - 1).astype(int) % 2) & (xn< 0) - return a + b \ No newline at end of file + ''' + theta = np.deg2rad(rotation) + x -= shiftx + y -= shifty + xn = x * np.cos(theta) - y * np.sin(theta) + yn = x * np.sin(theta) + y * np.cos(theta) + a = ((2/period * xn ).astype(int) % 2) & (xn>= 0) + b = ((2/period * xn - 1).astype(int) % 2) & (xn< 0) + return a + b + + +def get_spider_structure(x, y, rotdegree, thickness): + # Spiders of the ELT + # mask definition + A = np.pi/3 # 60 degrees + A0 = rotdegree * np.pi / 180 + _Aspider1 = (np.abs( np.sin(1 * A + A0) * x + np.cos(1 * A + A0) * y ) < thickness) + _Aspider2 = (np.abs( np.sin(2 * A + A0) * x + np.cos(2 * A + A0) * y ) < thickness) + _Aspider3 = (np.abs( np.sin(3 * A + A0) * x + np.cos(3 * A + A0) * y ) < thickness) + _Aspider = _Aspider1 | _Aspider2 | _Aspider3 + return _Aspider + diff --git a/fourierOptics/wavefrontErrors.py b/fourierOptics/wavefrontErrors.py index 2f22c22..d1b76b8 100644 --- a/fourierOptics/wavefrontErrors.py +++ b/fourierOptics/wavefrontErrors.py @@ -3,6 +3,7 @@ import pyfftw import types from math import ceil from scipy.special import factorial, j1 +from scipy import interpolate class WavefrontError: ''' @@ -259,6 +260,7 @@ class WavefrontError: _PSD = (s0**2 * A) / (_h0_norm * (1 + np.power((_rho_mn_list/rho_c), p))) self.PSD2D = _PSD + self.rho_mn_list = _rho_mn_list return _PSD, _rho_mn_list @@ -287,7 +289,8 @@ class WavefrontError: _dx = 1 / self.m _dy = 1 / self.n _A = (self.m * _dx) * (self.n * _dy) - _phase = np.random.uniform(0,1,(self.m,self.m)) + _phase = np.random.uniform(0,1,(self.n,self.m)) + self.phase = _phase # Calculating the PSD and phase _PSD, _rho_mn_list = self.power_spectral_density(s0, p, rho_c, _dx, _dy, _A) @@ -315,6 +318,83 @@ class WavefrontError: return self.data + def generate_height_map_window(self, xy, xy_img, ip_type='phase', kind='cubic', addToData=False): + ''' + This function takes the already generated heigh map + and interpolates within a small window. This can be useful + when the wavefront error is near the focal plane. + + xy - tuple containing the x and y coordinates of the small window + xy_img - tuple containing the x and y coordinates of the full plane + to which the wfe is applied. + ip_type - 'phase' or 'surface' + Defines what method to use to obtain the interpolated surface + height map. 'surface' does a normal interpolation on the height + map. This does not conserve the WFE rms value. Then, 'phase' + interpolates on the phase information in the PSD algorithm. This + does conserve the WFE rms within the window. + ''' + # normalise the coordinates to -1 to 1 + ximg, yimg = xy_img + ximg, yimg = ximg.real, yimg.real + x, y = xy + x, y = x.real, y.real + x /= ximg.max() + y /= yimg.max() + ximg /= ximg.max() + yimg /= yimg.max() + scale = (x.max() - x.min()) + + if ip_type == 'phase': + assert hasattr(self, 'phase'), 'No phase information present. Run generate_height_map() first!' + + # Calculation of the necessary constants + _dx = 1 / self.m + _dy = 1 / self.n + _A = (self.m * _dx) * (self.n * _dy) + + # Interpolate the phase information + try: + _phase_window = self.phase_ip(x[0,:], y[:,0]) + except AttributeError: + self.phase_ip = interpolate.interp2d(ximg[0,:], yimg[:,0], self.phase, kind=kind) + _phase_window = self.phase_ip(x[0,:], y[:,0]) + + _PSDw, _rho_mn_listw = self.PSD2D, self.rho_mn_list + _phiMap_window = self.phase_map(_phase_window) + _PSDw = np.fft.ifftshift(_PSDw) + + # Doing the fourier transform of the window + _inw = pyfftw.empty_aligned((self.n,self.m), dtype='complex128') + _inw[:] = np.exp( 1j * _phiMap_window) * np.sqrt(_A * np.abs(_PSDw)) + _outw = pyfftw.empty_aligned((self.n,self.m), dtype='complex128') + _FFTW_Objw = pyfftw.FFTW( _inw, _outw, axes=(0,1), direction='FFTW_BACKWARD', threads=6, flags=('FFTW_ESTIMATE',)) + _FFTW_Objw() + + _hMapw = 1 / ( _dx * _dy ) * _outw + rmsw = np.sqrt(np.mean(np.asarray(_hMapw.real)**2)) + ptvw = np.abs(np.max(_hMapw.real)) + np.abs(np.min(_hMapw.real)) + + elif ip_type == 'surface': + print('Hello there!') + assert hasattr(self, 'data'), 'No wfe data available to interpolate on!' + try: + _hMapw = self.wfe_ip(x[0,:], y[:,0]) + except AttributeError: + print('Hello there 2!') + self.wfe_ip = interpolate.interp2d(ximg[0,:], yimg[:,0], self.data, kind=kind) + _hMapw = self.wfe_ip(x[0,:], y[:,0]) + + if not addToData: + self.data_window = _hMapw.real + else: + self.data_window += _hMapw.real + + return _hMapw.real + + + + def scale_rms(self, factor, data=None, filteredDataFlag=False): # Scale the surface data such that the RMS gets multiplied by factor _factor = factor ** 2 diff --git a/scripts/SPIE/Thesis/Adding a WFE in the focal plane.ipynb b/scripts/SPIE/Thesis/Adding a WFE in the focal plane.ipynb new file mode 100644 index 0000000..a959bee --- /dev/null +++ b/scripts/SPIE/Thesis/Adding a WFE in the focal plane.ipynb @@ -0,0 +1,12080 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b0747294", + "metadata": {}, + "source": [ + "# Interpolating a provided WFE to a small subsection of the field\n", + "\n", + "The combination of Zernike modes and the PSD description of a WFE combine to provide a fantastic WFE over a large area. However, the resolution is not suitable to apply this at or near the focal plane of a system. Therefore, we need to find a way to interpolate the WFE in a way that is suitable.\n", + "\n", + "I see two primary ways of doing:\n", + "\n", + "1. Interpolate the original wavefront to a new area, assuming that the new area is a small part contained within the original large area. This will not add any data to the WFE. But then the WFE at the smaller area will primarily be a tip/tilt effect, without any smaller structure.\n", + "2. Interpolate the phase information of the WFE, while retaining the PSD relationship. Because the phase is randomly chosen, we could assume that the phase is actually continous at smaller scales. Then the points in the original data are consistent with the zoomed in area, while adding new structure as well. At least, that is how I think this works...\n", + "\n", + "Let's first try to recreate the original way of doing a WFE.\n", + "\n", + "\n", + "## Method #1: Interpolate on the generated WFE amplitude\n", + "We start with the built in way, i.e. the way it's currently implemented in the fourierOptics package for the MICADO ADC." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "faaa95c2", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import sys\n", + "sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Documents/Published/FourierOptics GitHub release/')\n", + "from fourierOptics import *" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "66c72ac8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Your resolution is 3001x3001 with a pixel size of 50\n", + "This means your grid is 150050.0 mm by 150050.0 mm, with each pixel 50 mm.\n", + "Generating new ADC WFE...\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "l = 1.49e-3\n", + "mcd = ParaxialMICADO(n=3001, m=3001, dpix=50)\n", + "wfe = WavefrontError(mcd.n, mcd.m)\n", + "\n", + "_, x3, y3 = mcd.propagate(l=l,surface=4, zoom=False, show=False, \n", + " print_progress=False)\n", + "wfe_p1 = ADC_surfaces(x3,y3)\n", + "# wfe_p1.generate_ADC_wfe(zernRMS=20e-6, psdRMS=15e-6)\n", + "wfe_p1.generate_ADC_wfe(zernRMS=10e-6, psdRMS=5e-6)\n", + "\n", + "mask = np.sqrt(wfe_p1.xz**2 + wfe_p1.yz**2) > 1\n", + "\n", + "surf_p1 = wfe_p1.latest_surface.copy()\n", + "surf_p1[mask]=np.nan\n", + "figp1, axp1 = plot_real_valued_results(surf_p1[850:2150,850:2150]*1e6, x3[850:2150,850:2150], y3[850:2150,850:2150],\n", + " colormap='jet', labeltext='Surface height (nm)', fig_size=None, show=False)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "07a53a6d", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "from scipy import interpolate\n", + "# Get the coordinates of the Focal Plane that we want to add a WFE to.\n", + "_, xfp, yfp = mcd.propagate(l=l,surface=1, zoom=True, show=False, \n", + " print_progress=False)\n", + "\n", + "surf_og = wfe_p1.latest_surface.copy()\n", + "surf_og_ip = interpolate.interp2d(x3[0,:].real, y3[:,0].real, surf_og, \n", + " kind='cubic')\n", + "\n", + "fp_wfe = surf_og_ip(xfp[0,:].real, yfp[:,0].real)\n", + "\n", + "figp1, axp1 = plot_real_valued_results(surf_p1[850:2150,850:2150]*1e6, x3[850:2150,850:2150], y3[850:2150,850:2150],\n", + " colormap='jet', labeltext='Surface height (nm)', fig_size=None, show=False)\n", + "plt.show()\n", + "\n", + "figp2, axp2 = plot_real_valued_results(fp_wfe*1e6, xfp, yfp,\n", + " colormap='jet', labeltext='Surface height (nm)', fig_size=None, show=False)\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "id": "7886cc59", + "metadata": {}, + "source": [ + "Unfortunately, this does not contain any information on the phase and PSD distribution anymore. So we need to return to the original functions to try approach #2." + ] + }, + { + "cell_type": "markdown", + "id": "1704d14a", + "metadata": {}, + "source": [ + "## Method #2: Rebuild the PSD function and use the phase information\n", + "\n", + "If we assume that we want to obtain a small window of the original WFE, which covers the full plane. This small window has a diameter $d$ and the full plane has a diameter $D$. \n", + "\n", + "Also, the PSD curve should in principle be space invariant, where only the range of spatial frequencies that are sampled change. Based on this, we can say that we keep our original expression of the PSD, but change the spatial frequencies with the change in size. So,\n", + "\n", + "$$\n", + "u' = \\left(\\frac{d}{D}\\right) u \\\\\n", + "v' = \\left(\\frac{d}{D}\\right) v\n", + "$$\n", + "\n", + "and to keep things consistent, we should also scale the cut-off frequency $\\rho_c$ with the same factor, \n", + "\n", + "$$\n", + "\\rho_c' = \\left(\\frac{d}{D}\\right) \\rho_c\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "7ad57761", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pyfftw\n", + "import types\n", + "from math import ceil\n", + "from scipy.special import factorial, j1\n", + "\n", + "def power_spectral_density(s0, p, rho_c, dx, dy, A, m=1001, n=1001):\n", + " # Lorentzian equation used as PSD function\n", + " UmList = [(M - ( m + 1) / 2) / (m * dx) for M in range(m)]\n", + " VnList = [(N - ( n + 1) / 2) / (n * dy) for N in range(n)]\n", + " VnList, UmList = np.meshgrid(UmList, VnList)\n", + "\n", + " _rho_mn_list = np.sqrt(UmList**2 + VnList**2)\n", + " _h0list = 1 / ( 1 + np.power( ( _rho_mn_list / rho_c ), p) )\n", + " _h0_norm = np.sum(_h0list)\n", + " _rho_mn_list = np.asarray(_rho_mn_list).reshape(m,n)\n", + "\n", + " _PSD = (s0**2 * A) / (_h0_norm * (1 + np.power((_rho_mn_list/rho_c), p)))\n", + " PSD2D = _PSD\n", + "\n", + " return _PSD, _rho_mn_list\n", + "\n", + "\n", + "def phase_map(phase, m=1001, n=1001):\n", + " # Uniform distribution of the phase\n", + " _phase_in\t \t\t= pyfftw.empty_aligned((n,m), dtype='complex128')\n", + " _phase_in[:] \t\t= phase\n", + " _phase_out\t \t\t= pyfftw.empty_aligned((n, m), dtype='complex128')\n", + " _FFTW_Obj \t \t\t= pyfftw.FFTW( _phase_in, _phase_out, axes=(0,1), direction='FFTW_FORWARD', threads=6, flags=('FFTW_ESTIMATE',))\n", + " _FFTW_Obj()\n", + " _phaseAngle \t\t= np.angle(_phase_out)\n", + " return _phaseAngle\n", + "\n", + "\n", + "def generate_height_map(s0, p, rho_c, m=1001, n=1001):\n", + " '''\n", + " This function generates a surface height distribution based\n", + " on a Lorentzian PSD profile. Following the paper by E. Sidick (2009).\n", + "\n", + " 2020-01-20: Removed \"mean=1e-4\" from function def. --> Did this do anything?\n", + " '''\n", + "\n", + " # Calculation of the necessary constants\n", + " _dx \t\t\t\t= 1 / m\n", + " _dy \t\t\t\t= 1 / n\n", + " _A \t\t\t\t\t= (m * _dx) * (n * _dy)\n", + " _phase \t\t\t\t= np.random.uniform(0,1,(n,m))\n", + "\n", + " # Calculating the PSD and phase\n", + " _PSD, _rho_mn_list \t= power_spectral_density(s0, p, rho_c, _dx, _dy, _A)\n", + " _phiMap \t\t\t= phase_map(_phase)\n", + "\n", + " # Shifting the coordinates for the FFT\n", + " _PSD \t\t\t\t= np.fft.ifftshift(_PSD)\n", + "\n", + " # Doing the fourier transform\n", + " _in\t \t\t\t\t= pyfftw.empty_aligned((n,m), dtype='complex128')\n", + " _in[:] \t\t\t\t= np.exp( 1j * _phiMap) * np.sqrt(_A * np.abs(_PSD))\n", + " _out\t \t\t\t= pyfftw.empty_aligned((n,m), dtype='complex128')\n", + " _FFTW_Obj \t \t\t= pyfftw.FFTW( _in, _out, axes=(0,1), direction='FFTW_BACKWARD', threads=6, flags=('FFTW_ESTIMATE',))\n", + " _FFTW_Obj()\t\n", + "\n", + " # The final height distribution\n", + " _hMap \t\t\t\t= 1 / ( _dx * _dy ) * _out\n", + " data = _hMap.real\n", + " rms \t\t\t= np.sqrt(np.mean(np.asarray(data)**2))\n", + " ptv \t\t\t= np.abs(np.max(data)) + np.abs(np.min(data))\n", + " return data" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "886e3a48", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[1.93633170e-05 2.10425308e-05 2.77771267e-05 ... 1.44274571e-05\n", + " 2.27325109e-05 2.23404535e-05]\n", + " [1.91990992e-05 3.48035221e-05 3.07693450e-05 ... 2.45813303e-05\n", + " 2.74385316e-05 3.09157219e-05]\n", + " [3.95044135e-05 3.58946453e-05 3.75796845e-05 ... 1.44757173e-05\n", + " 2.79688859e-05 3.20674296e-05]\n", + " ...\n", + " [1.26774660e-05 1.01140328e-05 2.02602288e-05 ... 1.63181162e-05\n", + " 7.44747354e-06 1.35122524e-05]\n", + " [1.02753006e-05 1.66528385e-06 7.30292121e-06 ... 7.90939382e-08\n", + " 2.18146860e-06 5.33645131e-06]\n", + " [2.51955094e-05 1.47003151e-05 2.25325249e-05 ... 1.68272061e-05\n", + " 5.81322513e-06 2.11535448e-05]]\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "hsurf = generate_height_map(15e-6, 2, 1)\n", + "print(hsurf)\n", + "plt.figure()\n", + "plt.imshow(hsurf, cmap='jet')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "83c177ad", + "metadata": {}, + "outputs": [], + "source": [ + "def generate_height_map_ip(s0, p, rho_c, xy_og, xy_new, m=1001, n=1001, phase=None):\n", + " '''\n", + " This function generates a surface height distribution based\n", + " on a Lorentzian PSD profile. Following the paper by E. Sidick (2009).\n", + "\n", + " 2020-01-20: Removed \"mean=1e-4\" from function def. --> Did this do anything?\n", + " '''\n", + " # Normalise coordinates\n", + " xog, yog = xy_og\n", + " xn, yn = xy_new\n", + " xnorm = xog/xog.max()\n", + " ynorm = yog/yog.max()\n", + " xwin = xn/xog.max()\n", + " ywin = yn/yog.max()\n", + " scale = (xwin.real.max() - xwin.real.min())/(xnorm.real.max() - xnorm.real.min())\n", + " \n", + " # Calculation of the necessary constants\n", + " _dx \t\t\t\t= 1 / m\n", + " _dy \t\t\t\t= 1 / n\n", + " _A \t\t\t\t\t= (m * _dx) * (n * _dy)\n", + " \n", + " if phase is None:\n", + " _phase \t\t\t\t= np.random.uniform(0,1,(n,m))\n", + " else:\n", + " _phase = phase\n", + " \n", + " # Interpolation of the phase info\n", + "# _phase_ip = interpolate.interp2d(xnorm[0,:].real, ynorm[:,0].real, _phase, kind='cubic')\n", + " _phase_ip = interpolate.RectBivariateSpline(ynorm[:,0].real, xnorm[0,:].real, _phase, kx=5, ky=5)\n", + "# _phase_window = _phase_ip(xwin[0,:].real, ywin[:,0].real)\n", + " _phase_window = _phase_ip(ywin[:,0].real, xwin[0,:].real)\n", + "\n", + " # Calculating the PSD and phase\n", + " _PSD, _rho_mn_list \t= power_spectral_density(s0, p, rho_c, _dx, _dy, _A, m=m, n=n)\n", + " _phiMap \t\t\t= phase_map(_phase, m=m, n=n)\n", + "\n", + " # Shifting the coordinates for the FFT\n", + " _PSD \t\t\t\t= np.fft.ifftshift(_PSD)\n", + " \n", + " # Doing the fourier transform\n", + " _in\t \t\t\t\t= pyfftw.empty_aligned((n,m), dtype='complex128')\n", + " _in[:] \t\t\t\t= np.exp( 1j * _phiMap) * np.sqrt(_A * np.abs(_PSD))\n", + " _out\t \t\t\t= pyfftw.empty_aligned((n,m), dtype='complex128')\n", + " _FFTW_Obj \t \t\t= pyfftw.FFTW( _in, _out, axes=(0,1), direction='FFTW_BACKWARD', threads=6, flags=('FFTW_ESTIMATE',))\n", + " _FFTW_Obj()\t \n", + " \n", + " # The final height distribution\n", + " _hMap \t\t\t\t= 1 / ( _dx * _dy ) * _out\n", + " data = _hMap.real\n", + " rms \t\t\t= np.sqrt(np.mean(np.asarray(data)**2))\n", + " ptv \t\t\t= np.abs(np.max(data)) + np.abs(np.min(data))\n", + " \n", + " # Now the smaller window\n", + " # Calculating the PSD and phase for the window\n", + " s0window = rms # The rms based on the window of the original data.\n", + " print(s0window)\n", + " _PSDw, _rho_mn_listw \t= power_spectral_density(s0window, p, rho_c, _dx, _dy, _A, m=m, n=n)\n", + " # Calculating the scaled phase\n", + " _phiMap_window \t= phase_map(_phase_window, m=m, n=n)\n", + "\n", + " # Shifting the coordinates for the FFT\n", + " _PSDw = np.fft.ifftshift(_PSDw)\n", + " \n", + " # Doing the fourier transform of the window\n", + " _inw\t \t\t\t\t= pyfftw.empty_aligned((n,m), dtype='complex128')\n", + " _inw[:] \t\t\t\t= np.exp( 1j * _phiMap_window) * np.sqrt(_A * np.abs(_PSD))\n", + " _outw\t \t\t\t= pyfftw.empty_aligned((n,m), dtype='complex128')\n", + " _FFTW_Objw \t \t\t= pyfftw.FFTW( _inw, _outw, axes=(0,1), direction='FFTW_BACKWARD', threads=6, flags=('FFTW_ESTIMATE',))\n", + " _FFTW_Objw()\t\n", + "\n", + " _hMapw \t\t\t\t= 1 / ( _dx * _dy ) * _outw\n", + " dataw = _hMapw.real\n", + " rmsw \t\t\t= np.sqrt(np.mean(np.asarray(dataw)**2))\n", + " ptvw \t\t\t= np.abs(np.max(dataw)) + np.abs(np.min(dataw))\n", + " print(rmsw)\n", + " _hMap_ip = interpolate.interp2d(xnorm[0,:].real, ynorm[:,0].real, data, kind='cubic')\n", + " dataip = _hMap_ip(xwin[0,:].real, ywin[:,0].real)\n", + " \n", + " return data, dataw, dataip" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "ee230029", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.4733511996914156e-05\n", + "2.4733511996914153e-05\n", + "2.4733511996914156e-05\n", + "2.473351199691415e-05\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.close('all')\n", + "xy3 = (x3, y3)\n", + "xyfp = (xfp, yfp)\n", + "\n", + "xfp2 = xfp + 1e-2 * xfp.max()\n", + "yfp2 = yfp\n", + "xyfp2 = (xfp2, yfp2)\n", + "\n", + "x3n = x3 / x3.max()\n", + "y3n = y3 / y3.max()\n", + "\n", + "# phasegen2 = np.random.uniform(0,1,x3.shape)\n", + "# phasegen = np.random.normal(0,1,x3.shape)\n", + "d, dw, dip = generate_height_map_ip(25e-6, 2, 1, xy3, xyfp, m=3001, n=3001, phase=phasegen2)\n", + "d2,dw2, dip2 = generate_height_map_ip(25e-6, 2, 1, xy3, xyfp2, m=3001, n=3001, phase=phasegen2)\n", + "\n", + "fig, axes = plt.subplots(ncols=3, nrows=2, sharex=True, sharey=True, figsize=(8,8))\n", + "axes[0,0].imshow(d, cmap='jet', extent=[x3.min(), x3.max(), y3.min(), y3.max()], vmin=-5e-5, vmax=5e-5)\n", + "axes[0,1].imshow(dw, cmap='jet', extent=[xfp.min(), xfp.max(), yfp.min(), yfp.max()], vmin=-5e-5, vmax=5e-5)\n", + "axes[0,2].imshow(dip, cmap='jet', extent=[xfp.min(), xfp.max(), yfp.min(), yfp.max()], vmin=-5e-5, vmax=5e-5)\n", + "axes[1,0].imshow(d2, cmap='jet', extent=[x3.min(), x3.max(), y3.min(), y3.max()], vmin=-5e-5, vmax=5e-5)\n", + "axes[1,1].imshow(dw2, cmap='jet', extent=[xfp2.min(), xfp2.max(), yfp2.min(), yfp2.max()], vmin=-5e-5, vmax=5e-5)\n", + "axes[1,2].imshow(dip2, cmap='jet', extent=[xfp2.min(), xfp2.max(), yfp2.min(), yfp2.max()], vmin=-5e-5, vmax=5e-5)\n", + "axes[0,0].set_xlim([xfp.min(),xfp.max()])\n", + "axes[0,0].set_ylim([yfp.min(),yfp.max()])\n", + "axes[1,0].set_xlim([xfp.min(),xfp.max()])\n", + "axes[1,0].set_ylim([yfp.min(),yfp.max()])\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "53a03cce", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "6289acab", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.4840107198148489e-05\n", + "1.4840107198148484e-05\n", + "1.484010719814849e-05\n" + ] + } + ], + "source": [ + "fig, axes = plt.subplots(ncols=2, nrows=2, sharex=True, sharey=True, figsize=(10,8))\n", + "axes[0,0].imshow(d, cmap='jet', extent=[x3.min(), x3.max(), y3.min(), y3.max()], vmin=-5e-5, vmax=5e-5)\n", + "axes[0,1].imshow(dw, cmap='jet', extent=[xfp.min(), xfp.max(), yfp.min(), yfp.max()], vmin=-5e-5, vmax=5e-5)\n", + "axes[1,0].imshow(d2, cmap='jet', extent=[x3.min(), x3.max(), y3.min(), y3.max()], vmin=-5e-5, vmax=5e-5)\n", + "axes[1,1].imshow(dw2, cmap='jet', extent=[xfp2.min(), xfp2.max(), yfp2.min(), yfp2.max()], vmin=-5e-5, vmax=5e-5)\n", + "\n", + "axes[0,0].set_xlim([-1,1])\n", + "axes[0,0].set_ylim([-1,1])\n", + "axes[1,0].set_xlim([-1,1])\n", + "axes[1,0].set_ylim([-1,1])\n", + "plt.show()\n", + "\n", + "print(np.sqrt(np.mean(np.asarray(d)**2)))\n", + "print(np.sqrt(np.mean(np.asarray(dw)**2)))\n", + "print(np.sqrt(np.mean(np.asarray(dw2)**2)))" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "751c5615", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "phasegen = np.random.uniform(0,1,x3.shape)\n", + "phasegen_ip = interpolate.interp2d(x3[0,:].real, y3[:,0].real, phasegen, kind='cubic', fill_value=np.nan)\n", + "phase1 = phasegen_ip(xfp[0,:].real, yfp[:,0].real)\n", + "phasegen_ip2 = interpolate.interp2d(x3[0,:].real, y3[:,0].real, phasegen, kind='cubic', fill_value=np.nan)\n", + "phase2 = phasegen_ip2(xfp[0,:].real+0.5, yfp[:,0].real)\n", + "\n", + "\n", + "fig, axes = plt.subplots(ncols=3, nrows=1, sharex=True, sharey=True, figsize=(9,4.8))\n", + "axes[0].imshow(phasegen, cmap='jet', extent=[x3.min(), x3.max(), y3.min(), y3.max()], vmin=0, vmax=1)\n", + "axes[1].imshow(phase1, cmap='jet', extent=[xfp.min(), xfp.max(), yfp.min(), yfp.max()], vmin=0, vmax=1)\n", + "axes[2].imshow(phase2, cmap='jet', extent=[xfp.min()+0.5, xfp.max()+0.5, yfp.min(), yfp.max()], vmin=0, vmax=1)\n", + "\n", + "axes[0].set_xlim([-1,1])\n", + "axes[0].set_ylim([-1,1])\n", + "plt.show()\n", + "\n", + "fig = plt.figure()\n", + "ax = fig.add_subplot(111)\n", + "ax.imshow(phase1 - phase2, cmap='jet', extent=[xfp.min(), xfp.max(), yfp.min(), yfp.max()], vmin=0, vmax=1)\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "02fb3ccb", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "_, xfp, yfp = mcd.propagate(l=l,surface=1, zoom=True, show=True, \n", + "\t\t\t\t\t\t\t\tprint_progress=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "8f252e3b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(-155.63238243799717+0j) (155.63238243799717+0j)\n" + ] + } + ], + "source": [ + "print(x3.min(), x3.max())" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "73af1f14", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib notebook" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "844ea83f", + "metadata": {}, + "outputs": [], + "source": [ + "// cash stands for chaos hash :D\n", + "int cash(int x, int y){ \n", + " int h = seed + x*374761393 + y*668265263; //all constants are prime\n", + " h = (h^(h >> 13))*1274126177;\n", + " return h^(h >> 16);\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "40b8f454", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 1 45748 91495 ... 137104480 137150228 137195975]\n", + " [ 81576 127324 173071 ... 137186056 137231803 137277550]\n", + " [ 163152 208899 254646 ... 137267631 137313378 137359126]\n", + " ...\n", + " [244481323 244527071 244572818 ... 381585803 381631550 381677297]\n", + " [244562899 244608646 244654393 ... 381667378 381713125 381758873]\n", + " [244644474 244690221 244735969 ... 381748953 381794701 381840448]]\n" + ] + }, + { + "ename": "ValueError", + "evalue": "Integers to negative integer powers are not allowed.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/yz/y0zt1db902bfbj6p8rfsrkxh0000gn/T/ipykernel_37535/882201994.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmgrid\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m3000\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m3000j\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m3000\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m3000j\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mastype\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 16\u001b[0;31m \u001b[0mqrf\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcash\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 17\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mimshow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mqrf\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0morigin\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'lower'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/var/folders/yz/y0zt1db902bfbj6p8rfsrkxh0000gn/T/ipykernel_37535/882201994.py\u001b[0m in \u001b[0;36mcash\u001b[0;34m(x, y)\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mh\u001b[0m \u001b[0;34m>>\u001b[0m \u001b[0;36m13\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mh\u001b[0m\u001b[0;34m**\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mh\u001b[0m \u001b[0;34m>>\u001b[0m \u001b[0;36m16\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 13\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmgrid\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m3000\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m3000j\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m3000\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;36m3000j\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mastype\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mValueError\u001b[0m: Integers to negative integer powers are not allowed." + ] + } + ], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "def cash(x,y):\n", + " try:\n", + " seed = 12345\n", + " h = seed + x*374761393 + y*668265263\n", + " print((h >> 13))\n", + " h = (h**(h >> 13))*1274126177\n", + " except ValueError:\n", + " print((h >> 13) < 0)\n", + " return h**(h >> 16)\n", + "\n", + "y, x = np.mgrid[0:3000:3000j, 0:3000:3000j].astype(int)\n", + "\n", + "qrf = cash(x,y)\n", + "\n", + "plt.imshow(qrf, origin='lower')\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "id": "21908224", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(array([[-155.63238244, -155.52862752, -155.42487259, ..., 155.42487259,\n", + " 155.52862752, 155.63238244],\n", + " [-155.63238244, -155.52862752, -155.42487259, ..., 155.42487259,\n", + " 155.52862752, 155.63238244],\n", + " [-155.63238244, -155.52862752, -155.42487259, ..., 155.42487259,\n", + " 155.52862752, 155.63238244],\n", + " ...,\n", + " [-155.63238244, -155.52862752, -155.42487259, ..., 155.42487259,\n", + " 155.52862752, 155.63238244],\n", + " [-155.63238244, -155.52862752, -155.42487259, ..., 155.42487259,\n", + " 155.52862752, 155.63238244],\n", + " [-155.63238244, -155.52862752, -155.42487259, ..., 155.42487259,\n", + " 155.52862752, 155.63238244]]), array([[-155.63238244, -155.63238244, -155.63238244, ..., -155.63238244,\n", + " -155.63238244, -155.63238244],\n", + " [-155.52862752, -155.52862752, -155.52862752, ..., -155.52862752,\n", + " -155.52862752, -155.52862752],\n", + " [-155.42487259, -155.42487259, -155.42487259, ..., -155.42487259,\n", + " -155.42487259, -155.42487259],\n", + " ...,\n", + " [ 155.42487259, 155.42487259, 155.42487259, ..., 155.42487259,\n", + " 155.42487259, 155.42487259],\n", + " [ 155.52862752, 155.52862752, 155.52862752, ..., 155.52862752,\n", + " 155.52862752, 155.52862752],\n", + " [ 155.63238244, 155.63238244, 155.63238244, ..., 155.63238244,\n", + " 155.63238244, 155.63238244]]))\n" + ] + } + ], + "source": [ + "xr, yr = x3.real, y3.real\n", + "print((xr,yr))" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5acddec2", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib notebook" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9780553e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "[-2.62118705+0.41501946j -0.70850924-0.41518566j 3.24396455+5.77869097j\n", + " -9.24802109+7.67145042j -5.3838523 -5.17726043j 0.58191832+2.7330733j\n", + " -6.01265766+1.18573479j -2.82441448+0.j -6.01265766-1.18573479j\n", + " 0.58191832-2.7330733j -5.3838523 +5.17726043j]\n", + "\n", + "\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"\\n# coeffs = np.random.uniform(0,1,(101,101))\\n# coeffs = np.zeros((101,101))\\n# coeffs[0,0] = 1\\nx = np.linspace(-np.pi,np.pi,101)\\ny = np.linspace(-np.pi,np.pi,101)\\nxx, yy = np.meshgrid(x, y)\\n\\ndata = fourierSeries(xx,yy,coeffs)\\nprint(data)\\n\\nplt.figure()\\nplt.imshow(np.abs(data), cmap='jet')\\nplt.colorbar()\\nplt.show()\\n\"" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "def fourierSeries(x,y,c):\n", + " f = np.zeros(np.shape(x)).astype('complex128')\n", + " mm,nn = np.meshgrid(range(len(x[0,:])), range(len(y[:,0])))\n", + " print(mm, nn)\n", + " for j, m in enumerate(mm):\n", + " for i, n in enumerate(nn):\n", + " px = 1/(n+1)\n", + " py = 1/(m+1)\n", + " fij = c * np.exp(1j*(2*np.pi/px)*x + (2*np.pi/py)*y)\n", + "# fij = c[i,j] * np.exp(1j*n*x+m*y)\n", + " f += fij\n", + " return f\n", + "\n", + "def fourierSeries1D(x,c, P=50, xs=25):\n", + " '''\n", + " x - x coordinates\n", + " c - Coefficients\n", + " P - Period before repeating the pattern\n", + " xs - shift of the full field x-coordinates. To remove symmetry around 0.\n", + " '''\n", + " xpos = x - xs\n", + " f = np.asarray([c[i]*np.exp(1j*i* 2*np.pi/P * xpos) for i in range(len(c))]).sum(axis=0)\n", + "# f = c*np.exp(1j*i*2*np.pi/P)\n", + " print(type(f[0]))\n", + " return f\n", + "\n", + "# 1D Fourier series test\n", + "np.random.seed(1)\n", + "coeffs = np.random.uniform(-1,1,100)\n", + "# coeffs[0] = 1\n", + "# coeffs[1] = 1\n", + "# coeffs[2] = 1\n", + "\n", + "xx0 = np.linspace(-10,40,11)\n", + "yy0 = fourierSeries1D(xx0, coeffs, P=1000)\n", + "print(yy0)\n", + "xx1 = np.linspace(-50, 50, 201)\n", + "yy1 = fourierSeries1D(xx1,coeffs, P=1000)\n", + "xx2 = np.linspace(-30, 45, 21)\n", + "yy2 = fourierSeries1D(xx2,coeffs, P=1000)\n", + "plt.figure()\n", + "plt.plot(xx0,yy0.real, 'g', marker='x', linestyle='--', zorder=2)\n", + "plt.plot(xx1,yy1.real, 'b-', zorder=2)\n", + "plt.plot(xx2,yy2.real, 'r-', zorder=3, linewidth=1)\n", + "plt.grid(alpha=0.3, zorder=0)\n", + "plt.show()\n", + "\n", + "'''\n", + "# coeffs = np.random.uniform(0,1,(101,101))\n", + "# coeffs = np.zeros((101,101))\n", + "# coeffs[0,0] = 1\n", + "x = np.linspace(-np.pi,np.pi,101)\n", + "y = np.linspace(-np.pi,np.pi,101)\n", + "xx, yy = np.meshgrid(x, y)\n", + "\n", + "data = fourierSeries(xx,yy,coeffs)\n", + "print(data)\n", + "\n", + "plt.figure()\n", + "plt.imshow(np.abs(data), cmap='jet')\n", + "plt.colorbar()\n", + "plt.show()\n", + "'''" + ] + }, + { + "cell_type": "markdown", + "id": "94b0d227", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b7a59755", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Elapsed time: 0.0037 s\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import time\n", + "\n", + "def fourierSeries2D(x, y, c, Px=500, Py=500, xs=250, ys=250):\n", + " '''\n", + " x - x coordinates\n", + " c - Coefficients\n", + " P - Period before repeating the pattern\n", + " xs - shift of the full field x-coordinates. To remove symmetry around 0.\n", + " '''\n", + " xpos = x #- np.sqrt(2)*xs\n", + " ypos = y #- np.sqrt(2)*ys\n", + " fout = []#np.zeros(c.shape).astype('complex128')\n", + " for i in range(c.shape[0]):\n", + " for j in range(c.shape[1]):\n", + " f = c[i,j] * np.exp(1j * i * 2*np.pi*xpos/Px) * np.exp(1j * j * 2*np.pi*ypos/Py)\n", + " fout.append(f)\n", + " fout = np.asarray(fout).sum(axis=0)\n", + " return fout\n", + "\n", + "\n", + "# 1D Fourier series test\n", + "np.random.seed(2)\n", + "coeffs = np.random.uniform(-1,1,(11,11))\n", + "x0 = np.linspace(-10,10,11)\n", + "y0 = np.linspace(-10,10,11)\n", + "xx, yy = np.meshgrid(x0,y0)\n", + "start = time.time()\n", + "zz = fourierSeries2D(xx, yy, coeffs)\n", + "end = time.time()\n", + "print('Elapsed time: {:.4f} s'.format(end - start))\n", + "\n", + "plt.figure()\n", + "plt.imshow(zz.real, extent=[-150,150,-150,150], cmap='jet', origin='lower')\n", + "plt.grid()\n", + "plt.colorbar()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "2d7b525b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Old one: 0.0000 s\n", + "New one: 1.8907 s\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from numba import njit, jit, complex64, float32,float64,complex128\n", + "import numba\n", + "import concurrent \n", + "\n", + "# def fourierSeries2Dopt(x, y, c, Px=500, Py=500, xs=0, ys=0):\n", + "# '''\n", + "# x - x coordinates\n", + "# c - Coefficients\n", + "# P - Period before repeating the pattern\n", + "# xs - shift of the full field x-coordinates. To remove symmetry around 0.\n", + "# '''\n", + "# xpos = x - xs# * np.sqrt(2)\n", + "# ypos = y - ys# * np.sqrt(2)\n", + "# j,i = np.mgrid[0:(c.shape[0]-1):(c.shape[0])*1j,0:(c.shape[1]-1):c.shape[1]*1j]\n", + "# print(i)\n", + "# print(xpos)\n", + "# ixt = np.asarray([i*2*np.pi*xpos/Px for i in range(c.shape[0])])\n", + "# jyt = np.asarray([j*2*np.pi*ypos/Py for j in range(c.shape[1])])\n", + "# f = c * np.exp(1j * (ixt + jyt)) * np.exp(1j * j * 2*np.pi*ypos/Py).sum(axis=0)\n", + "# # f = c * np.exp(1j * (ixt + jyt)).sum(axis=0)\n", + "# return f\n", + "def fourierSeries2Dopt2(x, y, c, Px=500, Py=500, xs=250, ys=250):\n", + " '''\n", + " x - x coordinates\n", + " c - Coefficients\n", + " P - Period before repeating the pattern\n", + " xs - shift of the full field x-coordinates. To remove symmetry around 0.\n", + " '''\n", + " xpos = x + np.sqrt(2)*xs\n", + " ypos = y + np.sqrt(2)*ys\n", + " cs0, cs1 = c.shape\n", + " xt = 2*np.pi*xpos/Px\n", + " yt = 2*np.pi*ypos/Py\n", + " fout = [c[i,j] * np.exp(1j * (i * xt + j * yt)) for i in range(cs0) for j in range(cs1)]\n", + " fout = np.array(fout).sum(axis=0)\n", + " return fout\n", + "\n", + "\n", + "def meshgrid(x, y):\n", + " xx = np.empty(shape=(len(x), len(y)), dtype=np.int64)\n", + " yy = np.empty(shape=(len(x), len(y)), dtype=np.int64)\n", + " for j in y:\n", + " for k in x:\n", + " xx[j,k] = k # change to x[k] if indexing xy\n", + " yy[j,k] = j # change to y[j] if indexing xy\n", + " return xx, yy\n", + "\n", + "\n", + "def fourierSeries2Dopt(x, y, c, Px=500, Py=500, xs=250, ys=250):\n", + " '''\n", + " x - x coordinates\n", + " c - Coefficients\n", + " P - Period before repeating the pattern\n", + " xs - shift of the full field x-coordinates. To remove symmetry around 0.\n", + " '''\n", + " xpos = x + np.sqrt(2)*xs\n", + " ypos = y + np.sqrt(2)*ys\n", + " cs0, cs1 = c.shape\n", + " xt = 2*np.pi*xpos/Px\n", + " yt = 2*np.pi*ypos/Py\n", + " c_ij = c.flatten()\n", + " i,j = meshgrid(range(cs0), range(cs1))\n", + " I = i.flatten()\n", + " J = j.flatten()\n", + " fout = f(c, i, j, xt, yt)\n", + " return fout\n", + "\n", + "@njit\n", + "def f(c, i, j, xt, yt):\n", + " cs0, cs1 = c.shape\n", + " fout = np.zeros(xt.shape, dtype=np.float64)\n", + " for j in range(cs1):\n", + " for i in range(cs0):\n", + " v = c[i,j] * np.exp(1j * (i * xt + j * yt)) \n", + " fout += v.real\n", + " return fout\n", + "\n", + "# def expo_helper(var):\n", + "# i, j, xt, yt, c = var\n", + "# return c[i,j] * np.exp(1j * (i * xt + j * yt)) \n", + " \n", + "\n", + "# def fourierSeries2Dopt3(x, y, c, Px=500, Py=500, xs=250, ys=250):\n", + "# '''\n", + "# x - x coordinates\n", + "# c - Coefficients\n", + "# P - Period before repeating the pattern\n", + "# xs - shift of the full field x-coordinates. To remove symmetry around 0.\n", + "# '''\n", + "# xpos = x + np.sqrt(2)*xs\n", + "# ypos = y + np.sqrt(2)*ys\n", + "# cs0, cs1 = c.shape\n", + "# xt = 2*np.pi*xpos/Px\n", + "# yt = 2*np.pi*ypos/Py\n", + "# c_ij = c.flatten()\n", + "# i,j = meshgrid(range(cs0), range(cs1))\n", + "# I = i.flatten()\n", + "# J = j.flatten()\n", + "# var_list = [(c_ij[k], I[k], J[k], xt, yt) for k in range(len(c_ij))]\n", + "# fout = np.zeros(np.shape(var_list))\n", + "# executor = concurrent.futures.ProcessPoolExecutor(10)\n", + "# futures = [executor.submit(expo_helper, var) for var in var_list]\n", + "# concurrent.futures.wait(futures)\n", + "\n", + "# print(futures[0].result() )\n", + " \n", + "# print(np.array(futures).shape)\n", + "# fout = np.array(futures).sum(axis=0)\n", + "# return fout\n", + "\n", + "\n", + "# 2D Fourier series test\n", + "np.random.seed(2183)\n", + "coeffs = np.random.uniform(-1,1,(101,101))\n", + "# coeffs = np.ones((101,101))\n", + "x0 = np.linspace(-50,50,1001)\n", + "y0 = np.linspace(-50,50,1001)\n", + "xx, yy = np.meshgrid(x0,y0)\n", + "s0 = time.time()\n", + "# zz = fourierSeries2Dopt2(xx, yy, coeffs, )#Px=100, Py=100, xs=0, ys=0)\n", + "s1 = time.time()\n", + "zz = fourierSeries2Dopt(xx, yy, coeffs, )#Px=100, Py=100, xs=0, ys=0)\n", + "s2 = time.time()\n", + "print('Old one: {:.4f} s'.format(s1-s0))\n", + "print('New one: {:.4f} s'.format(s2-s1))\n", + "\n", + "\n", + "# Second set of coordinates\n", + "x1 = np.linspace( -10,10,101)\n", + "y1 = np.linspace(-10,40,101)\n", + "xx1, yy1 = np.meshgrid(x1,y1)\n", + "zz1 = fourierSeries2Dopt(xx1, yy1, coeffs, )#Px=100, Py=100, xs=0, ys=0)\n", + "\n", + "ext = [x0.min(), x0.max(), y0.min(), y0.max()]\n", + "fig, axes = plt.subplots(ncols=3, nrows=1, figsize=(10,4), sharex=True)\n", + "im = axes[0].imshow(zz.real, extent=ext, cmap='jet', origin='lower')\n", + "# fig.colorbar(im, ax=axes[0])\n", + "\n", + "axes[1].plot( xx[int(xx.shape[0]/2),:], zz.real[int(xx.shape[0]/2),:], 'b-', zorder=2)\n", + "axes[1].plot(xx1[int(xx1.shape[0]/2),:], zz1.real[int(xx1.shape[0]/2),:], 'g-', marker='x', zorder=1)\n", + "\n", + "axes[2].plot( yy[:,int(xx.shape[0]/2)], zz.real[:,int(xx.shape[0]/2)], 'r-', zorder=2)\n", + "axes[2].plot(yy1[:,int(zz1.shape[0]/2)], zz1.real[:,int(xx1.shape[0]/2)], 'g-', marker='x', zorder=1)\n", + "\n", + "axes[1].grid(alpha=0.3, zorder=1)\n", + "axes[2].grid(alpha=0.3, zorder=1)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "2ad234e1", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ext = [x0.min(), x0.max(), y0.min(), y0.max()]\n", + "fig, axes = plt.subplots(ncols=3, nrows=1, figsize=(10,4), sharex=True)\n", + "im = axes[0].imshow(zz.real, extent=ext, cmap='jet', origin='lower')\n", + "# fig.colorbar(im, ax=axes[0])\n", + "\n", + "axes[1].plot( xx[int(xx.shape[0]/2),:], zz.real[int(xx.shape[0]/2),:], 'b-', zorder=2)\n", + "axes[1].plot(xx1[int(xx1.shape[0]/2),:], zz1.real[int(xx1.shape[0]/2),:], 'g-', marker='x', zorder=1)\n", + "\n", + "axes[2].plot( yy[:,int(xx.shape[0]/2)], zz.real[:,int(xx.shape[0]/2)], 'r-', zorder=2)\n", + "axes[2].plot(yy1[:,int(zz1.shape[0]/2)], zz1.real[:,int(xx1.shape[0]/2)], 'g-', marker='x', zorder=1)\n", + "\n", + "axes[1].grid(alpha=0.3, zorder=1)\n", + "axes[2].grid(alpha=0.3, zorder=1)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "65096595", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from matplotlib import cm\n", + "fig, axes = plt.subplots(ncols=3, nrows=1, figsize=(10,4), subplot_kw={\"projection\": \"3d\"}, \n", + " sharex=True, sharey=True)\n", + "# fig.colorbar(im, ax=axes[0])\n", + "# surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm, linewidth=0, antialiased=False)\n", + "\n", + "surf1 = axes[0].plot_surface(xx, yy, zz.real, cmap=cm.coolwarm, linewidth=0, antialiased=False)\n", + "surf2 = axes[1].plot_surface(xx1, yy1, zz1.real, cmap=cm.coolwarm, linewidth=0, antialiased=False)\n", + "\n", + "axes[2].plot_wireframe(xx, yy, zz.real, cmap=cm.coolwarm, linewidth=.8, antialiased=False)\n", + "axes[2].plot_surface(xx1, yy1, zz1.real, cmap=cm.coolwarm, linewidth=1, antialiased=False)\n", + "\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "id": "cd1b454d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0 1 2 ... 97 98 99]\n", + " [ 0 1 2 ... 97 98 99]\n", + " [ 0 1 2 ... 97 98 99]\n", + " ...\n", + " [ 0 1 2 ... 97 98 99]\n", + " [ 0 1 2 ... 97 98 99]\n", + " [ 0 1 2 ... 97 98 99]] [[ 0 0 0 ... 0 0 0]\n", + " [ 1 1 1 ... 1 1 1]\n", + " [ 2 2 2 ... 2 2 2]\n", + " ...\n", + " [97 97 97 ... 97 97 97]\n", + " [98 98 98 ... 98 98 98]\n", + " [99 99 99 ... 99 99 99]]\n", + "[[ 0 0 0 ... 0 0 0]\n", + " [ 1 1 1 ... 1 1 1]\n", + " [ 2 2 2 ... 2 2 2]\n", + " ...\n", + " [97 97 97 ... 97 97 97]\n", + " [98 98 98 ... 98 98 98]\n", + " [99 99 99 ... 99 99 99]] [[ 0 1 2 ... 97 98 99]\n", + " [ 0 1 2 ... 97 98 99]\n", + " [ 0 1 2 ... 97 98 99]\n", + " ...\n", + " [ 0 1 2 ... 97 98 99]\n", + " [ 0 1 2 ... 97 98 99]\n", + " [ 0 1 2 ... 97 98 99]]\n" + ] + } + ], + "source": [ + "x, y = np.meshgrid(range(100), range(100))\n", + "print(x,y)\n", + "\n", + "def meshgrid(x, y):\n", + " xx = np.empty(shape=(len(x), len(y)), dtype=int)\n", + " yy = np.empty(shape=(len(x), len(y)), dtype=int)\n", + " for j in y:\n", + " for k in x:\n", + " xx[j,k] = k # change to x[k] if indexing xy\n", + " yy[j,k] = j # change to y[j] if indexing xy\n", + " return yy, xx\n", + "\n", + "x2, y2 = meshgrid(range(100), range(100))\n", + "print(x2, y2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fff92fde", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "hide_input": false, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + }, + "latex_envs": { + "LaTeX_envs_menu_present": true, + "autoclose": false, + "autocomplete": true, + "bibliofile": "biblio.bib", + "cite_by": "apalike", + "current_citInitial": 1, + "eqLabelWithNumbers": true, + "eqNumInitial": 1, + "hotkeys": { + "equation": "Ctrl-E", + "itemize": "Ctrl-I" + }, + "labels_anchors": false, + "latex_user_defs": false, + "report_style_numbering": false, + "user_envs_cfg": false + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/scripts/SPIE/Thesis/geometric_distortions.py b/scripts/SPIE/Thesis/geometric_distortions.py new file mode 100644 index 0000000..cf5fd9e --- /dev/null +++ b/scripts/SPIE/Thesis/geometric_distortions.py @@ -0,0 +1,163 @@ +import numpy as np +import sys +# sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') +# sys.path.insert(0, '/home/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') +sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Documents/Published/FourierOptics GitHub release/') +from fourierOptics import * + +''' + Author: Joost van den Born, PhD student at University of Groningen + Contact: born@astro.rug.nl + Description: Script to generate the geometric distortions due to WFEs + off the ADC prisms. The distortions are produced by measuring + the offset of the PSF centroid with and without WFE as a + function of field position. + Script made for the SPIE manuscript. +''' + +def plot_distortions(): + # Coordinates without WFEs + xe = load_variable('./data/GD_obscured_PSF_xcentroid_expected_zern_psd.pkl') + ye = load_variable('./data/GD_obscured_PSF_ycentroid_expected_zern_psd.pkl') + + # Coordinates with WFEs + xf = load_variable('./data/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd.pkl') + yf = load_variable('./data/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd.pkl') + + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3) + + n = xe.shape[0] + X = xe.flatten() + Y = ye.flatten() + dX = xf - xe + dY = yf - ye + dx = dX.flatten() + dy = dY.flatten() + + # Magnitude of the distortions + R = np.sqrt(dx**2 + dy**2) * mcd.platescale * 1e6 + + # Dummy figure, used for the colorbar colors in the quiver plot + im = plt.imshow(np.reshape(R, (n,n))) + plt.close() + + fig = plt.figure() + ax = fig.add_subplot() + for i in range(len(xe)): + ax.plot(xe[:,i], ye[:,i], 'k-', alpha=0.2, linewidth=.5) + ax.plot(xe[i,:], ye[i,:], 'k-', alpha=0.2, linewidth=.5) + ax.quiver(X,Y,dx,dy, angles='xy', edgecolor='k', facecolor='None', linewidth=.5, units='dots') + ax.quiver(X,Y,dx,dy,R, alpha=.5, units='dots') + ax.set_aspect('equal', adjustable='box') + ax.set_xlabel('x (mm)') + ax.set_ylabel('y (mm)') + fig.colorbar(im, ax=ax, label='PSF centroid shift ($\\mu$as)') + plt.savefig('./figures/UPDATED_GD_full_field_zern_psd.pdf', bbox_inches='tight', transparent=True) + plt.savefig('./figures/UPDATED_GD_full_field_zern_psd.png', bbox_inches='tight', transparent=True) + plt.tight_layout() + plt.show() + + +if __name__ == '__main__': + # plt.rc('text', usetex=True) + # plt.rc('font', family='serif') + + # Setup of simulation + l = 1.49e-3 + Elist, Eplist = [], [] + xlist, ylist = [], [] + xplist, yplist = [], [] + x_expected_list, y_expected_list = [], [] + x_centroid_list, y_centroid_list = [], [] + + # Set field points + hxList, hyList = np.mgrid[-1:1:11j,-1:1:11j]-(1/300) + hxList = hxList.flatten() + hyList = hyList.flatten() + + # Loading in the WFEs + wfe_p1 = load_variable('./data/compressed items/ADC_tWFE_p1_zern_20nm_psd_15nm.pkl') + wfe_p2 = load_variable('./data/compressed items/ADC_tWFE_p2_zern_20nm_psd_15nm.pkl') + wfe_p3 = load_variable('./data/compressed items/ADC_tWFE_p3_zern_20nm_psd_15nm.pkl') + wfe_p4 = load_variable('./data/compressed items/ADC_tWFE_p4_zern_20nm_psd_15nm.pkl') + + # Propagate through the field positions without any WFEs + for i in range(len(hxList)): + hx = hxList[i] + hy = hyList[i] + print('hx: {} - hy: {}'.format(hx,hy) ) + + # Initializing MICADO paraxial model + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, entrance_pupil='obscured') + + # Propagate through the MICADO system + E, x, y = mcd.propagate(l=l,surface=-1, zoom=False, show=False, + print_progress=False, hx=hx, hy=hy) + + # Find approximation for the centroid and amplitude as a first + # guess for the Obscured Airy pattern fit. + amplitude_guess = np.max(np.abs(E))**2 + xbc, ybc, _, _ = mcd.centroid_shift(method='Barycenter') + print('Barycenter guess: ', xbc, ybc) + + # Do the Obscured Airy Pattern Fit for the unperturbed PSF + xe, ye, _, _ = mcd.centroid_shift(method='Obscured Airy', initialGuess=(amplitude_guess, xbc, ybc, 0.0, 0.03)) + print('Airy result: ', xe, ye) + x_expected_list.append(xe) + y_expected_list.append(ye) + + + + # Load in the WFEs and set them to the corresponding ADC surfaces + print('\nSetting the wavefront errors...') + # null_wfe = np.zeros((3001,3001)) + mcd.ELT_ADC.p1.add_wavefront_error(wfe_p1) + mcd.ELT_ADC.p2.add_wavefront_error(wfe_p2) + mcd.ELT_ADC.p3.add_wavefront_error(wfe_p3) + mcd.ELT_ADC.p4.add_wavefront_error(wfe_p4) + + + # Propagate through the MICADO system + print('Propagating with Wavefront Errors applied...') + Ep, xp, yp = mcd.propagate(l=l,surface=-1, zoom=False, show=False, + print_progress=False, hx=hx, hy=hy) + + # Do the Obscured Airy Pattern Fit for the perturbed PSF + xf, yf, _, _ = mcd.c entroid_shift(method='Obscured Airy', initialGuess=(amplitude_guess, xe, ye, 0.0, 0.03)) + + # Resulting centroid shift due to the WFE + x_centroid_list.append(xf) + y_centroid_list.append(yf) + R = np.sqrt( (xf-xe)**2 + (yf-ye)**2 ) * mcd.platescale * 1e6 + print('xf: {}, yf: {}, shift (uas): {}'.format(xf,yf, R)) + + # Clear the memory by emptying the large arrays + mcd = None + wfe = None + E, x, y = None, None, None + Ep, xp, yp = None, None, None + + + # Reshaping the arrays + shape = (int(np.sqrt(len(x_expected_list))), int(np.sqrt(len(x_expected_list)))) + x_expected = np.asarray(x_expected_list).reshape(shape) + y_expected = np.asarray(y_expected_list).reshape(shape)#.T + + # Saving the arrays + save_variable('./data/GD_obscured_PSF_xcentroid_expected_zern_psd.pkl', x_expected) + save_variable('./data/GD_obscured_PSF_ycentroid_expected_zern_psd.pkl', y_expected) + + + # Reshaping the arrays + shape = (int(np.sqrt(len(x_centroid_list))), int(np.sqrt(len(x_centroid_list)))) + x_centroid = np.asarray(x_centroid_list).reshape(shape) + y_centroid = np.asarray(y_centroid_list).reshape(shape) + + # Saving the arrays + save_variable('./data/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd.pkl', x_centroid) + save_variable('./data/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd.pkl', y_centroid) + + plot_distortions() + + + diff --git a/scripts/SPIE/Thesis/geometric_distortions_fp_wfe.py b/scripts/SPIE/Thesis/geometric_distortions_fp_wfe.py new file mode 100644 index 0000000..ca1db18 --- /dev/null +++ b/scripts/SPIE/Thesis/geometric_distortions_fp_wfe.py @@ -0,0 +1,212 @@ +import numpy as np +import sys +# sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') +# sys.path.insert(0, '/home/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') +sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Documents/Published/FourierOptics GitHub release/') +from fourierOptics import * +from pdb import set_trace + +''' + Author: Joost van den Born, PhD student at University of Groningen + Contact: born@astro.rug.nl + Description: Script to generate the geometric distortions due to WFEs + off the ADC prisms. The distortions are produced by measuring + the offset of the PSF centroid with and without WFE as a + function of field position. + Script made for the SPIE manuscript. +''' + +def plot_distortions(): + # Coordinates without WFEs + xe = load_variable('../data/GD_IFP_obscured_PSF_xcentroid_expected_zern.pkl') + ye = load_variable('../data/GD_IFP_obscured_PSF_ycentroid_expected_zern.pkl') + + # Coordinates with WFEs + xf = load_variable('../data/GD_IFP_obscured_PSF_xcentroid_found_with_wfe_airy_zern.pkl') + yf = load_variable('../data/GD_IFP_obscured_PSF_ycentroid_found_with_wfe_airy_zern.pkl') + + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3) + + n = xe.shape[0] + X = xe.flatten() + Y = ye.flatten() + dX = xf - xe + dY = yf - ye + dx = dX.flatten() + dy = dY.flatten() + + # Magnitude of the distortions + R = np.sqrt(dx**2 + dy**2) * mcd.platescale * 1e6 + + # Dummy figure, used for the colorbar colors in the quiver plot + im = plt.imshow(np.reshape(R, (n,n))) + plt.close() + + fig = plt.figure() + ax = fig.add_subplot() + for i in range(len(xe)): + ax.plot(xe[:,i], ye[:,i], 'k-', alpha=0.2, linewidth=.5) + ax.plot(xe[i,:], ye[i,:], 'k-', alpha=0.2, linewidth=.5) + ax.quiver(X,Y,dx,dy, angles='xy', edgecolor='k', facecolor='None', linewidth=.5, units='dots') + ax.quiver(X,Y,dx,dy,R, alpha=.5, units='dots') + ax.set_aspect('equal', adjustable='box') + ax.set_xlabel('x (mm)') + ax.set_ylabel('y (mm)') + fig.colorbar(im, ax=ax, label='PSF centroid shift ($\\mu$as)') + plt.savefig('../figures/UPDATED_GD_focal_plane_zern.pdf', bbox_inches='tight', transparent=True) + plt.savefig('../figures/UPDATED_GD_focal_plane_zern.png', bbox_inches='tight', transparent=True) + plt.tight_layout() + plt.show() + + +if __name__ == '__main__': + # plt.rc('text', usetex=True) + # plt.rc('font', family='serif') + + # Setup of simulation + l = 1.49e-3 + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50) + # mcd.plot_ray_trace() + wfe = WavefrontError(mcd.n, mcd.m) + + # Get x3 and y3 (the coordinates to apply the ADC WFE to): + # The focal plane area is about 200x200 mm, so we apply the wavefront error to an area + # of 200x200 mm. + xfp, yfp = np.meshgrid(np.linspace(-150, 150, mcd.n), np.linspace(-150, 150, mcd.m)) + # _, xfp, yfp = mcd.propagate(l=l,surface=1, zoom=False, show=False, + # print_progress=False) + + # Generate a wavefront error for the intermediate focal plane. + print('\nSetting the wavefront errors...') + wfe_fp = WavefrontError(mcd.n,mcd.m) + wfe_rms = 50e-6 + #PSD Wavefront error + # surface_map_fp = wfe_fp.generate_height_map(wfe_rms, 2, 1) + # plot_real_valued_results(surface_map_fp*1e6, xfp, yfp, logscale=False, fig_size=(8,6.4), grid=False, show=True, + # colormap='jet', labeltext='Surface height (nm)') + # save_variable('../data/MCD_IFP_wfe_object_PSD_50nm.pkl', wfe_fp) + # wfe_fp = load_variable('../data/MCD_IFP_wfe_object_PSD_50nm.pkl') + # surface_map_fp = wfe_fp.data + + # Zernike wavefront error + # We re0se the ADC_surface classes because it can be applicable to any surface, if used properly + ifp_wfe = ADC_surfaces(xfp, yfp, clearAperture=300) + ifp_wfe.generate_ADC_wfe(zernRMS=wfe_rms, psdRMS=0.) + mask = np.sqrt(ifp_wfe.xz**2 + ifp_wfe.yz**2) > 1 + + # Plot the wfe + # surf_ifp = ifp_wfe.latest_surface.copy() + # surf_ifp[mask]=np.nan + + # surface_map_fp = surf_ifp + # wfe_fp.data = surf_ifp + wfe_fp = load_variable('../data/MCD_IFP_wfe_object_zern_50nm.pkl') + surface_map_fp = wfe_fp.data + # save_variable('../data/MCD_IFP_wfe_object_zern_50nm.pkl', wfe_fp) + # figp1, axp1 = plot_real_valued_results(surface_map_fp*1e6, xfp, yfp, + # colormap='jet', labeltext='Surface height (nm)', fig_size=None, show=True) + plt.show() + + + Elist, Eplist = [], [] + xlist, ylist = [], [] + xplist, yplist = [], [] + x_expected_list, y_expected_list = [], [] + x_centroid_list, y_centroid_list = [], [] + + # Set field points + hxList, hyList = np.mgrid[-1:1:11j,-1:1:11j]-(1/300) + # hxList, hyList = np.mgrid[-.1/11:.1/11:11j,-.1/11:.1/11:11j]-(1/300) # Zoomed in to central area + hxList = hxList.flatten() + hyList = hyList.flatten() + + # List of performed simulations + # Defined by PSD + ## GD_IFP : -1:1 + ## GD_IFP_zoom : -1/11 :1/11 + ## GD_IFP_zoom2 : -.5/11:.5/11 + ## GD_IFP_zoom3 : -.1/11:.1/11 + + # Defined by zernikes + ## GD_IFP_obscured_PSF_xcentroid_expected_zern : 50 nm rms 19 zernike modes : -1 : 1 + ## To do; check PSF shape at center field with zern wfe + ## To do; check interpolated WFE at any zoomed in field point. + + # Propagate through the field positions without any WFEs + for i in range(len(hxList)): + hx = hxList[i] + hy = hyList[i] + print('hx: {} - hy: {}'.format(hx,hy) ) + + # Initializing MICADO paraxial model + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, entrance_pupil='obscured') + + # Propagate through the MICADO system + E, x, y = mcd.propagate(l=l,surface=-1, zoom=False, show=False, + print_progress=False, hx=hx, hy=hy) + + # Find approximation for the centroid and amplitude as a first + # guess for the Obscured Airy pattern fit. + amplitude_guess = np.max(np.abs(E))**2 + xbc, ybc, _, _ = mcd.centroid_shift(method='Barycenter') + print('Barycenter guess: ', xbc, ybc) + + # Do the Obscured Airy Pattern Fit for the unperturbed PSF + xe, ye, _, _ = mcd.centroid_shift(method='Obscured Airy', initialGuess=(amplitude_guess, xbc, ybc, 0.0, 0.03)) + print('Airy result: ', xe, ye) + x_expected_list.append(xe) + y_expected_list.append(ye) + + + + # Load in the WFEs and set them to the corresponding ADC surfaces + print('\nSetting the wavefront errors...') + # null_wfe = np.zeros((3001,3001)) + mcd.ELT_FP.add_wavefront_error(surface_map_fp, xfp, yfp, wfe=wfe_fp) + + + # Propagate through the MICADO system + print('Propagating with Wavefront Errors applied...') + Ep, xp, yp = mcd.propagate(l=l,surface=-1, zoom=False, show=False, + print_progress=False, hx=hx, hy=hy) + + + # Do the Obscured Airy Pattern Fit for the perturbed PSF + xf, yf, _, _ = mcd.centroid_shift(method='Obscured Airy', initialGuess=(amplitude_guess, xe, ye, 0.0, 0.03)) + + # Resulting centroid shift due to the WFE + x_centroid_list.append(xf) + y_centroid_list.append(yf) + R = np.sqrt( (xf-xe)**2 + (yf-ye)**2 ) * mcd.platescale * 1e6 + print('xf: {}, yf: {}, shift (uas): {}'.format(xf,yf, R)) + + # Clear the memory by emptying the large arrays + mcd = None + wfe = None + E, x, y = None, None, None + Ep, xp, yp = None, None, None + + + # Reshaping the arrays + shape = (int(np.sqrt(len(x_expected_list))), int(np.sqrt(len(x_expected_list)))) + x_expected = np.asarray(x_expected_list).reshape(shape) + y_expected = np.asarray(y_expected_list).reshape(shape)#.T + + # Saving the arrays + save_variable('../data/GD_IFP_obscured_PSF_xcentroid_expected_zern.pkl', x_expected) + save_variable('../data/GD_IFP_obscured_PSF_ycentroid_expected_zern.pkl', y_expected) + + + # Reshaping the arrays + shape = (int(np.sqrt(len(x_centroid_list))), int(np.sqrt(len(x_centroid_list)))) + x_centroid = np.asarray(x_centroid_list).reshape(shape) + y_centroid = np.asarray(y_centroid_list).reshape(shape) + + # Saving the arrays + save_variable('../data/GD_IFP_obscured_PSF_xcentroid_found_with_wfe_airy_zern.pkl', x_centroid) + save_variable('../data/GD_IFP_obscured_PSF_ycentroid_found_with_wfe_airy_zern.pkl', y_centroid) + + plot_distortions() + + + diff --git a/scripts/SPIE/Thesis/geometric_distortions_incl_rotations.py b/scripts/SPIE/Thesis/geometric_distortions_incl_rotations.py new file mode 100644 index 0000000..8cef8b1 --- /dev/null +++ b/scripts/SPIE/Thesis/geometric_distortions_incl_rotations.py @@ -0,0 +1,174 @@ +import numpy as np +import sys +# sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') +sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Documents/Published/FourierOptics GitHub release/') +from fourierOptics import * + +''' + Author: Joost van den Born, PhD student at University of Groningen + Contact: born@astro.rug.nl + Description: Script to generate the geometric distortions due to WFEs + off the ADC prisms. The distortions are produced by measuring + the offset of the PSF centroid with and without WFE as a + function of field position. + Script made for the SPIE manuscript. +''' + +# Rotation of ADC angles - [0.125, 0.25, 0.5, 0.75, 1, 2, 5, 10, 30, 45, 60, 90, 180] + + +def plot_distortions(): + # Coordinates without WFEs + xe = load_variable('./data/ADC rotation/GD_obscured_PSF_xcentroid_expected_zern_psd.pkl') + ye = load_variable('./data/ADC rotation/GD_obscured_PSF_ycentroid_expected_zern_psd.pkl') + + # Coordinates with WFEs + xf = load_variable('./data/ADC rotation/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd.pkl') + yf = load_variable('./data/ADC rotation/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd.pkl') + + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3) + + n = xe.shape[0] + X = xe.flatten() + Y = ye.flatten() + dX = xf - xe + dY = yf - ye + dx = dX.flatten() + dy = dY.flatten() + + # Magnitude of the distortions + R = np.sqrt(dx**2 + dy**2) * mcd.platescale * 1e6 + + # Dummy figure, used for the colorbar colors in the quiver plot + im = plt.imshow(np.reshape(R, (n,n))) + plt.close() + + fig = plt.figure() + ax = fig.add_subplot() + for i in range(len(xe)): + ax.plot(xe[:,i], ye[:,i], 'k-', alpha=0.2, linewidth=.5) + ax.plot(xe[i,:], ye[i,:], 'k-', alpha=0.2, linewidth=.5) + ax.quiver(X,Y,dx,dy, angles='xy', edgecolor='k', facecolor='None', linewidth=.5, units='dots') + ax.quiver(X,Y,dx,dy,R, alpha=.5, units='dots') + ax.set_aspect('equal', adjustable='box') + ax.set_xlabel('x (mm)') + ax.set_ylabel('y (mm)') + fig.colorbar(im, ax=ax, label='PSF centroid shift ($\\mu$as)') + plt.savefig('./figures/UPDATED2_GD_full_field_zern_psd.pdf', bbox_inches='tight', transparent=True) + plt.savefig('./figures/UPDATED2_GD_full_field_zern_psd.png', bbox_inches='tight', transparent=True) + plt.tight_layout() + plt.show() + + +if __name__ == '__main__': + # plt.rc('text', usetex=True) + # plt.rc('font', family='serif') + + # Loading in the WFEs + wfe_p1 = load_variable('./data/compressed items/ADC_tWFE_p1_zern_20nm_psd_15nm.pkl') + wfe_p2 = load_variable('./data/compressed items/ADC_tWFE_p2_zern_20nm_psd_15nm.pkl') + wfe_p3 = load_variable('./data/compressed items/ADC_tWFE_p3_zern_20nm_psd_15nm.pkl') + wfe_p4 = load_variable('./data/compressed items/ADC_tWFE_p4_zern_20nm_psd_15nm.pkl') + + + + # Rotation of ADC angles - [0.125, 0.25, 0.5, 0.75, 1, 2, 5, 10], 30, 45, 60, 90, 180] + thetaList = [0.001, 0.125, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3, 3.5, 4, 4.5, 5, 6, 7, 8, 9, 10] + # Propagate through the field positions without any WFEs + for theta in thetaList: + + # Setup of simulation + l = 1.49e-3 + Elist, Eplist = [], [] + xlist, ylist = [], [] + xplist, yplist = [], [] + x_expected_list, y_expected_list = [], [] + x_centroid_list, y_centroid_list = [], [] + + # Set field points + hxList, hyList = np.mgrid[-1:1:11j,-1:1:11j]-(1/300) + hxList = hxList.flatten() + hyList = hyList.flatten() + + for i in range(len(hxList)): + hx = hxList[i] + hy = hyList[i] + print('Rotation: {:.3f} - hx: {} - hy: {}'.format(theta,hx,hy) ) + + # Initializing MICADO paraxial model + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, entrance_pupil='obscured') + + # Propagate through the MICADO system + mcd.ELT_ADC.set_orientation(theta) + E, x, y = mcd.propagate(l=l,surface=-1, zoom=False, show=False, + print_progress=False, hx=hx, hy=hy) + + # Find approximation for the centroid and amplitude as a first + # guess for the Obscured Airy pattern fit. + amplitude_guess = np.max(np.abs(E))**2 + xbc, ybc, _, _ = mcd.centroid_shift(method='Barycenter') + print('Barycenter guess: ', xbc, ybc) + + # Do the Obscured Airy Pattern Fit for the unperturbed PSF + xe, ye, _, _ = mcd.centroid_shift(method='Obscured Airy', initialGuess=(amplitude_guess, xbc, ybc, 0.0, 0.03)) + print('Airy result: ', xe, ye) + x_expected_list.append(xe) + y_expected_list.append(ye) + + + + # Load in the WFEs and set them to the corresponding ADC surfaces + print('\nSetting the wavefront errors...') + # null_wfe = np.zeros((3001,3001)) + mcd.ELT_ADC.set_orientation(0) + mcd.ELT_ADC.p1.add_wavefront_error(wfe_p1) + mcd.ELT_ADC.p2.add_wavefront_error(wfe_p2) + mcd.ELT_ADC.p3.add_wavefront_error(wfe_p3) + mcd.ELT_ADC.p4.add_wavefront_error(wfe_p4) + + + # Propagate through the MICADO system + print('Propagating with Wavefront Errors applied...') + mcd.ELT_ADC.set_orientation(theta) + Ep, xp, yp = mcd.propagate(l=l,surface=-1, zoom=False, show=False, + print_progress=False, hx=hx, hy=hy) + + # Do the Obscured Airy Pattern Fit for the perturbed PSF + xf, yf, _, _ = mcd.centroid_shift(method='Obscured Airy', initialGuess=(amplitude_guess, xe, ye, 0.0, 0.03)) + + # Resulting centroid shift due to the WFE + x_centroid_list.append(xf) + y_centroid_list.append(yf) + R = np.sqrt( (xf-xe)**2 + (yf-ye)**2 ) * mcd.platescale * 1e6 + print('xf: {}, yf: {}, shift (uas): {}'.format(xf,yf, R)) + + # Clear the memory by emptying the large arrays + mcd = None + wfe = None + E, x, y = None, None, None + Ep, xp, yp = None, None, None + + + # Reshaping the arrays + shape = (int(np.sqrt(len(x_expected_list))), int(np.sqrt(len(x_expected_list)))) + x_expected = np.asarray(x_expected_list).reshape(shape) + y_expected = np.asarray(y_expected_list).reshape(shape)#.T + + # Saving the arrays + save_variable('./data/ADC rotation/GD_obscured_PSF_xcentroid_expected_zern_psd_rot{}.pkl'.format(theta), x_expected) + save_variable('./data/ADC rotation/GD_obscured_PSF_ycentroid_expected_zern_psd_rot{}.pkl'.format(theta), y_expected) + + + # Reshaping the arrays + shape = (int(np.sqrt(len(x_centroid_list))), int(np.sqrt(len(x_centroid_list)))) + x_centroid = np.asarray(x_centroid_list).reshape(shape) + y_centroid = np.asarray(y_centroid_list).reshape(shape) + + # Saving the arrays + save_variable('./data/ADC rotation/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd_rot{}.pkl'.format(theta), x_centroid) + save_variable('./data/ADC rotation/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd_rot{}.pkl'.format(theta), y_centroid) + + # plot_distortions() + + + diff --git a/scripts/SPIE/Thesis/plotting_detectors.py b/scripts/SPIE/Thesis/plotting_detectors.py new file mode 100644 index 0000000..66176c6 --- /dev/null +++ b/scripts/SPIE/Thesis/plotting_detectors.py @@ -0,0 +1,471 @@ + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.patches as patches +import matplotlib.ticker as plticker +from matplotlib.gridspec import GridSpec +import pandas as pd +import sys +sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Documents/Published/FourierOptics GitHub release/') +from fourierOptics import * +from pdb import set_trace + +''' + Date: 2022-06-20 + Author: Joost van den Born + Description: Plotting the geometric distortions, that were calculated + in an earlier simulation. + Here we also include the detector layout in the plot. +''' + +def add_detector_layout(figureObj, lw=0.8, alpha=0.5, show_label=True): + ''' + Adds the detector layout to an existing figure+axes object. + The detector outlines are based on the MICADO Hawaii 4RG mosaic + and the dimensions are in mm. + + Parameters + ---------- + figureObj : tuple + Tuple containing the figure and axes objects (fig,ax) + lw : float, optional + Linewidth of the drawn detectors. Default: 0.8 + alpha : float, optional + Alpha value to draw the rectangles at + show_label : bool, optional + If True, shows the detector ID number in the rectangle + + Returns + ------- + TYPE + Updated fig, axes objects + ''' + fig, ax = figureObj + + # Detector layout + det_hdr = ['id','x_cen', 'y_cen', 'x_size', 'y_size', 'pixel_size', 'angle', 'gain'] + det_props = np.array([[1, -63.94, 63.94, 4096, 4096, 0.015, 0.0, 1.0], + [2, 0.00, 63.94, 4096, 4096, 0.015, 270.0, 1.0], + [3, 63.94, 63.94, 4096, 4096, 0.015, 180.0, 1.0], + [6, -63.94, 0.00, 4096, 4096, 0.015, 0.0, 1.0], + [5, 0.00, 0.00, 4096, 4096, 0.015, 180.0, 1.0], + [4, 67.94, 0.00, 4096, 4096, 0.015, 180.0, 1.0], + [7, -63.94, -63.94, 4096, 4096, 0.015, 0.0, 1.0], + [8, 0.00, -63.94, 4096, 4096, 0.015, 90.0, 1.0], + [9, 63.94, -63.94, 4096, 4096, 0.015, 180.0, 1.0]]) + detectors = pd.DataFrame(det_props, columns=det_hdr) + detectors["id"] = detectors["id"].astype(int) + detectors["x_size"] = detectors["x_size"].astype(int) + detectors["y_size"] = detectors["y_size"].astype(int) + + for ind, det in detectors.iterrows(): + x_mm = det['x_cen'] - det['x_size']/2*det['pixel_size'] + y_mm = det['y_cen'] - det['y_size']/2*det['pixel_size'] + width = det['x_size']*det['pixel_size'] + height = det['y_size']*det['pixel_size'] + x_cen, y_cen = det['x_cen'], det['y_cen'] + + rect = patches.Rectangle((x_mm, y_mm), width, height, + linewidth=lw, edgecolor='k', facecolor='none', alpha=alpha, zorder=1) + ax.add_patch(rect) + if show_label: + ax.text(x_cen, y_cen, det['id']) + return fig, ax + +def convert_detector_size(x, y, old=True): + old_ps = 100.13151872258219 + new_ps = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3).platescale * 1e3 + conv_fact = old_ps / new_ps + + if old: + x *= conv_fact + y *= conv_fact + elif not old: + x /= conv_fact + y /= conv_fact + + return x, y + + +def plot_distortions(xe, ye, xp, yp, lims=False, add_detectors=True, + save=False, figtitle='geom_distortions', + show=True): + + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3) + n = xe.shape[0] + X = xe.flatten() + Y = ye.flatten() + dX = xp - xe + dY = yp - ye + dx = dX.flatten() + dy = dY.flatten() + + # Magnitude of the distortions + R = np.sqrt(dx**2 + dy**2) * mcd.platescale * 1e6 + print('RMS of the PSF shifts: {:.4f} μas.'.format(np.sqrt(np.mean((R)**2)))) + + # Dummy figure, used for the colorbar colors in the quiver plot + im = plt.imshow(np.reshape(R, (n,n))) + plt.close() + + # Initiate the figure + fig = plt.figure(figsize=(6.4,4.8)) + ax = fig.add_subplot(aspect='equal') + + # Add the detector layout if so desired. + if add_detectors: + add_detector_layout((fig,ax), lw=.8, alpha=0.5, show_label=False) + + # Plot the vector plot + for i in range(len(xe)): + ax.plot(xe[:,i], ye[:,i], 'k-', alpha=0.2, linewidth=.5) + ax.plot(xe[i,:], ye[i,:], 'k-', alpha=0.2, linewidth=.5) + ax.quiver(X,Y,dx,dy, angles='xy', edgecolor='k', + facecolor='None', linewidth=.8, units='dots', zorder=300) + ax.quiver(X,Y,dx,dy,R, alpha=1, units='dots', zorder=300) + ax.set_xlabel('x (mm)', fontsize=16) + ax.set_ylabel('y (mm)', fontsize=16) + ax.tick_params(axis='both', direction='in', labelsize=14, zorder=-1, + top=True, right=True) + cb = fig.colorbar(im, ax=ax) + cb.set_label(label='PSF centroid shift (μas)', size=14) + cb.ax.tick_params(labelsize=14) + if not lims: + ax.set_xlim([-125, 115]) + ax.set_ylim([-115, 115]) + elif lims: + ax.set_xlim(lims[0]) + ax.set_ylim(lims[1]) + plt.tight_layout() + if save: + plt.savefig('{}.pdf'.format(figtitle), + bbox_inches='tight', + transparent=False, + facecolor=(1, 1, 0, 0.0)) + plt.savefig('{}.png'.format(figtitle), + bbox_inches='tight', + transparent=False, + facecolor=(1, 1, 0, 0.0)) + if show: + plt.show() + return fig, ax, R + +def get_differential_distortion_vectors(xe, ye, xp, yp, xe_ref, ye_ref, xp_ref, yp_ref): + # Reference distortions: + # Shape of the arrays + n = xe_ref.shape[0] + + # Reshaping of the arrays so that they can be used for the quiver plot. + X_ref = xe_ref.flatten() + Y_ref = ye_ref.flatten() + dX_ref = xp_ref - xe_ref + dY_ref = yp_ref - ye_ref + + dx_ref = dX_ref.flatten() + dy_ref = dY_ref.flatten() + + # Distortions to compare + # Reshaping of the arrays so that they can be used for the quiver plot. + X = xe.flatten() + Y = ye.flatten() + dX = xp - xe + dY = yp - ye + + dx = dX.flatten() + dy = dY.flatten() + + ddx = dX - dX_ref + ddy = dY - dY_ref + + return ddx, ddy + + +def plot_differential_distortions(xe, ye, xp, yp, xe_ref, ye_ref, xp_ref, yp_ref, + lims=False, add_detectors=True, save=False, + figtitle='diff_geom_distortions', show=True): + # Reference distortions: + # Shape of the arrays + n = xe_ref.shape[0] + + # Reshaping of the arrays so that they can be used for the quiver plot. + X_ref = xe_ref.flatten() + Y_ref = ye_ref.flatten() + dX_ref = xp_ref - xe_ref + dY_ref = yp_ref - ye_ref + + dx_ref = dX_ref.flatten() + dy_ref = dY_ref.flatten() + + # Distortions to compare + # Reshaping of the arrays so that they can be used for the quiver plot. + X = xe.flatten() + Y = ye.flatten() + dX = xp - xe + dY = yp - ye + + dx = dX.flatten() + dy = dY.flatten() + + ddx = dX - dX_ref + ddy = dY - dY_ref + + # Two values are comparatively (extremely large), they are set to 0 + # to be able to see the other values. + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3) + R = (np.sqrt(ddx**2 + ddy**2) * mcd.platescale * 1e6) # Magnitude of the shift in micro arcseconds + print('RMS of the PSF shifts compared to the reference position: {:.4f} μas.'.format(np.sqrt(np.mean((R)**2)))) + + + # Dummy figure + im = plt.imshow(np.reshape(R, (n,n))) + plt.close() + + # Actual plotting of the figure + fig = plt.figure(figsize=(6.4, 4.8)) + ax = fig.add_subplot() + + # Add the detector layout if so desired. + if add_detectors: + add_detector_layout((fig,ax), lw=.8, alpha=0.5, show_label=False) + + # Plot the vector plot + for i in range(len(xe_ref)): + ax.plot(xe_ref[:,i], ye_ref[:,i], 'k-', alpha=0.2, linewidth=.5) + ax.plot(xe_ref[i,:], ye_ref[i,:], 'k-', alpha=0.2, linewidth=.5) + ax.quiver(X,Y,ddx,ddy, angles='xy', edgecolor='k', + facecolor='None', linewidth=.8, units='dots', zorder=300) + ax.quiver(X,Y,ddx,ddy,R, alpha=1, units='dots', zorder=300) + ax.set_xlabel('x (mm)', fontsize=16) + ax.set_ylabel('y (mm)', fontsize=16) + ax.tick_params(axis='both', direction='in', labelsize=14, zorder=-1, + top=True, right=True) + cb = fig.colorbar(im, ax=ax) + cb.set_label(label='PSF centroid shift (μas)', size=14) + cb.ax.tick_params(labelsize=14) + if not lims: + ax.set_xlim([-125, 115]) + ax.set_ylim([-115, 115]) + elif lims: + ax.set_xlim(lims[0]) + ax.set_ylim(lims[1]) + plt.tight_layout() + if save: + plt.savefig('{}.pdf'.format(figtitle), + bbox_inches='tight', + transparent=False, + facecolor=(1, 1, 0, 0.0)) + plt.savefig('{}.png'.format(figtitle), + bbox_inches='tight', + transparent=False, + facecolor=(1, 1, 0, 0.0)) + if show: + plt.show() + return fig, ax, R + +def get_data(): + # Coordinates without WFEs + xe = load_variable('../data/GD_obscured_PSF_xcentroid_expected_zern_psd.pkl') + ye = load_variable('../data/GD_obscured_PSF_ycentroid_expected_zern_psd.pkl') + + # Coordinates with WFEs + xf = load_variable('../data/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd.pkl') + yf = load_variable('../data/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd.pkl') + return xe, ye, xf, yf + + +def normalize_relative_angle(theta, degrees=True): + if type(theta) in [list, np.ndarray]: + values = np.array([normalize_relative_angle(i) for i in theta]) + return values + circle = 360 if degrees else 2*np.pi + theta_mod = theta % circle + # theta_mod_half_circle = theta % (circle/2) + if theta_mod > (circle / 2): + rel_angle = theta_mod - (circle) + else: + rel_angle = theta_mod + return rel_angle + +if __name__ == '__main__': + ps = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3).platescale + print(ps) + + plt.rc('font', family='serif') + plt.rc('axes', labelsize='large') + + # convert_detector_size() + # Plotting the original distortions + xe_og = load_variable('../data/GD full field (Airy small offset)/GD_PSF_xcentroid_expected.pkl') + ye_og = load_variable('../data/GD full field (Airy small offset)/GD_PSF_ycentroid_expected.pkl') + xp_og = load_variable('../data/GD full field (Airy small offset)/GD_PSF_xcentroid_found_with_wfe_airy.pkl') + yp_og = load_variable('../data/GD full field (Airy small offset)/GD_PSF_ycentroid_found_with_wfe_airy.pkl') + xe_og, ye_og = convert_detector_size(xe_og, ye_og) + xp_og, yp_og = convert_detector_size(xp_og, yp_og) + figtitle = '../figures adapted for thesis/GD_full_field_psd' + plot_distortions(xe_og, ye_og, xp_og, yp_og, show=False, save=False, figtitle=figtitle, + lims=[[-120,130],[-120,120]]) + + # Plotting the original distortions + xe_og = load_variable('../data/GD_obscured_PSF_xcentroid_expected_zern_psd.pkl') + ye_og = load_variable('../data/GD_obscured_PSF_ycentroid_expected_zern_psd.pkl') + xp_og = load_variable('../data/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd.pkl') + yp_og = load_variable('../data/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd.pkl') + figtitle = '../figures adapted for thesis/GD_full_field_zern_psd' + plot_distortions(xe_og, ye_og, xp_og, yp_og, show=False, save=False, figtitle=figtitle) + + + # Plotting the distortions when the ADC can rotate. + # First for a very small rotation, this will serve as the reference distortions + # that are calibrated for + xe = load_variable('../data/ADC rotation/GD_obscured_PSF_xcentroid_expected_zern_psd_rot0.001.pkl') + ye = load_variable('../data/ADC rotation/GD_obscured_PSF_ycentroid_expected_zern_psd_rot0.001.pkl') + xp = load_variable('../data/ADC rotation/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd_rot0.001.pkl') + yp = load_variable('../data/ADC rotation/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd_rot0.001.pkl') + figtitle = '../figures adapted for thesis/ADC rotation/GD_full_field_zern_psd_0' + plot_distortions(xe, ye, xp, yp, show=False, save=False, figtitle=figtitle, lims=None) + + # And comparing it to the original (i.e. no rotation) + figtitle = '../figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0' + plot_differential_distortions(xe, ye, xp, yp, xe_og, ye_og, xp_og, yp_og, + show=False, save=False, figtitle=figtitle, lims=None) + + # Next we plot all the simulated rotation values + # rotangles = [0.001, 0.125, 0.25, 0.5, 0.75, 1, 2, 5, 10, 30, 45, 90, 180] + rotangles = [0.001, 0.125, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3, 3.5, 4, 4.5, 5, 6, 7, 8, 9, 10] + rms_of_distortions, max_of_distortions = [], [] + vector_magnitude = np.zeros((121,len(rotangles))) + for i,rx in enumerate(rotangles): + # Full field + xe2 = load_variable('../data/ADC rotation/GD_obscured_PSF_xcentroid_expected_zern_psd_rot{}.pkl'.format(rx)) + ye2 = load_variable('../data/ADC rotation/GD_obscured_PSF_ycentroid_expected_zern_psd_rot{}.pkl'.format(rx)) + xp2 = load_variable('../data/ADC rotation/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd_rot{}.pkl'.format(rx)) + yp2 = load_variable('../data/ADC rotation/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd_rot{}.pkl'.format(rx)) + + figtitle = '../figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_{}'.format(rx) + _,_, R = plot_differential_distortions(xe2, ye2, xp2, yp2, xe, ye, xp, yp, show=False, save=False, + figtitle=figtitle, lims=[[-120,120],[-120,120]]) + + rms_of_distortions.append(np.sqrt(np.mean((R)**2))) + max_of_distortions.append(np.max(R)) + Rflat = R.flatten() + for j in range(len(Rflat)): + vector_magnitude[j,i] = Rflat[j] + + + gs = GridSpec(1, 2, width_ratios=[3,2]) + fig = plt.figure(figsize=(6.4*2,4.8)) + ax = fig.add_subplot(gs[0,0]) + ax2 = fig.add_subplot(gs[0,1], projection='polar') + + relangle = normalize_relative_angle(2*np.asarray(rotangles)) + for i in range(len(Rflat)): + ax.plot(relangle, vector_magnitude[i], 'b-', linewidth=0.5, alpha=0.1) + ax.plot(relangle, rms_of_distortions, 'b-', label='RMS', zorder=30) + ax.plot(relangle, rms_of_distortions, 'bx', zorder=30) + ax.plot(relangle, max_of_distortions, 'r-', label='Maximum', zorder=30) + ax.plot(relangle, max_of_distortions, 'rx', zorder=30) + + ax.axhline(50, color='k', linestyle='-', linewidth=1, label='50 μas', zorder=4) + ax.axhline(10, color='k', linestyle='-.', linewidth=1, label='10 μas', zorder=4) + ax.grid(True, alpha=0.3) + ax.tick_params(axis='both', direction='in', labelsize=14, zorder=-1, + top=True, right=True) + ax.set_xlabel('Relative prism rotation (deg)', fontsize=16) + ax.set_ylabel('Differential distortion (μas)', fontsize=16) + ax.set_xlim([0,20]) + ax.set_ylim([-3,100]) + # plt.tight_layout() + # save = False + # if save: + # figtitle = '../figures adapted for thesis/rotation_relation_relangles' + # plt.savefig('{}.pdf'.format(figtitle), + # bbox_inches='tight', + # transparent=False, + # facecolor=(1, 1, 0, 0.0)) + # plt.savefig('{}.png'.format(figtitle), + # bbox_inches='tight', + # transparent=False, + # facecolor=(1, 1, 0, 0.0)) + + # Trying to plot the change in the distortion vector as a function of ADC rotation + + ax2.grid(True, alpha=0.3) + vxs, vys = [], [] + for i,rx in enumerate(rotangles): + xe2 = load_variable('../data/ADC rotation/GD_obscured_PSF_xcentroid_expected_zern_psd_rot{}.pkl'.format(rx)) + ye2 = load_variable('../data/ADC rotation/GD_obscured_PSF_ycentroid_expected_zern_psd_rot{}.pkl'.format(rx)) + xp2 = load_variable('../data/ADC rotation/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd_rot{}.pkl'.format(rx)) + yp2 = load_variable('../data/ADC rotation/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd_rot{}.pkl'.format(rx)) + + # Find the distortion vector + ddx, ddy = get_differential_distortion_vectors(xe2, ye2, xp2, yp2, xe, ye, xp, yp) + cx = xe2.flatten() + cy = ye2.flatten() + # print(cx[ci], cy[ci]) + vx = ddx.flatten() * ps * 1e6 + vy = ddy.flatten() * ps * 1e6 + + vxs.append([vx]) + vys.append([vy]) + + phi, mag = [],[] + for i in range(121): + # set_trace() + # Array slicing: rotation, 0, grid index + vx_evo = np.asarray(vxs)[:,0,i] + vy_evo = np.asarray(vys)[:,0,i] + + phi_evo = np.arctan2(vy_evo, vx_evo) + mag_evo = np.sqrt(vx_evo**2 + vy_evo**2) + + phi.append(phi_evo[-1]) + mag.append(mag_evo[-1]) + + # Plot the vectors on the central detectors separately + if i in [48, 49, 50, 59, 60, 61, 70, 71, 72]: + if i == 48: + ax.plot(relangle, vector_magnitude[i], 'g-', linewidth=1, alpha=1, zorder=20, label='Central detector') + ax2.plot(phi_evo, mag_evo, 'g-', linewidth=2, zorder=300) + else: + ax.plot(relangle, vector_magnitude[i], 'g-', linewidth=1, alpha=1, zorder=20) + ax2.plot(phi_evo, mag_evo, 'g-', linewidth=2, zorder=300) + else: + ax2.plot(phi_evo, mag_evo, 'b-', linewidth=1, zorder=1) + + # Making a legend on the first sub figure in the right order + handles, labels = ax.get_legend_handles_labels() + order = [0,1,4,2,3] + + ax.legend([handles[idx] for idx in order],[labels[idx] for idx in order], fontsize=14) + + # Slicing the polar plot to the relevant part. + phi = np.asarray(phi) + ax2.set_thetamin(np.degrees(np.min(phi%(2*np.pi)))-10) + ax2.set_thetamax(np.degrees(np.max(phi%(2*np.pi)))+10) + + label_position=ax2.get_rlabel_position() + ax2.text(np.radians(label_position-10),ax2.get_rmax()/2.,'Differential\ndistortion (μas)', + rotation=np.degrees(np.min(phi%(2*np.pi)))-10,ha='center',va='center', fontsize=14) + + loc = plticker.MultipleLocator(base=np.radians(10)) # this locator puts ticks at regular intervals + ax2.xaxis.set_major_locator(loc) + ticks = [40, '', 60,'', 80, '', 100, '', 120, '', 140, '', 160, '', 180, '', 200, '', 220] + ax2.xaxis.set_ticklabels(ticks) + ax2.tick_params(axis='both', direction='in', labelsize=12, zorder=-1, + top=True, right=False, left=True) + plt.tight_layout() + save = True + if save: + figtitle = '../figures adapted for thesis/rotation_relation_with_polar_plot' + plt.savefig('{}.pdf'.format(figtitle), + bbox_inches='tight', + transparent=False, + facecolor=(1, 1, 0, 0.0)) + plt.savefig('{}.png'.format(figtitle), + bbox_inches='tight', + transparent=False, + facecolor=(1, 1, 0, 0.0)) + plt.show() + + + diff --git a/scripts/SPIE/Thesis/plotting_geometric_distortions_IFP.py b/scripts/SPIE/Thesis/plotting_geometric_distortions_IFP.py new file mode 100644 index 0000000..40ee220 --- /dev/null +++ b/scripts/SPIE/Thesis/plotting_geometric_distortions_IFP.py @@ -0,0 +1,280 @@ + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.patches as patches +import matplotlib.ticker as plticker +from matplotlib.gridspec import GridSpec +import pandas as pd +import sys +sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Documents/Published/FourierOptics GitHub release/') +from fourierOptics import * +from pdb import set_trace +from matplotlib.colors import Normalize, LogNorm + +''' + Date: 2022-06-20 + Author: Joost van den Born + Description: Plotting the geometric distortions, that were calculated + in an earlier simulation. + Here we also include the detector layout in the plot. +''' + +def add_detector_layout(figureObj, lw=0.8, alpha=0.5, show_label=True): + ''' + Adds the detector layout to an existing figure+axes object. + The detector outlines are based on the MICADO Hawaii 4RG mosaic + and the dimensions are in mm. + + Parameters + ---------- + figureObj : tuple + Tuple containing the figure and axes objects (fig,ax) + lw : float, optional + Linewidth of the drawn detectors. Default: 0.8 + alpha : float, optional + Alpha value to draw the rectangles at + show_label : bool, optional + If True, shows the detector ID number in the rectangle + + Returns + ------- + TYPE + Updated fig, axes objects + ''' + fig, ax = figureObj + + # Detector layout + det_hdr = ['id','x_cen', 'y_cen', 'x_size', 'y_size', 'pixel_size', 'angle', 'gain'] + det_props = np.array([[1, -63.94, 63.94, 4096, 4096, 0.015, 0.0, 1.0], + [2, 0.00, 63.94, 4096, 4096, 0.015, 270.0, 1.0], + [3, 63.94, 63.94, 4096, 4096, 0.015, 180.0, 1.0], + [6, -63.94, 0.00, 4096, 4096, 0.015, 0.0, 1.0], + [5, 0.00, 0.00, 4096, 4096, 0.015, 180.0, 1.0], + [4, 67.94, 0.00, 4096, 4096, 0.015, 180.0, 1.0], + [7, -63.94, -63.94, 4096, 4096, 0.015, 0.0, 1.0], + [8, 0.00, -63.94, 4096, 4096, 0.015, 90.0, 1.0], + [9, 63.94, -63.94, 4096, 4096, 0.015, 180.0, 1.0]]) + detectors = pd.DataFrame(det_props, columns=det_hdr) + detectors["id"] = detectors["id"].astype(int) + detectors["x_size"] = detectors["x_size"].astype(int) + detectors["y_size"] = detectors["y_size"].astype(int) + + for ind, det in detectors.iterrows(): + x_mm = det['x_cen'] - det['x_size']/2*det['pixel_size'] + y_mm = det['y_cen'] - det['y_size']/2*det['pixel_size'] + width = det['x_size']*det['pixel_size'] + height = det['y_size']*det['pixel_size'] + x_cen, y_cen = det['x_cen'], det['y_cen'] + + rect = patches.Rectangle((x_mm, y_mm), width, height, + linewidth=lw, edgecolor='k', facecolor='none', alpha=alpha, zorder=1) + ax.add_patch(rect) + if show_label: + ax.text(x_cen, y_cen, det['id']) + return fig, ax + +def convert_detector_size(x, y, old=True): + old_ps = 100.13151872258219 + new_ps = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3).platescale * 1e3 + conv_fact = old_ps / new_ps + + if old: + x *= conv_fact + y *= conv_fact + elif not old: + x /= conv_fact + y /= conv_fact + + return x, y + + +def plot_distortions(xe, ye, xp, yp, lims=False, add_detectors=True, + save=False, figtitle='geom_distortions', + show=True, cmap='magma'): + vmin, vmax = 0.1, 100 + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3) + n = xe.shape[0] + X = xe.flatten() + Y = ye.flatten() + dX = xp - xe + dY = yp - ye + dx = dX.flatten() + dy = dY.flatten() + + # Magnitude of the distortions + R = np.sqrt(dx**2 + dy**2) * mcd.platescale * 1e6 + print('RMS of the PSF shifts: {:.4f} μas.'.format(np.sqrt(np.mean((R)**2)))) + + # Dummy figure, used for the colorbar colors in the quiver plot + # im = plt.imshow(np.reshape(R, (n,n))) + im = plt.imshow(np.reshape(R[:121], (11,11))) + im = plt.scatter(X, Y, s=R, c=np.log10(R), cmap=cmap, vmin=np.log10(vmin), vmax=np.log10(vmax)) + # im = plt.scatter(X, Y, s=R, c=R, cmap='jet') + plt.close() + + # Initiate the figure + # fig = plt.figure(figsize=(9.6,4.8)) + # ax = fig.add_subplot(121, aspect='equal') + # ax2 = fig.add_subplot(122, aspect='equal') + fig, axes = plt.subplots(ncols=2, + nrows=1, + gridspec_kw={'width_ratios': [1,1]}, + figsize=(9.6,4.8)) + ax, ax2 = axes + ax.set_aspect('equal') + ax2.set_aspect('equal') + # Add the detector layout if so desired. + if add_detectors: + add_detector_layout((fig,ax), lw=.8, alpha=1, show_label=False) + + # Plot the vector plot + xe = np.asarray(xe[:121]).reshape((11,11)) + ye = np.asarray(ye[:121]).reshape((11,11)) + # for i in range(len(xe)): + # ax.plot(xe[:,i], ye[:,i], 'k-', alpha=0.2, linewidth=.5) + # ax.plot(xe[i,:], ye[i,:], 'k-', alpha=0.2, linewidth=.5) + # ax.quiver(X,Y,dx,dy,R, alpha=1, units='dots', zorder=300) + + for k in range(4): + print() + ind_end = (k+1) * 121 + ind_start = k * 121 + dpix = np.diff(Y)[ind_start:ind_end][1] + print(k, ind_start, ind_end, dpix) + + xplot = np.asarray(X[ind_start:ind_end]).reshape((11,11)) + yplot = np.asarray(Y[ind_start:ind_end]).reshape((11,11)) + yplot, xplot = np.mgrid[yplot.min()+dpix/2:yplot.max()-dpix/2:12j, + xplot.min()+dpix/2:xplot.max()-dpix/2:12j] + + + Rtemp = np.asarray(R[ind_start:ind_end]).reshape((11,11)) + ax.pcolor(-1*yplot.T, -1*xplot.T, Rtemp.T, cmap=cmap, norm=LogNorm(vmin=vmin, vmax=vmax)) + # ax2.pcolor(-1*xplot.T, -1*yplot.T, Rtemp.T, cmap=cmap, norm=LogNorm(vmin=1, vmax=100)) + shiftx = X[ind_start:ind_end][61]#np.abs(dpix)/2 + shifty = Y[ind_start:ind_end][60] + print(Y[ind_start:ind_end]) + print('shiftx:', shiftx) + print('shifty:', shifty) + + # ax2.pcolor(k*shifty-1*yplot.T, k*shiftx-1*xplot.T, Rtemp.T*10, cmap=cmap, norm=LogNorm(vmin=1, vmax=100)) + if k == 1: ax2.pcolor(shifty-1*yplot.T, shiftx-1*xplot.T, Rtemp.T, cmap=cmap, norm=LogNorm(vmin=vmin, vmax=vmax)) + elif k == 2: ax2.pcolor(2*shifty-1*yplot.T, 2*shiftx-1*xplot.T, Rtemp.T, cmap=cmap, norm=LogNorm(vmin=vmin, vmax=vmax)) + elif k == 3: ax2.pcolor(2*shifty-1*yplot.T, 2*shiftx-1*xplot.T, Rtemp.T, cmap=cmap, norm=LogNorm(vmin=vmin, vmax=vmax)) + if k == 0: + ax.quiver(X[ind_start:ind_end],Y[ind_start:ind_end], + dx[ind_start:ind_end],dy[ind_start:ind_end], + angles='xy', edgecolor='k', facecolor='k', + linewidth=.8, zorder=300, alpha=1, scale=1.4e-5, + units='dots') + elif k in [2,3]: + ax2.quiver(X[ind_start:ind_end],Y[ind_start:ind_end], + dx[ind_start:ind_end],dy[ind_start:ind_end], + angles='xy', edgecolor='k', facecolor='k', + linewidth=.8, zorder=300, alpha=1, scale=0.2e-5, + units='dots') + # ax.pcolor(xplot * (1 - dpix/105), yplot * (1 - dpix/105), Rtemp, cmap='jet', norm=LogNorm(vmin=1, vmax=100)) + + ax.set_xlabel('x (mm)', fontsize=16) + ax.set_ylabel('y (mm)', fontsize=16) + ax.tick_params(axis='both', direction='in', labelsize=14, zorder=-1, + top=True, right=True) + ax2.set_xlabel('x (mm)', fontsize=16) + ax2.set_ylabel('y (mm)', fontsize=16) + ax2.tick_params(axis='both', direction='in', labelsize=14, zorder=-1, + top=True, right=True) + + + cb = fig.colorbar(im, ax=axes, fraction=0.025, pad=0.04) + cb.set_label(label='PSF centroid shift (μas)', size=14) + cb.ax.tick_params(labelsize=14) + + lbls = 10**np.asarray(cb.ax.get_yticks()) + print(lbls) + newlbls = ['{:.0f}'.format(x) for x in 10**np.asarray(cb.ax.get_yticks())] + newlbls[1] = '{:.1f}'.format(lbls[1]) + newlbls[0] = '{:.1f}'.format(lbls[0]) + cb.ax.set_yticklabels(newlbls) + print(cb.ax.get_yticks()) + if not lims: + ax.set_xlim([-125, 115]) + ax.set_ylim([-115, 115]) + elif lims: + ax.set_xlim(lims[0]) + ax.set_ylim(lims[1]) + ax2.set_xlim([-2.01,2.75]) + ax2.set_ylim([-2.01,2.75]) + ax.indicate_inset_zoom(ax2, edgecolor="black") + fig.subplots_adjust(top=1.0, + bottom=0.0, + left=0.1, + right=0.8, + hspace=0.135, + wspace=0.3) + # plt.tight_layout() + if save: + plt.savefig('{}.pdf'.format(figtitle), + bbox_inches='tight', + transparent=False, + facecolor=(1, 1, 0, 0.0)) + plt.savefig('{}.png'.format(figtitle), + bbox_inches='tight', + transparent=False, + facecolor=(1, 1, 0, 0.0)) + if show: + plt.show() + return fig, ax, R + + +if __name__ == '__main__': + ps = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3).platescale + print(ps) + + plt.rc('font', family='serif') + plt.rc('axes', labelsize='large') + + # convert_detector_size() + # Plotting the original distortions + data_dir = '../data/GD Focal Plane WFEs/' + xe_og = load_variable(data_dir + 'GD_IFP_obscured_PSF_xcentroid_expected_zern_psd.pkl') + ye_og = load_variable(data_dir + 'GD_IFP_obscured_PSF_ycentroid_expected_zern_psd.pkl') + xp_og = load_variable(data_dir + 'GD_IFP_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd.pkl') + yp_og = load_variable(data_dir + 'GD_IFP_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd.pkl') + + # Zoom 1: + xe_og1 = load_variable(data_dir + 'GD_IFP_zoom_obscured_PSF_xcentroid_expected_zern_psd.pkl') + ye_og1 = load_variable(data_dir + 'GD_IFP_zoom_obscured_PSF_ycentroid_expected_zern_psd.pkl') + xp_og1 = load_variable(data_dir + 'GD_IFP_zoom_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd.pkl') + yp_og1 = load_variable(data_dir + 'GD_IFP_zoom_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd.pkl') + + # Zoom 2 + xe_og2 = load_variable(data_dir + 'GD_IFP_zoom2_obscured_PSF_xcentroid_expected_zern_psd.pkl') + ye_og2 = load_variable(data_dir + 'GD_IFP_zoom2_obscured_PSF_ycentroid_expected_zern_psd.pkl') + xp_og2 = load_variable(data_dir + 'GD_IFP_zoom2_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd.pkl') + yp_og2 = load_variable(data_dir + 'GD_IFP_zoom2_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd.pkl') + + # Zoom 3 + xe_og3 = load_variable(data_dir + 'GD_IFP_zoom3_obscured_PSF_xcentroid_expected_zern_psd.pkl') + ye_og3 = load_variable(data_dir + 'GD_IFP_zoom3_obscured_PSF_ycentroid_expected_zern_psd.pkl') + xp_og3 = load_variable(data_dir + 'GD_IFP_zoom3_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd.pkl') + yp_og3 = load_variable(data_dir + 'GD_IFP_zoom3_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd.pkl') + + xe = np.append(xe_og, [xe_og1, xe_og2, xe_og3]) + ye = np.append(ye_og, [ye_og1, ye_og2, ye_og3]) + xp = np.append(xp_og, [xp_og1, xp_og2, xp_og3]) + yp = np.append(yp_og, [yp_og1, yp_og2, yp_og3]) + + # xe = np.append(xe_og2, xe_og3) + # ye = np.append(ye_og2, ye_og3) + # xp = np.append(xp_og2, xp_og3) + # yp = np.append(yp_og2, yp_og3) + + + figtitle = '../figures adapted for thesis/Focal Plane/GD_IFP_zern' + plot_distortions(xe, ye, xp, yp, show=True, save=True, figtitle=figtitle, + lims=[[-120,120],[-120,120]]) + + + + + diff --git a/scripts/SPIE/Thesis/plotting_geometric_distortions_IFP_zern.py b/scripts/SPIE/Thesis/plotting_geometric_distortions_IFP_zern.py new file mode 100644 index 0000000..a068cbd --- /dev/null +++ b/scripts/SPIE/Thesis/plotting_geometric_distortions_IFP_zern.py @@ -0,0 +1,332 @@ + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.patches as patches +import matplotlib.ticker as plticker +from matplotlib.gridspec import GridSpec +import pandas as pd +import sys +sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Documents/Published/FourierOptics GitHub release/') +from fourierOptics import * +from pdb import set_trace +from matplotlib.colors import Normalize, LogNorm + +''' + Date: 2022-06-20 + Author: Joost van den Born + Description: Plotting the geometric distortions, that were calculated + in an earlier simulation. + Here we also include the detector layout in the plot. +''' + +def add_detector_layout(figureObj, lw=0.8, alpha=0.5, show_label=True): + ''' + Adds the detector layout to an existing figure+axes object. + The detector outlines are based on the MICADO Hawaii 4RG mosaic + and the dimensions are in mm. + + Parameters + ---------- + figureObj : tuple + Tuple containing the figure and axes objects (fig,ax) + lw : float, optional + Linewidth of the drawn detectors. Default: 0.8 + alpha : float, optional + Alpha value to draw the rectangles at + show_label : bool, optional + If True, shows the detector ID number in the rectangle + + Returns + ------- + TYPE + Updated fig, axes objects + ''' + fig, ax = figureObj + + # Detector layout + det_hdr = ['id','x_cen', 'y_cen', 'x_size', 'y_size', 'pixel_size', 'angle', 'gain'] + det_props = np.array([[1, -63.94, 63.94, 4096, 4096, 0.015, 0.0, 1.0], + [2, 0.00, 63.94, 4096, 4096, 0.015, 270.0, 1.0], + [3, 63.94, 63.94, 4096, 4096, 0.015, 180.0, 1.0], + [6, -63.94, 0.00, 4096, 4096, 0.015, 0.0, 1.0], + [5, 0.00, 0.00, 4096, 4096, 0.015, 180.0, 1.0], + [4, 67.94, 0.00, 4096, 4096, 0.015, 180.0, 1.0], + [7, -63.94, -63.94, 4096, 4096, 0.015, 0.0, 1.0], + [8, 0.00, -63.94, 4096, 4096, 0.015, 90.0, 1.0], + [9, 63.94, -63.94, 4096, 4096, 0.015, 180.0, 1.0]]) + detectors = pd.DataFrame(det_props, columns=det_hdr) + detectors["id"] = detectors["id"].astype(int) + detectors["x_size"] = detectors["x_size"].astype(int) + detectors["y_size"] = detectors["y_size"].astype(int) + + for ind, det in detectors.iterrows(): + x_mm = det['x_cen'] - det['x_size']/2*det['pixel_size'] + y_mm = det['y_cen'] - det['y_size']/2*det['pixel_size'] + width = det['x_size']*det['pixel_size'] + height = det['y_size']*det['pixel_size'] + x_cen, y_cen = det['x_cen'], det['y_cen'] + + rect = patches.Rectangle((x_mm, y_mm), width, height, + linewidth=lw, edgecolor='k', facecolor='none', alpha=alpha, zorder=1) + ax.add_patch(rect) + if show_label: + ax.text(x_cen, y_cen, det['id']) + return fig, ax + +def convert_detector_size(x, y, old=True): + old_ps = 100.13151872258219 + new_ps = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3).platescale * 1e3 + conv_fact = old_ps / new_ps + + if old: + x *= conv_fact + y *= conv_fact + elif not old: + x /= conv_fact + y /= conv_fact + + return x, y + + +def plot_distortions(xe, ye, xp, yp, lims=False, add_detectors=True, + save=False, figtitle='geom_distortions', + show=True): + + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3) + n = xe.shape[0] + X = xe.flatten() + Y = ye.flatten() + dX = xp - xe + dY = yp - ye + dx = dX.flatten() + dy = dY.flatten() + + # Magnitude of the distortions + R = np.sqrt(dx**2 + dy**2) * mcd.platescale * 1e6 + print('RMS of the PSF shifts: {:.4f} μas.'.format(np.sqrt(np.mean((R)**2)))) + + # Dummy figure, used for the colorbar colors in the quiver plot + im = plt.imshow(np.reshape(R, (n,n))) + plt.close() + + # Initiate the figure + fig = plt.figure(figsize=(6.4,4.8)) + ax = fig.add_subplot(aspect='equal') + + # Add the detector layout if so desired. + if add_detectors: + add_detector_layout((fig,ax), lw=.8, alpha=0.5, show_label=False) + + # Plot the vector plot + for i in range(len(xe)): + ax.plot(xe[:,i], ye[:,i], 'k-', alpha=0.2, linewidth=.5) + ax.plot(xe[i,:], ye[i,:], 'k-', alpha=0.2, linewidth=.5) + ax.quiver(X,Y,dx,dy, angles='xy', edgecolor='k', + facecolor='None', linewidth=.8, units='dots', zorder=300) + ax.quiver(X,Y,dx,dy,R, alpha=1, units='dots', zorder=300) + ax.set_xlabel('x (mm)', fontsize=16) + ax.set_ylabel('y (mm)', fontsize=16) + ax.tick_params(axis='both', direction='in', labelsize=14, zorder=-1, + top=True, right=True) + cb = fig.colorbar(im, ax=ax) + cb.set_label(label='PSF centroid shift (μas)', size=14) + cb.ax.tick_params(labelsize=14) + if not lims: + ax.set_xlim([-125, 115]) + ax.set_ylim([-115, 115]) + elif lims: + ax.set_xlim(lims[0]) + ax.set_ylim(lims[1]) + plt.tight_layout() + if save: + plt.savefig('{}.pdf'.format(figtitle), + bbox_inches='tight', + transparent=False, + facecolor=(1, 1, 0, 0.0)) + plt.savefig('{}.png'.format(figtitle), + bbox_inches='tight', + transparent=False, + facecolor=(1, 1, 0, 0.0)) + if show: + plt.show() + return fig, ax, R + + +def plot_distortions2(xe, ye, xp, yp, lims=False, add_detectors=True, + save=False, figtitle='geom_distortions', + show=True, cmap='magma'): + vmin, vmax = 0.1, 100 + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3) + n = xe.shape[0] + X = xe.flatten() + Y = ye.flatten() + dX = xp - xe + dY = yp - ye + dx = dX.flatten() + dy = dY.flatten() + + # Magnitude of the distortions + R = np.sqrt(dx**2 + dy**2) * mcd.platescale * 1e6 + print('RMS of the PSF shifts: {:.4f} μas.'.format(np.sqrt(np.mean((R)**2)))) + + # Dummy figure, used for the colorbar colors in the quiver plot + # im = plt.imshow(np.reshape(R, (n,n))) + im = plt.imshow(np.reshape(R[:121], (11,11))) + im = plt.scatter(X, Y, s=R, c=np.log10(R), cmap=cmap, vmin=np.log10(vmin), vmax=np.log10(vmax)) + # im = plt.scatter(X, Y, s=R, c=R, cmap='jet') + plt.close() + + # Initiate the figure + # fig = plt.figure(figsize=(9.6,4.8)) + # ax = fig.add_subplot(121, aspect='equal') + # ax2 = fig.add_subplot(122, aspect='equal') + fig, axes = plt.subplots(ncols=2, + nrows=1, + gridspec_kw={'width_ratios': [1,1]}, + figsize=(9.6,4.8)) + ax, ax2 = axes + ax.set_aspect('equal') + ax2.set_aspect('equal') + # Add the detector layout if so desired. + if add_detectors: + add_detector_layout((fig,ax), lw=.8, alpha=1, show_label=False) + + # Plot the vector plot + xe = np.asarray(xe[:121]).reshape((11,11)) + ye = np.asarray(ye[:121]).reshape((11,11)) + # for i in range(len(xe)): + # ax.plot(xe[:,i], ye[:,i], 'k-', alpha=0.2, linewidth=.5) + # ax.plot(xe[i,:], ye[i,:], 'k-', alpha=0.2, linewidth=.5) + # ax.quiver(X,Y,dx,dy,R, alpha=1, units='dots', zorder=300) + + for k in range(1): + print() + ind_end = (k+1) * 121 + ind_start = k * 121 + dpix = np.diff(Y)[ind_start:ind_end][1] + print(k, ind_start, ind_end, dpix) + + xplot = np.asarray(X[ind_start:ind_end]).reshape((11,11)) + yplot = np.asarray(Y[ind_start:ind_end]).reshape((11,11)) + yplot, xplot = np.mgrid[yplot.min()+dpix/2:yplot.max()-dpix/2:12j, + xplot.min()+dpix/2:xplot.max()-dpix/2:12j] + + + Rtemp = np.asarray(R[ind_start:ind_end]).reshape((11,11)) + ax.pcolor(-1*yplot.T, -1*xplot.T, Rtemp.T, cmap=cmap, norm=LogNorm(vmin=vmin, vmax=vmax)) + # ax2.pcolor(-1*xplot.T, -1*yplot.T, Rtemp.T, cmap=cmap, norm=LogNorm(vmin=1, vmax=100)) + shiftx = X[ind_start:ind_end][61]#np.abs(dpix)/2 + shifty = Y[ind_start:ind_end][60] + print(Y[ind_start:ind_end]) + print('shiftx:', shiftx) + print('shifty:', shifty) + + # ax2.pcolor(k*shifty-1*yplot.T, k*shiftx-1*xplot.T, Rtemp.T*10, cmap=cmap, norm=LogNorm(vmin=1, vmax=100)) + if k == 1: ax2.pcolor(shifty-1*yplot.T, shiftx-1*xplot.T, Rtemp.T, cmap=cmap, norm=LogNorm(vmin=vmin, vmax=vmax)) + elif k == 2: ax2.pcolor(2*shifty-1*yplot.T, 2*shiftx-1*xplot.T, Rtemp.T, cmap=cmap, norm=LogNorm(vmin=vmin, vmax=vmax)) + elif k == 3: ax2.pcolor(2*shifty-1*yplot.T, 2*shiftx-1*xplot.T, Rtemp.T, cmap=cmap, norm=LogNorm(vmin=vmin, vmax=vmax)) + if k == 0: + ax.quiver(X[ind_start:ind_end],Y[ind_start:ind_end], + dx[ind_start:ind_end],dy[ind_start:ind_end], + angles='xy', edgecolor='k', facecolor='k', + linewidth=.8, zorder=300, alpha=1, scale=1.4e-5, + units='dots') + elif k in [2,3]: + ax2.quiver(X[ind_start:ind_end],Y[ind_start:ind_end], + dx[ind_start:ind_end],dy[ind_start:ind_end], + angles='xy', edgecolor='k', facecolor='k', + linewidth=.8, zorder=300, alpha=1, scale=0.2e-5, + units='dots') + # ax.pcolor(xplot * (1 - dpix/105), yplot * (1 - dpix/105), Rtemp, cmap='jet', norm=LogNorm(vmin=1, vmax=100)) + + ax.set_xlabel('x (mm)', fontsize=16) + ax.set_ylabel('y (mm)', fontsize=16) + ax.tick_params(axis='both', direction='in', labelsize=14, zorder=-1, + top=True, right=True) + ax2.set_xlabel('x (mm)', fontsize=16) + ax2.set_ylabel('y (mm)', fontsize=16) + ax2.tick_params(axis='both', direction='in', labelsize=14, zorder=-1, + top=True, right=True) + + + cb = fig.colorbar(im, ax=axes, fraction=0.025, pad=0.04) + cb.set_label(label='PSF centroid shift (μas)', size=14) + cb.ax.tick_params(labelsize=14) + + lbls = 10**np.asarray(cb.ax.get_yticks()) + print(lbls) + newlbls = ['{:.0f}'.format(x) for x in 10**np.asarray(cb.ax.get_yticks())] + newlbls[1] = '{:.1f}'.format(lbls[1]) + newlbls[0] = '{:.1f}'.format(lbls[0]) + cb.ax.set_yticklabels(newlbls) + print(cb.ax.get_yticks()) + if not lims: + ax.set_xlim([-125, 115]) + ax.set_ylim([-115, 115]) + elif lims: + ax.set_xlim(lims[0]) + ax.set_ylim(lims[1]) + ax2.set_xlim([-2.01,2.75]) + ax2.set_ylim([-2.01,2.75]) + ax.indicate_inset_zoom(ax2, edgecolor="black") + fig.subplots_adjust(top=1.0, + bottom=0.0, + left=0.1, + right=0.8, + hspace=0.135, + wspace=0.3) + # plt.tight_layout() + if save: + plt.savefig('{}.pdf'.format(figtitle), + bbox_inches='tight', + transparent=False, + facecolor=(1, 1, 0, 0.0)) + plt.savefig('{}.png'.format(figtitle), + bbox_inches='tight', + transparent=False, + facecolor=(1, 1, 0, 0.0)) + if show: + plt.show() + return fig, ax, R + + +if __name__ == '__main__': + ps = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3).platescale + print(ps) + + plt.rc('font', family='serif') + plt.rc('axes', labelsize='large') + + # convert_detector_size() + # Plotting the original distortions + data_dir = '../data/GD Focal Plane WFEs/' + xe_og = load_variable(data_dir + 'GD_IFP_obscured_PSF_xcentroid_expected_zern.pkl') + ye_og = load_variable(data_dir + 'GD_IFP_obscured_PSF_ycentroid_expected_zern.pkl') + xp_og = load_variable(data_dir + 'GD_IFP_obscured_PSF_xcentroid_found_with_wfe_airy_zern.pkl') + yp_og = load_variable(data_dir + 'GD_IFP_obscured_PSF_ycentroid_found_with_wfe_airy_zern.pkl') + + # # Zoom 1: + # xe_og1 = load_variable(data_dir + 'GD_IFP_zoom_obscured_PSF_xcentroid_expected_zern.pkl') + # ye_og1 = load_variable(data_dir + 'GD_IFP_zoom_obscured_PSF_ycentroid_expected_zern.pkl') + # xp_og1 = load_variable(data_dir + 'GD_IFP_zoom_obscured_PSF_xcentroid_found_with_wfe_airy_zern.pkl') + # yp_og1 = load_variable(data_dir + 'GD_IFP_zoom_obscured_PSF_ycentroid_found_with_wfe_airy_zern.pkl') + + xe = xe_og#np.append(xe_og, xe_og1) + ye = ye_og#np.append(ye_og, ye_og1) + xp = xp_og#np.append(xp_og, xp_og1) + yp = yp_og#np.append(yp_og, yp_og1) + + # xe = np.append(xe_og2, xe_og3) + # ye = np.append(ye_og2, ye_og3) + # xp = np.append(xp_og2, xp_og3) + # yp = np.append(yp_og2, yp_og3) + + + figtitle = '../figures adapted for thesis/Focal Plane/GD_IFP_zern' + plot_distortions(xe, ye, xp, yp, show=True, save=False, figtitle=figtitle, + lims=[[-120,120],[-120,120]]) + + + + + diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.001.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.001.pdf new file mode 100644 index 0000000..8fd98de Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.001.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.001.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.001.png new file mode 100644 index 0000000..c8e95ea Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.001.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.125.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.125.pdf new file mode 100644 index 0000000..e958050 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.125.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.125.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.125.png new file mode 100644 index 0000000..9c99b28 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.125.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.25.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.25.pdf new file mode 100644 index 0000000..59b8b92 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.25.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.25.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.25.png new file mode 100644 index 0000000..52a8808 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.25.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.5.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.5.pdf new file mode 100644 index 0000000..0492981 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.5.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.5.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.5.png new file mode 100644 index 0000000..91f78e2 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.5.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.75.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.75.pdf new file mode 100644 index 0000000..b50781d Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.75.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.75.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.75.png new file mode 100644 index 0000000..1119cac Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_0.75.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.25.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.25.pdf new file mode 100644 index 0000000..ac7f758 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.25.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.25.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.25.png new file mode 100644 index 0000000..4bc11af Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.25.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.5.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.5.pdf new file mode 100644 index 0000000..25ebd18 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.5.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.5.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.5.png new file mode 100644 index 0000000..6fcc96b Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.5.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.75.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.75.pdf new file mode 100644 index 0000000..f0aeda1 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.75.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.75.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.75.png new file mode 100644 index 0000000..ac3b0d9 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.75.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.pdf new file mode 100644 index 0000000..74bd8f6 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.png new file mode 100644 index 0000000..b6783bb Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_1.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_10.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_10.pdf new file mode 100644 index 0000000..f46d733 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_10.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_10.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_10.png new file mode 100644 index 0000000..e744f53 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_10.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_2.5.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_2.5.pdf new file mode 100644 index 0000000..7212a45 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_2.5.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_2.5.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_2.5.png new file mode 100644 index 0000000..f665d91 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_2.5.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_2.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_2.pdf new file mode 100644 index 0000000..eb17a30 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_2.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_2.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_2.png new file mode 100644 index 0000000..8412d29 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_2.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_3.5.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_3.5.pdf new file mode 100644 index 0000000..2d808ff Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_3.5.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_3.5.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_3.5.png new file mode 100644 index 0000000..0c422df Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_3.5.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_3.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_3.pdf new file mode 100644 index 0000000..aabdd0b Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_3.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_3.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_3.png new file mode 100644 index 0000000..d36d213 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_3.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_4.5.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_4.5.pdf new file mode 100644 index 0000000..f5ed3ed Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_4.5.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_4.5.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_4.5.png new file mode 100644 index 0000000..a741ab9 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_4.5.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_4.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_4.pdf new file mode 100644 index 0000000..981c77a Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_4.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_4.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_4.png new file mode 100644 index 0000000..37b522e Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_4.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_5.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_5.pdf new file mode 100644 index 0000000..b81b6f6 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_5.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_5.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_5.png new file mode 100644 index 0000000..deaf39d Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_5.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_6.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_6.pdf new file mode 100644 index 0000000..784935e Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_6.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_6.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_6.png new file mode 100644 index 0000000..3da2c1b Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_6.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_7.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_7.pdf new file mode 100644 index 0000000..05fdcaa Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_7.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_7.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_7.png new file mode 100644 index 0000000..d321e13 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_7.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_8.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_8.pdf new file mode 100644 index 0000000..0008144 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_8.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_8.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_8.png new file mode 100644 index 0000000..564a73c Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_8.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_9.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_9.pdf new file mode 100644 index 0000000..faa2d39 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_9.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_9.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_9.png new file mode 100644 index 0000000..371b993 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/DGD_full_field_zern_psd_9.png differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/GD_full_field_zern_psd_0.pdf b/scripts/SPIE/figures adapted for thesis/ADC rotation/GD_full_field_zern_psd_0.pdf new file mode 100644 index 0000000..83a07d1 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/GD_full_field_zern_psd_0.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/ADC rotation/GD_full_field_zern_psd_0.png b/scripts/SPIE/figures adapted for thesis/ADC rotation/GD_full_field_zern_psd_0.png new file mode 100644 index 0000000..69eba08 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/ADC rotation/GD_full_field_zern_psd_0.png differ diff --git a/scripts/SPIE/figures adapted for thesis/Focal Plane/GD_IFP_psd.pdf b/scripts/SPIE/figures adapted for thesis/Focal Plane/GD_IFP_psd.pdf new file mode 100644 index 0000000..08b49ab Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/Focal Plane/GD_IFP_psd.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/Focal Plane/GD_IFP_psd.png b/scripts/SPIE/figures adapted for thesis/Focal Plane/GD_IFP_psd.png new file mode 100644 index 0000000..25cafc5 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/Focal Plane/GD_IFP_psd.png differ diff --git a/scripts/SPIE/figures adapted for thesis/GD_full_field_psd.pdf b/scripts/SPIE/figures adapted for thesis/GD_full_field_psd.pdf new file mode 100644 index 0000000..d221ad4 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/GD_full_field_psd.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/GD_full_field_psd.png b/scripts/SPIE/figures adapted for thesis/GD_full_field_psd.png new file mode 100644 index 0000000..9b1bb26 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/GD_full_field_psd.png differ diff --git a/scripts/SPIE/figures adapted for thesis/GD_full_field_zern_psd_new.pdf b/scripts/SPIE/figures adapted for thesis/GD_full_field_zern_psd_new.pdf new file mode 100644 index 0000000..dbc2aa9 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/GD_full_field_zern_psd_new.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/GD_full_field_zern_psd_new.png b/scripts/SPIE/figures adapted for thesis/GD_full_field_zern_psd_new.png new file mode 100644 index 0000000..7a5a8d5 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/GD_full_field_zern_psd_new.png differ diff --git a/scripts/SPIE/figures adapted for thesis/rotation_relation.pdf b/scripts/SPIE/figures adapted for thesis/rotation_relation.pdf new file mode 100644 index 0000000..82f3a3a Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/rotation_relation.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/rotation_relation.png b/scripts/SPIE/figures adapted for thesis/rotation_relation.png new file mode 100644 index 0000000..023ec75 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/rotation_relation.png differ diff --git a/scripts/SPIE/figures adapted for thesis/rotation_relation_relangles.pdf b/scripts/SPIE/figures adapted for thesis/rotation_relation_relangles.pdf new file mode 100644 index 0000000..ae83731 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/rotation_relation_relangles.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/rotation_relation_relangles.png b/scripts/SPIE/figures adapted for thesis/rotation_relation_relangles.png new file mode 100644 index 0000000..a745e9d Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/rotation_relation_relangles.png differ diff --git a/scripts/SPIE/figures adapted for thesis/rotation_relation_with_polar_plot.pdf b/scripts/SPIE/figures adapted for thesis/rotation_relation_with_polar_plot.pdf new file mode 100644 index 0000000..f570383 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/rotation_relation_with_polar_plot.pdf differ diff --git a/scripts/SPIE/figures adapted for thesis/rotation_relation_with_polar_plot.png b/scripts/SPIE/figures adapted for thesis/rotation_relation_with_polar_plot.png new file mode 100644 index 0000000..e4cce95 Binary files /dev/null and b/scripts/SPIE/figures adapted for thesis/rotation_relation_with_polar_plot.png differ diff --git a/scripts/SPIE/generate_wfe.py b/scripts/SPIE/generate_wfe.py index f49edff..06c1ff4 100644 --- a/scripts/SPIE/generate_wfe.py +++ b/scripts/SPIE/generate_wfe.py @@ -1,6 +1,7 @@ import numpy as np import sys -sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') +# sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') +sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Documents/Published/FourierOptics GitHub release/') from fourierOptics import * from pdb import set_trace diff --git a/scripts/SPIE/geometric_distortions.py b/scripts/SPIE/geometric_distortions.py index 6b7841d..849feb6 100644 --- a/scripts/SPIE/geometric_distortions.py +++ b/scripts/SPIE/geometric_distortions.py @@ -1,7 +1,8 @@ import numpy as np import sys # sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') -sys.path.insert(0, '/home/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') +# sys.path.insert(0, '/home/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') +sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Documents/Published/FourierOptics GitHub release/') from fourierOptics import * ''' @@ -51,8 +52,8 @@ def plot_distortions(): ax.set_xlabel('x (mm)') ax.set_ylabel('y (mm)') fig.colorbar(im, ax=ax, label='PSF centroid shift ($\\mu$as)') - plt.savefig('./figures/GD_full_field_zern_psd.pdf', bbox_inches='tight', transparent=True) - plt.savefig('./figures/GD_full_field_zern_psd.png', bbox_inches='tight', transparent=True) + plt.savefig('./figures/UPDATED_GD_full_field_zern_psd.pdf', bbox_inches='tight', transparent=True) + plt.savefig('./figures/UPDATED_GD_full_field_zern_psd.png', bbox_inches='tight', transparent=True) plt.tight_layout() plt.show() @@ -75,10 +76,10 @@ if __name__ == '__main__': hyList = hyList.flatten() # Loading in the WFEs - wfe_p1 = load_variable('./data/ADC_tWFE_p1_zern_20nm_psd_15nm.pkl') - wfe_p2 = load_variable('./data/ADC_tWFE_p2_zern_20nm_psd_15nm.pkl') - wfe_p3 = load_variable('./data/ADC_tWFE_p3_zern_20nm_psd_15nm.pkl') - wfe_p4 = load_variable('./data/ADC_tWFE_p4_zern_20nm_psd_15nm.pkl') + wfe_p1 = load_variable('./data/compressed items/ADC_tWFE_p1_zern_20nm_psd_15nm.pkl') + wfe_p2 = load_variable('./data/compressed items/ADC_tWFE_p2_zern_20nm_psd_15nm.pkl') + wfe_p3 = load_variable('./data/compressed items/ADC_tWFE_p3_zern_20nm_psd_15nm.pkl') + wfe_p4 = load_variable('./data/compressed items/ADC_tWFE_p4_zern_20nm_psd_15nm.pkl') # Propagate through the field positions without any WFEs for i in range(len(hxList)): diff --git a/scripts/SPIE/geometric_distortions_incl_rotations.py b/scripts/SPIE/geometric_distortions_incl_rotations.py new file mode 100644 index 0000000..8cef8b1 --- /dev/null +++ b/scripts/SPIE/geometric_distortions_incl_rotations.py @@ -0,0 +1,174 @@ +import numpy as np +import sys +# sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') +sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Documents/Published/FourierOptics GitHub release/') +from fourierOptics import * + +''' + Author: Joost van den Born, PhD student at University of Groningen + Contact: born@astro.rug.nl + Description: Script to generate the geometric distortions due to WFEs + off the ADC prisms. The distortions are produced by measuring + the offset of the PSF centroid with and without WFE as a + function of field position. + Script made for the SPIE manuscript. +''' + +# Rotation of ADC angles - [0.125, 0.25, 0.5, 0.75, 1, 2, 5, 10, 30, 45, 60, 90, 180] + + +def plot_distortions(): + # Coordinates without WFEs + xe = load_variable('./data/ADC rotation/GD_obscured_PSF_xcentroid_expected_zern_psd.pkl') + ye = load_variable('./data/ADC rotation/GD_obscured_PSF_ycentroid_expected_zern_psd.pkl') + + # Coordinates with WFEs + xf = load_variable('./data/ADC rotation/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd.pkl') + yf = load_variable('./data/ADC rotation/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd.pkl') + + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3) + + n = xe.shape[0] + X = xe.flatten() + Y = ye.flatten() + dX = xf - xe + dY = yf - ye + dx = dX.flatten() + dy = dY.flatten() + + # Magnitude of the distortions + R = np.sqrt(dx**2 + dy**2) * mcd.platescale * 1e6 + + # Dummy figure, used for the colorbar colors in the quiver plot + im = plt.imshow(np.reshape(R, (n,n))) + plt.close() + + fig = plt.figure() + ax = fig.add_subplot() + for i in range(len(xe)): + ax.plot(xe[:,i], ye[:,i], 'k-', alpha=0.2, linewidth=.5) + ax.plot(xe[i,:], ye[i,:], 'k-', alpha=0.2, linewidth=.5) + ax.quiver(X,Y,dx,dy, angles='xy', edgecolor='k', facecolor='None', linewidth=.5, units='dots') + ax.quiver(X,Y,dx,dy,R, alpha=.5, units='dots') + ax.set_aspect('equal', adjustable='box') + ax.set_xlabel('x (mm)') + ax.set_ylabel('y (mm)') + fig.colorbar(im, ax=ax, label='PSF centroid shift ($\\mu$as)') + plt.savefig('./figures/UPDATED2_GD_full_field_zern_psd.pdf', bbox_inches='tight', transparent=True) + plt.savefig('./figures/UPDATED2_GD_full_field_zern_psd.png', bbox_inches='tight', transparent=True) + plt.tight_layout() + plt.show() + + +if __name__ == '__main__': + # plt.rc('text', usetex=True) + # plt.rc('font', family='serif') + + # Loading in the WFEs + wfe_p1 = load_variable('./data/compressed items/ADC_tWFE_p1_zern_20nm_psd_15nm.pkl') + wfe_p2 = load_variable('./data/compressed items/ADC_tWFE_p2_zern_20nm_psd_15nm.pkl') + wfe_p3 = load_variable('./data/compressed items/ADC_tWFE_p3_zern_20nm_psd_15nm.pkl') + wfe_p4 = load_variable('./data/compressed items/ADC_tWFE_p4_zern_20nm_psd_15nm.pkl') + + + + # Rotation of ADC angles - [0.125, 0.25, 0.5, 0.75, 1, 2, 5, 10], 30, 45, 60, 90, 180] + thetaList = [0.001, 0.125, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3, 3.5, 4, 4.5, 5, 6, 7, 8, 9, 10] + # Propagate through the field positions without any WFEs + for theta in thetaList: + + # Setup of simulation + l = 1.49e-3 + Elist, Eplist = [], [] + xlist, ylist = [], [] + xplist, yplist = [], [] + x_expected_list, y_expected_list = [], [] + x_centroid_list, y_centroid_list = [], [] + + # Set field points + hxList, hyList = np.mgrid[-1:1:11j,-1:1:11j]-(1/300) + hxList = hxList.flatten() + hyList = hyList.flatten() + + for i in range(len(hxList)): + hx = hxList[i] + hy = hyList[i] + print('Rotation: {:.3f} - hx: {} - hy: {}'.format(theta,hx,hy) ) + + # Initializing MICADO paraxial model + mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, entrance_pupil='obscured') + + # Propagate through the MICADO system + mcd.ELT_ADC.set_orientation(theta) + E, x, y = mcd.propagate(l=l,surface=-1, zoom=False, show=False, + print_progress=False, hx=hx, hy=hy) + + # Find approximation for the centroid and amplitude as a first + # guess for the Obscured Airy pattern fit. + amplitude_guess = np.max(np.abs(E))**2 + xbc, ybc, _, _ = mcd.centroid_shift(method='Barycenter') + print('Barycenter guess: ', xbc, ybc) + + # Do the Obscured Airy Pattern Fit for the unperturbed PSF + xe, ye, _, _ = mcd.centroid_shift(method='Obscured Airy', initialGuess=(amplitude_guess, xbc, ybc, 0.0, 0.03)) + print('Airy result: ', xe, ye) + x_expected_list.append(xe) + y_expected_list.append(ye) + + + + # Load in the WFEs and set them to the corresponding ADC surfaces + print('\nSetting the wavefront errors...') + # null_wfe = np.zeros((3001,3001)) + mcd.ELT_ADC.set_orientation(0) + mcd.ELT_ADC.p1.add_wavefront_error(wfe_p1) + mcd.ELT_ADC.p2.add_wavefront_error(wfe_p2) + mcd.ELT_ADC.p3.add_wavefront_error(wfe_p3) + mcd.ELT_ADC.p4.add_wavefront_error(wfe_p4) + + + # Propagate through the MICADO system + print('Propagating with Wavefront Errors applied...') + mcd.ELT_ADC.set_orientation(theta) + Ep, xp, yp = mcd.propagate(l=l,surface=-1, zoom=False, show=False, + print_progress=False, hx=hx, hy=hy) + + # Do the Obscured Airy Pattern Fit for the perturbed PSF + xf, yf, _, _ = mcd.centroid_shift(method='Obscured Airy', initialGuess=(amplitude_guess, xe, ye, 0.0, 0.03)) + + # Resulting centroid shift due to the WFE + x_centroid_list.append(xf) + y_centroid_list.append(yf) + R = np.sqrt( (xf-xe)**2 + (yf-ye)**2 ) * mcd.platescale * 1e6 + print('xf: {}, yf: {}, shift (uas): {}'.format(xf,yf, R)) + + # Clear the memory by emptying the large arrays + mcd = None + wfe = None + E, x, y = None, None, None + Ep, xp, yp = None, None, None + + + # Reshaping the arrays + shape = (int(np.sqrt(len(x_expected_list))), int(np.sqrt(len(x_expected_list)))) + x_expected = np.asarray(x_expected_list).reshape(shape) + y_expected = np.asarray(y_expected_list).reshape(shape)#.T + + # Saving the arrays + save_variable('./data/ADC rotation/GD_obscured_PSF_xcentroid_expected_zern_psd_rot{}.pkl'.format(theta), x_expected) + save_variable('./data/ADC rotation/GD_obscured_PSF_ycentroid_expected_zern_psd_rot{}.pkl'.format(theta), y_expected) + + + # Reshaping the arrays + shape = (int(np.sqrt(len(x_centroid_list))), int(np.sqrt(len(x_centroid_list)))) + x_centroid = np.asarray(x_centroid_list).reshape(shape) + y_centroid = np.asarray(y_centroid_list).reshape(shape) + + # Saving the arrays + save_variable('./data/ADC rotation/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd_rot{}.pkl'.format(theta), x_centroid) + save_variable('./data/ADC rotation/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd_rot{}.pkl'.format(theta), y_centroid) + + # plot_distortions() + + +