From 3d05715c71fc438fcfc20ca73bbb0163c424ba0b Mon Sep 17 00:00:00 2001 From: Ida Date: Sun, 24 Sep 2017 15:56:20 +0100 Subject: [PATCH 1/6] Add tutorial on elemental mapping w EELS --- .../EELS/tutorial_elemental_mapping.ipynb | 835 ++++++++++++++++++ .../EELS_mapping_tutorial.hspy | Bin 0 -> 459497 bytes .../HAADF.hspy | Bin 0 -> 27531 bytes 3 files changed, 835 insertions(+) create mode 100644 electron_microscopy/EELS/tutorial_elemental_mapping.ipynb create mode 100644 electron_microscopy/EELS/tutorial_elemental_mapping_datasets/EELS_mapping_tutorial.hspy create mode 100644 electron_microscopy/EELS/tutorial_elemental_mapping_datasets/HAADF.hspy diff --git a/electron_microscopy/EELS/tutorial_elemental_mapping.ipynb b/electron_microscopy/EELS/tutorial_elemental_mapping.ipynb new file mode 100644 index 0000000..6f1fc96 --- /dev/null +++ b/electron_microscopy/EELS/tutorial_elemental_mapping.ipynb @@ -0,0 +1,835 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating and plotting elemental maps\n", + "In this tutorial, elemental maps of copper and zinc will be created from electron energy loss spectra (EELS). The data is recorded using a sample of copper and zinc oxide deposited on carbon nanotubes. The particles are likely to be very small, and the carbon is not completely covered. In this sample, the ratio of Zn and Cu is 3:1, and approximately 80 wt% of the sample is carbon. Due to the small amount of copper and zinc, the signal strength is low. To improve the signal strength without lowering the spatial resolution, the spectra were recorded with a high energy dispersion, resulting in low energy resolution. This means that any fine structure is not visible, and Hartree-Slater cross sections will be used to fit Cu and Zn. \n", + "\n", + "This notebook requires HyperSpy 1.3 or higher." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Changes\n", + "\n", + "2017/09/24: initial version by Ida Hjorth" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Table of contents" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. Decomposition \n", + "2. Background removal\n", + "3. Fitting Cu and Zn Hartree-Slater cross sections\n", + "4. Saving and restoring the model\n", + "5. Creating elemental maps from the model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1. Decomposition" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib nbagg\n", + "import numpy as np\n", + "import hyperspy.api as hs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "s_eels = hs.load('tutorial_elemental_mapping_datasets/EELS_mapping_tutorial.hspy')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Principal component analysis (PCA) so used to denoise the data. For more information about this, see [HyperSpy's PCA documentation](http://hyperspy.org/hyperspy-doc/current/user_guide/mva.html#denoising).\n", + "\n", + "The data has to be in 64-bit float for the PCA to work in HyperSpy." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "s_eels.change_dtype('float64')\n", + "s_eels.decomposition()\n", + "s_eels.plot_explained_variance_ratio()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "s_eels = s_eels.get_decomposition_model(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Generate synthetic zero peak for low-loss signal (if lacking)\n", + "In the model-based approach, the low-loss signal can be convolved with the high-loss signal such that the energy spread of the electron beam and plural scattering is taken into account. This will give better model. In addition, the experimental zero loss peak can be used to precisely calibrate the energy scale. If you have recorded a low-loss signal, you can skip this part and go directly to the generation of the background model.\n", + "\n", + "Unfortunately, in our example the low-loss signal was not recorded. Therefore, we generate a zero loss peak by creating a Gaussian model component with a given fwhm (equivalent to the energy spread) and a low loss signal with the same spatial dimensions as the high-loss EELS signal. This is the second best option, after of course having a real low-loss signal." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "g = hs.model.components1D.Gaussian(A=1., centre=0.) #Zero loss peak\n", + "g.fwhm = 5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The zero-loss signal `s_zlg` can be generated as follows. This synthetic signal accounts for the energy spread of the electron beam, but not plural scattering as would with a recorded low-loss signal." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x = s_eels.axes_manager.navigation_axes[0].size\n", + "y = s_eels.axes_manager.navigation_axes[1].size\n", + "a = g.function(np.arange(-50, 200, 1)) #low-loss signal, from - 50 to 200 eV with 1 eV step size\n", + "b = []\n", + "for i in range(y):\n", + "\tbj = []\n", + "\tfor i in range(x):\n", + "\t\tbj.append(a)\n", + "\tb.append(bj)\n", + "c = np.asarray(b)\n", + "s_zlg = hs.signals.EELSSpectrum(c)\n", + "s_zlg.axes_manager.signal_axes[0].offset = -50.\n", + "s_zlg.plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. Find and subtract the background" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The background of the high-loss signal is found by fitting a power law to the signal. If you have recorded a low-loss signal, load it now and insert it in the model rather than the constructed version" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "bm = s_eels.create_model(ll=s_zlg) #ll=low-loss signal or constructed zero-loss signal. \n", + "bm.set_signal_range(750., 915.) #Cu-edge starts at 931 eV" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As there are large variations in intensity across the sample, the background is best fitted by giving constraints such as boundary conditions and initial vaules. The following constraints are found to work well on the dataset used in this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "bm.components" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "p = bm.components.PowerLaw\n", + "p.A.value = 100000000000.\n", + "p.A.bmin = 100000000000.\n", + "p.A.bmax = 10000000000000.\n", + "p.r.value = 3.\n", + "p.r.bmin = 1.\n", + "p.r.bmax = 3.\n", + "bm.components.PowerLaw.A.assign_current_value_to_all() #sets the value to all the probe positions points (navigation dimension).\n", + "bm.components.PowerLaw.r.assign_current_value_to_all()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "bm.multifit(fitter='mpfit', bounded=True)\n", + "bm.reset_signal_range()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now plot the signal and the fitted background model, to see the quality of the fit. Go specifically to point (31,14), where there is a clear copper and zinc edge. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "bm.plot() #Check point (31, 14)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The background fits nicely to most of the image, but in vacuum the fitting is not as good (naturally). A trace non-quantifiable Ni-component at about 850 eV also makes the background fitting more challenging." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's see how the signal looks like with the background removed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "s1_eels = s_eels - bm.as_signal()\n", + "s1_eels.plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3. Fitting Cu and Zn components to the spectrum" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A new model consiting of Cu and Zn Hartree-Slater cross sections will be created. For this to work, Hartree-Slater GOS files must configured in HyperSpy." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The path to these cross sections must be set in the preferences, under the EELS tab" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hs.preferences.gui()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, the experimental microscope parameters is set, and the model is created" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "s1_eels.set_microscope_parameters(beam_energy=200, convergence_angle=13.33, collection_angle=55.28) \n", + "s1_eels.add_elements(('Cu', 'Zn')) \n", + "#Adding Hartree Slater cross sections. For this to work, H-S GOS must be installed.\n", + "m = s1_eels.create_model(ll=s_zlg, auto_background=False)\n", + "#Background is already subtracted, so this can be set to False." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m.components" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Due to the varying and low signal strength, some initial parameters and boundaries are set to ensure successful fitting. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "m.components.Cu_L3.onset_energy.value = 931\n", + "m.components.Zn_L3.onset_energy.value = 1020\n", + "m.components.Zn_L3.intensity.bmin = 0 # lower boundary value (negative values are non-physical)\n", + "m.components.Zn_L3.intensity.value = 60 # initial value\n", + "m.components.Zn_L3.intensity.assign_current_value_to_all() # sets these values to all the scanning points.\n", + "m.components.Cu_L3.intensity.bmin = 0\n", + "m.components.Cu_L3.intensity.value = 60\n", + "m.components.Cu_L3.intensity.assign_current_value_to_all()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We first fit the copper components, as it appears in front of the Zn component. Only the Cu component must then be active, and the signal range is set to a region where only Cu appears." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Fitting Cu component first\n", + "m.components.Cu_L1.active = True\n", + "m.components.Cu_L2.active = True\n", + "m.components.Cu_L3.active = True\n", + "m.components.Zn_L1.active = False\n", + "m.components.Zn_L2.active = False\n", + "m.components.Zn_L3.active = False\n", + "\n", + "m.set_signal_range(900., 1000.)\n", + "m.multifit(fitter='mpfit', bounded=True) #Using bounded fitting" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After fitting Cu, the parameters locked for the Cu-components, and the Zn-components are set to active. Now Zn will be fitted, while the Cu component is constant." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Zn fitting\n", + "m.components.Cu_L3.intensity.free = False\n", + "\n", + "m.set_signal_range(1000., 1300.)\n", + "m.components.Zn_L1.active = True\n", + "m.components.Zn_L2.active = True\n", + "m.components.Zn_L3.active = True\n", + "\n", + "m.multifit(fitter='mpfit', bounded=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lastly, the components are fitted together over the whole energy range." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m.components.Cu_L3.intensity.free = True\n", + "m.reset_signal_range()\n", + "m.multifit(fitter='mpfit', bounded=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's see the results. Go to the region around (32,14), where there clearly is copper or zinc of some kind." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m.plot(plot_components=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 4. Saving and restoring the models" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As the fitting process can be slow, saving the models can be a good idea." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "m.save('model', overwrite=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The model can be loaded and restored." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "mr = hs.load('model.hspy')\n", + "mr = mr.models.restore('a')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " Models can also be stored as spectrums. Currently, all components are active, so `ms` will be the sum of all components." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ms = mr.as_signal()\n", + "ms.save('model_spectrum', overwrite=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To save the Cu model separately, set Zn components not active." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mr.components.Zn_L1.active = False\n", + "mr.components.Zn_L2.active = False\n", + "mr.components.Zn_L3.active = False\n", + "m_cu = mr.as_signal()\n", + "m_cu.save('m_cu', overwrite=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "# 5. Make maps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this section, elemental maps will be made from the intensity of the Cu-L$_3$ and Zn-L$_3$ components saved above. The required libraries and data files are loaded. The HAADF image will also be plotted, for comparision with the elemental distribution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import matplotlib.gridspec as gridspec\n", + "from mpl_toolkits.axes_grid1.anchored_artists import AnchoredSizeBar\n", + "import matplotlib.font_manager as fm\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "m = hs.load('model.hspy')\n", + "mr = m.models.restore('a')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "s_copper = mr.components.Cu_L3.intensity.as_signal()\n", + "s_zinc = mr.components.Zn_L3.intensity.as_signal()\n", + "s_haadf = hs.load('tutorial_elemental_mapping_datasets/HAADF.hspy').as_signal2D((0, 1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Gridspec is a convenient function to use when creating several subplots. We create axes for the elemental maps, HAADF signal as well as the color-bars that will be used indicate the numerical intensity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = plt.figure(figsize=(6, 2.7))\n", + "gs = gridspec.GridSpec(30, 3)\n", + "ax_cu = fig.add_subplot(gs[0:-1, 0])\n", + "ax_zn = fig.add_subplot(gs[0:-1, 1])\n", + "ax_haadf = fig.add_subplot(gs[0:-1, 2])\n", + "cbar_cu = fig.add_subplot(gs[-1, 0]) #Colorbars are much thinner than the map axes (1/30 of the height of the image)\n", + "cbar_zn = fig.add_subplot(gs[-1, 1])\n", + "cbar_haadf = fig.add_subplot(gs[-1, 2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The signals can be plotted by using imshow. Origin and extent can be changed to alter the ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "cax_cu = ax_cu.imshow(\n", + "\ts_copper.data,\n", + "\tinterpolation='nearest',\n", + "\torigin='lower',\n", + "\textent=s_copper.axes_manager.signal_extent\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "cax_zn = ax_zn.imshow(\n", + "\ts_zinc.data,\n", + "\tinterpolation='nearest',\n", + "\torigin='lower',\n", + "\textent=s_zinc.axes_manager.signal_extent\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "NumPy functions, such as `flipud`, `fliplr` and `rot90` can also be used to give the image the correct orientation. Here,`flipud` is used to give the HAADF image the same orientation as the elemental maps." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ax_haadf.imshow(\n", + "\tnp.flipud(s_haadf.data),\n", + "\tinterpolation='nearest',\n", + "\torigin='upper',\n", + "\textent=s_haadf.axes_manager.signal_extent\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Disable axis ticks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ax_haadf.set_xticks([])\n", + "ax_cu.set_xticks([])\n", + "ax_zn.set_xticks([])\n", + "ax_zn.set_yticks([])\n", + "ax_cu.set_yticks([])\n", + "ax_haadf.set_yticks([])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Scalebars can be added by using AnchoredSizeBar. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fontprops = fm.FontProperties(size=18)\n", + "scalebar0 = AnchoredSizeBar(\n", + " ax_cu.transData,\n", + " 5, '5 nm', 1, # length of bar, label, loc\n", + " pad=0.1,\n", + " color='white',\n", + " frameon=False,\n", + " size_vertical=0.6,\n", + " fontproperties=fontprops)\n", + "scalebar1 = AnchoredSizeBar(\n", + " ax_cu.transData,\n", + " 5, '5 nm', 1,\n", + " pad=0.1,\n", + " color='white',\n", + " frameon=False,\n", + " size_vertical=0.6,\n", + " fontproperties=fontprops)\n", + "scalebar2 = AnchoredSizeBar(\n", + " ax_cu.transData,\n", + " 5, '5 nm', 1,\n", + " pad=0.1,\n", + " color='white',\n", + " frameon=False,\n", + " size_vertical=0.6,\n", + " fontproperties=fontprops)\n", + "ax_cu.add_artist(scalebar0)\n", + "ax_zn.add_artist(scalebar1)\n", + "ax_haadf.add_artist(scalebar2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The color schemes viridis, inferno, plasma and magma are nice to use as they are grayscale compatible, perceptually uniform and colorblind-proof. For more info on these color maps, see https://www.youtube.com/watch?v=xAoljeRJ3lU" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "cax_zn.set_clim(vmin=0, vmax=30)\n", + "cax_zn.set_cmap('inferno')\n", + "cax_cu.set_clim(vmin=0, vmax=21)\n", + "cax_cu.set_cmap('viridis')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We add labels to indicate what shown is in each image." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ax_cu.text(0.05,0.05, 'Cu', color='white', size=18, transform=ax_cu.transAxes)\n", + "ax_zn.text(0.05,0.05, 'Zn', color='white', size=18, transform=ax_zn.transAxes)\n", + "ax_haadf.text(0.05,0.05, 'HAADF', color='white', size=18, transform=ax_haadf.transAxes)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lastly, the colorbars are added. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "cb_zn = fig.colorbar(ax_zn.images[0], cax=cbar_zn, extend='both', orientation='horizontal') \n", + "cb_cu = fig.colorbar(ax_cu.images[0], cax=cbar_cu,extend='both', orientation='horizontal') \n", + "cb_haadf = fig.colorbar(ax_haadf.images[0], cax=cbar_haadf, extend='both', orientation='horizontal')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "cb_cu.set_ticks([0, 10, 20, 30, 40])\n", + "cb_zn.set_ticks([0, 15, 30, 45, 60])\n", + "cb_haadf.set_ticks([0, 1000, 2000])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "gs.update(hspace=0.02, wspace=0.02)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Saving the matplotlib figure object as a png-file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "fig.savefig(\"elemental_map.png\", dpi=300)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.5.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/electron_microscopy/EELS/tutorial_elemental_mapping_datasets/EELS_mapping_tutorial.hspy b/electron_microscopy/EELS/tutorial_elemental_mapping_datasets/EELS_mapping_tutorial.hspy new file mode 100644 index 0000000000000000000000000000000000000000..def91835b34969928ddf22a517b152be272791e9 GIT binary patch literal 459497 zcmeFZ1yr3+vM;)EcM0we!5xCT1lJJU-QC?KxCDZG2=4Cg4#C|ec;H1~=AQq#v*ygX z_pSTxn{3wF^>uYsb@gv|)z_AUhzW_nL1RG!etsY!0iXcHUymp8B50*$WuL&ejIDCKd*k_IAHmFXNK$Mc`kgNYCT-uj3jP zAoe3xevVjJ04e|yU}$1)pk-)fYoTNRs|8FTpkJClqv^K~zenziKR-*_&x83V4;CQ% zY!CIX_Ou)gZ0$^}EdNV(e}>hI-QUnWH}XsSr!PPM{vMAn{loa#nI9ka=Q#Uk@pyyv zTmC;<|IPdJ{r`Ui{u+-2&s|o19zQP#2n$Oo5GYt1=-JykSP+O?=olINs(Oh7_7}nb zB2{=RBlL3wzl^)n=l95qy}yp*|DxG{JF$P12eh9PtM_w!0|B7^L%#Ui`NC5yZ?Y;0{>|;LbDe5rFun;h^kP;}G$S@KJDu~O=5HQoz3qIGwKGTi(hunp#}fUM}DT_mi7jgb|&^N1Qdc+4nN+8l)ozwp6gMb=|76k=bsDkV)v(< zmYs!;xjC)A1rq>(^sN8lr?`$K0Ud#~j;+1PuNT#eA20f%;vxic4my_hKeB+ZJOJ?0 z{!9N96osXKnneAJT?t_sDFS|ZX-0Z_k>{#kc&I;GgXy&f)!b?O)vo`ajm8KMntHMc`#0@yoMS<7b-r^m_;8 zAMI1g0ebszEOc_Zj$-{`OVVE?N9e;4`R@eTNg=XQVM``=|G{LlOO z65p!N)i3*&-p_geMe8N*WM3Hni}ZJVL;UTWfZ(~|mv;DYem9`~?8nP~=MCX+{)-#| z(eE-J$#43+Jr0N_`;T&U&*eFa-+cPt1hD)1`Qaasm;QeG+kX3R!N2A=f@ggS>faA9 z{>eUa)3jN?y(f|B4qZfd2Ws670v>B_zPk#P!#~ z4&o1-e?!gp%l3<(jL$8>0@$DRAby>5S{WMJ8T>dhMIzw+n;tU^fcfb!=Q=Nr{#_sZ zm%g2zj``1q{|owUk8Vf2umHZl`Tg_U)>6mfm*4;Fy!K~5w4cY&PuKo_UJC^v`(?+` z#7M{fm#v?^V*EHT_^I;~kKY(T0z~+w6oh~96y=qEl3x`s_`J}+`}ezl4wfdrPO|@* ze?QMBUi|yLr~Wchf3@_VzW&jvCrli&XnbblEC0|NgjziU5_qZiSa@%@tT zUiklA`jVIanxFsGyhQQrz~A%A|Gjw$>_=bzznqtTzk&XHc}e#9{PIs;`tL@}zn9)W z%1bvd0sjog|66(Kuk*owH7{j7`}e<;msSJ#{;vNo&zF99K=4EVzc*j{dEen>KJs&E z{l7aeJ;U?E?u&o_{rS@GY281|OaJqGbAO$G|Fiur{?XrA@OPUpz4$}_T@d(dUJ`rm zc=TV^iCQlgqn_&(bS$iYE_4)3j4XA`0RT~h9~YH=Jn-w;IGFsn=kw!!sFsPP-OpQg zKkmN1tT}$HKc4l0e(ab1%5~2P59r4_?B@pvl+xF|6E4> zUc>xcOA`Fj2LT4Xq5ahk;HRrU-Tdj|i#h=Lhvd)C&$c-0d)5Aa!}S;YKYjg-)1Q9) zwO`^fzvJ^iDg1@O&h;M|{|LXoIrtyN|CIh9@CSiE2>e0d4+4J>_=CV71pXlK2Z8^K z5OCH6!10~8wh7+S>{{9t>DXM;gHUfex4LRCALK7`ODg%f7IiC4zFw<~5S?i^nlBD8 z*=JvT>aT;UP))84nNPPbS#DVS^c85BNze)s4CK1Eu?f4eOcx5HxXeAS1D~U8P=K=@ z-9Fa7aH3?kWL2-Ou;4`EEr-F`ckDLA@ zGcV&55$#xBP(q{HnU%f+8?r{{L63N=6r%_VIMPFwWYE)%oykFBsG2XyH^e5Q&#>It3-$V?Pr<Y9AX(y;8T#!j_`X)dOtruW_2$vf4%TN~;90`bS< zA9V^VJ{+7cxcbyF;?r2o)LIg&dq`^_LOyJPM(6-xu3Y8z$VjmngsS8v-xjnoup8ol zuv|~D0SEg|*=Z0{IU`rrkI!7fY>&uDd=3i?5>{QTD>K*CxILkUIUyMD?X2xV2`or6 zhzNF0PsbiQKJs zdSBV2uBLS%FB<88T)jaYmQgRwGsik{BA!flZNz7;Ov+0Rm&$mIYd6(l)$<_{pR7oT z0Q8$(d7D=tKO+h)Su3mA#1><`6gEVMiKLA}5~2sQG6jSL92oYaUkq?xS!t|J(q2re zmtKmUubv{;+h9|5&O8>$1u7nNMSXO1qFW?ZAQ~{|BT=y_Qn=iVrfJAnXUJ$aJ_Q_H z7-Lx|;6|q>CA+Ozeq8nrg1mh>JY;PSN_ss;nCSQ}q6l3bo+8}sO{Zp+Y6hiwQw2I( z>*>S}s=X2s8Gknt-TR%+d>>{0k6rG%+~V)$l(l4uhPw%oI*w=Wzk7Xh-S^?EoVJQs zkqqInGNRXH7ts7B`XD1CdQS=qPYREI33NW%)j@dx3WQ!-UCo1fdVZf9FuHL$F})L- z%!&1NWMJ>BrBE(R=s{^CmDet!rQJoKsppt`km`M|cQnh$s}z$;SOb$id8g-jDTa&>ukYYIjD;#+I7n9X+Sbm2^oX85&oq@lge$0&{K$T8gsrt722r zUTb}NgD3^1HS}5p5Y4E+a=i{Q#eT04eO#t-k{K*uO@7G(%gZ0fNh_(8MwXc248O@WO z-N%#tQ%#{EM?*lO&kyzB}jO*ta z=X29Mjr=bij& zX&1d^csBC9j|&c{IRoo(m0W$reLHprg1^z$rv<5{=9#4Tp5BZCNMAkm-!gI{H{L9r zj{RuDHOzL%u%b1W_q|}yDKCZ*H7vs!Gs@VPJ7Vdu4rUL(<5IOOfl*Spljim)Q|i)5x?$o=2!k#(l7@pZTC(DS8ep`-H+Ye>O}uV&Vgll5gL@YvP=RivvmWZox43THcLdk~ zAY%QiSEG4R)&BAQP6#I2pZaMHD}A^KgfL{@i9(?B^231t_<3g-Ztx(+AYw+^rn`c> zi)znEILGpfjz0Lu)@j6Kmh`t&w|<-q$aboaVfsbNg2uoV6q8GjjEY6{P2kOJ{R8<#Z`1rD zMBB;i37~Ijs5~XBwtgTp(A%#t=?mJI+@h%T`pKjo_XH;+H=x;Eh}d+3Z=K=J@G`xq z1z|?H3_8dR_g$G<@e+LTP(2uTZq|JZBnZT}vHOFX`}5`Y zh_;N}4iRzT5O+;Ln4vx6H>D)o^8G3;y}abYIq+gUlvxl3&>37n&zX-wgir4Jy3awc z`t~OZzciPnSJ&01mvy-`xcWHxb~QybhM4vH=SRc^evEI7@C#K>_S5|6(zXZgE?GbX zw4h?EoT^W@EtuUX(kecp2liP-A!au1cxzN~J=;6g{0a42dZZN1>FHVBdM9yPYObY* z$wLnGt+A*YBlRneNmSE@OS0Y*P%}c{fKveEh_7xg z`)^u?ipNX*m)H7_#s>5Avoaeh!dn6&-5ZNN49wi)y!?F{E%XhebmI$4WI{!yT?{3X zyj{EYq(*@6iw@Yx^UZlQI=YE)LMC7pYg%BF0H6O)YP{$HNs0>Kd3jv)iFau$vn`<#IPh< zN6SD&JtEK$#N&u*fBUV)O~G3g{KKNM8-5Fd-S^3yA{ed5(}g9a0@;{vle;Fq^uT(H zV_^x>Lz8j9ak}d@ugPhO^M&Y}ickpnF~gwjW0nlYCPt@z%nB?0oJ22MrDq30gNy!F z6qp^>j+A--2A{j~(P-zf25q-}`9u5X-tOdzq^QWsx}t<^_YkZ2(9XE@(9n>Oh|V=IJU-T^dk1m656a+C3?=4;=kjeS?LmMYUnnzGa-TG_^wbC! z4#S{5$0B$`0A-Addw2|TNsj#;y05Yi8?C!w_;cm<+~{hzzGG};L5Nj#UqsdCuqYXm z7OgM~btkXjBF7JA_U4vKWvO(dB}O__;E6MnJ9=-jw7=o0DDJ zuMzkY493#s4p70zfY%Y zoXb0#xe34gIVr7Il+uszta151z9OOG>525O)bSinQqVre7F&79*;DDrp|JTlDy*P2 z5fm*gg>58xEob`qXL%E&(K3Y_o3o0^pq=5uuwKA{pHA&HL9HE+AHtmF4@@T2tYn#& zHFT2NR))9qZhtk_4vI)h7V#4GR(JL@@(s07^9=OU?lh{5u^N`|%9|$|r^eF{ z`=eKmia%6l1x}~-c=?xyCr9gv*ZYLnBpK>>T6zd8JGco58XHAi#6!gn6sYh4HFzoo zFT4)dC_b-4^X^a9Ul%|;>K%_#*HrFoT_(>#o+aiIMizg#p(1zyNBA@!{6d zKUi#6KH3oVUZ?aZ5m=9Y8@qN=_BeN zb)?w57%PcxhgMHUu;~pQ`Nn43T4AXANL2!(RiwiScck$n(#MmtC;n4Swe_teZ)tDB zxovo&vPAq>5s#5k`a0qK#HM=W;_Ny^Pn%uX6l-xQl#|g z@NkDfr))%Q{jZjGPY(vxtCxq1)|xls+l$-#C#-yeKSakTR8B|M6a}>fMJ1S}WTX^Z zM4Koo+a_BC_!igqb=JF0Ll6t4?V-9krLDirwN$03t5B1x8P;GXc*eFYS zEY(-3S4>JMlVntM*}?BjzVpBjL5CdOj=Cx!%hJ%s93AtuB^wjfIN>b%*2PF&CtJ{q z=M8H`xd+GHaUr)Yz1B}Us_D(itNhRxYGY*SVlC=eCZ?zDL+^;fvakV8N2@z4hKY94 z(0|$kPj@>vaEd+GwDt9-y|A{rciqDjtZ-*vf= zYnk-@4x^uBPEh~^HBYYe#c(sOT?@$sakt