diff --git a/SciFiReaders/__version__.py b/SciFiReaders/__version__.py index 7f56255..9777a45 100644 --- a/SciFiReaders/__version__.py +++ b/SciFiReaders/__version__.py @@ -1,2 +1,2 @@ -version = '0.10.0' -time = '2023-07-31 17:00:00' +version = '0.11.0' +time = '2023-11-08 17:00:00' diff --git a/SciFiReaders/readers/microscopy/em/tem/__init__.py b/SciFiReaders/readers/microscopy/em/tem/__init__.py index 59c7905..ccbe8e0 100644 --- a/SciFiReaders/readers/microscopy/em/tem/__init__.py +++ b/SciFiReaders/readers/microscopy/em/tem/__init__.py @@ -13,7 +13,8 @@ from .dm_reader import DMReader, DM3Reader from .nion_reader import NionReader from .emd_reader import EMDReader +from .edax_reader import EDAXReader -__all__ = ['DMReader', 'DM3Reader', 'NionReader', 'EMDReader'] +__all__ = ['DMReader', 'DM3Reader', 'NionReader', 'EMDReader', 'EDAXReader'] -all_readers = [DMReader, DM3Reader, NionReader, EMDReader] \ No newline at end of file +all_readers = [DMReader, DM3Reader, NionReader, EMDReader, EDAXReader] \ No newline at end of file diff --git a/SciFiReaders/readers/microscopy/em/tem/dm_reader.py b/SciFiReaders/readers/microscopy/em/tem/dm_reader.py index 1d12ef4..bbcdbf9 100644 --- a/SciFiReaders/readers/microscopy/em/tem/dm_reader.py +++ b/SciFiReaders/readers/microscopy/em/tem/dm_reader.py @@ -186,6 +186,7 @@ def read(self): print("| parse DM3 file: %.3g s" % (t2 - t1)) if '1' in self.__stored_tags['ImageList']: start=1 + channel_number = 0 for image_number in self.__stored_tags['ImageList'].keys(): print(image_number, start) if int(image_number) >= start: @@ -215,7 +216,8 @@ def read(self): dataset.source = 'SciFiReaders.DMReader' dataset.original_metadata['DM']['full_file_name'] = self.__filename - key = f"Channel_{int(image_number)-start:03d}" + key = f"Channel_{int(channel_number):03d}" + channel_number += 1 self.datasets[key] = dataset self.extract_crucial_metadata(key) diff --git a/SciFiReaders/readers/microscopy/em/tem/edax_reader.py b/SciFiReaders/readers/microscopy/em/tem/edax_reader.py new file mode 100644 index 0000000..18770f8 --- /dev/null +++ b/SciFiReaders/readers/microscopy/em/tem/edax_reader.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python +# -*- coding: iso-8859-1 -*- + +################################################################################ +# Python class for reading EDAX .h5 files into sidpy Dataset +# and extracting all metadata +# +# Written by Gerd Duscher, UTK 2023 +# +# Works for python 3 +# +################################################################################ + +import struct +import h5py +# from warnings import warn +import sys +import numpy as np +import os + +import sidpy + +__all__ = ["EdaxReader", "version"] + +version = '0.1beta' + +debugLevel = 0 # 0=none, 1-3=basic, 4-5=simple, 6-10 verbose + +if sys.version_info.major == 3: + unicode = str + +# ### utility functions ### + + + + +def get_dataset_keys(h5_object): + if not isinstance(h5_object, (h5py.Group, h5py.File)): + raise TypeError('h5_object should be a h5py.File or h5py.Group object') + + dataset_list = [] + h5_object.visit(lambda key : dataset_list.append(key) if isinstance(h5_object[key], h5py.Dataset) else None) + return dataset_list + + +def reader(h5_file): + base_group = h5_file[list(h5_file.keys())[0]] + all_datasets_list = get_dataset_keys(base_group) + + datasets = {} + # read spectum images first + for dataset_item in all_datasets_list: + if 'Live Map' in dataset_item: + if 'SPD' in dataset_item: + dataset = read_spectrum_image(base_group, dataset_item) + +def read_metadata_array(structured_array): + metadata = {} + for i in range(len(structured_array.dtype.fields)): + # print(structured_array.dtype.names[i], structured_array[structured_array.dtype.names[i]]) + metadata[structured_array.dtype.names[i]] = structured_array[structured_array.dtype.names[i]] + return(metadata) + +def read_spectrum_image(base_group, dataset_item): + group_end = dataset_item.index('/SPD') + dataset = sidpy.Dataset.from_array(base_group[dataset_item][:]) + if dataset.ndim !=3: + print('Error reading spectrum image') + dataset.original_metadata = read_metadata_array(base_group[dataset_item[:group_end]+'/SPC'][:]) + dataset.original_metadata.update(dict(base_group[dataset_item].attrs)) + # print(dataset.original_metadata['StartEnergy'], dataset.original_metadata['EndEnergy']) + # print(dataset.shape[2]/dataset.original_metadata['EndEnergy']) + # print(dataset.shape[2]*dataset.original_metadata['evPerChannel'] ) + # print(dataset.original_metadata['MicronPerPixelX'], dataset.original_metadata['MicronPerPixelY']) + + + dataset.title = dataset_item[:group_end].replace('/', '_').replace(' ','')+'_SpectrumImage' + dataset.data_type = 'spectral_image' + dataset.set_dimension(0, sidpy.Dimension(np.arange(dataset.shape[0])* dataset.original_metadata['MicronPerPixelX']*1000, + name='x', units='nm', + quantity='distance', + dimension_type='spatial')) + dataset.set_dimension(1, sidpy.Dimension(np.arange(dataset.shape[1])* dataset.original_metadata['MicronPerPixelY']*1000, + name='y', units='nm', + quantity='distance', + dimension_type='spatial')) + dataset.set_dimension(2, sidpy.Dimension(np.arange(dataset.shape[2])* dataset.original_metadata['evPerChannel'], + name='energy_scale', units='eV', + quantity='energy', + dimension_type='spectral')) + + dataset.units = 'counts' + dataset.quantity = 'intensity' + + # print(dataset.title) + + return dataset + + +def read_image(base_group, dataset_item): + dataset = sidpy.Dataset.from_array(base_group[dataset_item][:]) + + if dataset.ndim !=2: + print('Error reading image') + + if dataset_item[-4:] == '.dat': + dataset.original_metadata = read_metadata_array(base_group[dataset_item[:-3]+'ipr'][:]) + dataset.original_metadata.update(dict(base_group[dataset_item].attrs)) + dataset.title = dataset_item[:-4].replace('/', '_').replace(' ','') + elif 'MAPIMAGE' in dataset_item: + group_end = dataset_item.index('/MAPIMAGE') + dataset.original_metadata = read_metadata_array(base_group[dataset_item[:group_end]+'/MAPIMAGEIPR'][:]) + dataset.original_metadata.update(read_metadata_array(base_group[dataset_item[:group_end]+'/MAPIMAGECOLLECTIONPARAMS'][:])) + dataset.original_metadata.update(dict(base_group[dataset_item].attrs)) + dataset.title = dataset_item[:group_end].replace('/', '_').replace(' ','')+'_Image' + elif 'FOVIMAGE' in dataset_item: + group_end = dataset_item.index('/FOVIMAGE') + dataset.original_metadata = read_metadata_array(base_group[dataset_item[:group_end]+'/FOVIPR'][:]) + dataset.original_metadata.update(read_metadata_array(base_group[dataset_item[:group_end]+'/FOVIMAGECOLLECTIONPARAMS'][:])) + dataset.original_metadata.update(dict(base_group[dataset_item].attrs)) + dataset.title = dataset_item[:group_end].replace('/', '_').replace(' ','')+'_SurveyImage' + + dataset.data_type = 'image' + dataset.units = 'counts' + dataset.quantity = 'intensity' + + dataset.set_dimension(0, sidpy.Dimension(np.arange(dataset.shape[0])* dataset.original_metadata['MicronsPerPixelX']*1000, + name='x', units='nm', + quantity='distance', + dimension_type='spatial')) + dataset.set_dimension(1, sidpy.Dimension(np.arange(dataset.shape[1])* dataset.original_metadata['MicronsPerPixelY']*1000, + name='y', units='nm', + quantity='distance', + dimension_type='spatial')) + return dataset + +class EDAXReader(sidpy.Reader): + + def __init__(self, file_path, verbose=False): + """ + file_path: filepath to dm3 file. + """ + + super().__init__(file_path) + + # initialize variables ## + self.verbose = verbose + self.__filename = file_path + + path, file_name = os.path.split(self.__filename) + self.basename, self.extension = os.path.splitext(file_name) + self.datasets = {} + + if 'h5' in self.extension: + try: + h5_file = h5py.File(self.__filename, mode='r') + if not isinstance(h5_file, h5py.File): + raise TypeError("File {} does not seem to be of EDAX`s .h5 format".format(self.__filename)) + + if len(h5_file.keys()) != 1: + raise TypeError("File {} does not seem to be of EDAX`s .h5 format".format(self.__filename)) + + base_metadata = dict(h5_file[list(h5_file.keys())[0]].attrs) + edax_file = False + if 'Company' in base_metadata: + if base_metadata['Company'] == 'EDAX, LLC': + edax_file =True + if not edax_file: + raise TypeError("File {} does not seem to be of EDAX`s .h5 format".format(self.__filename)) + h5_file.close() + except IOError: + raise IOError("File {} does not seem to be of EDAX's .h5 format".format(self.__filename)) + + def read(self): + if 'h5' in self.extension: + # TODO: use lazy load for large datasets + self.__f = h5py.File(self.__filename, 'r') + base_group = self.__f[list(self.__f.keys())[0]] + all_datasets_list = get_dataset_keys(base_group) + + for dataset_item in all_datasets_list: + if 'Live Map' in dataset_item: + if 'SPD' in dataset_item: + dataset = read_spectrum_image(base_group, dataset_item) + self.datasets[dataset.title] = dataset + group_end = dataset_item.index('/SPD') + self.extract_crucial_metadata(dataset.title) + if dataset_item[:group_end]+'/MAPIMAGE' in all_datasets_list: + image_dataset = read_image(base_group, dataset_item[:group_end]+'/MAPIMAGE') + self.datasets[image_dataset.title] = image_dataset + self.extract_crucial_metadata(image_dataset.title) + parent_group = dataset_item.index('/Live Map') + if dataset_item[:parent_group]+'/FOVIMAGE' in all_datasets_list: + image_dataset = read_image(base_group,dataset_item[:parent_group]+'/FOVIMAGE') + self.datasets[image_dataset.title] = image_dataset + self.extract_crucial_metadata(image_dataset.title) + for item in all_datasets_list: + if dataset_item[:-3]+'ROIs' in item: + if item[-3:] == 'dat': + image_dataset = read_image(base_group,item) + self.datasets[image_dataset.title] = image_dataset + self.extract_crucial_metadata(image_dataset.title) + + self.__f.close() + return self.datasets + + def get_filename(self): + return self.__filename + + filename = property(get_filename) + + + def get_tags(self): + return self.original_metadata + + tags = property(get_tags) + + def get_datasets(self): + return self.datasets + + def extract_crucial_metadata(self, key): + """Read essential parameter from original_metadata originating from a Nion file""" + + original_metadata = self.datasets[key].original_metadata + + if not isinstance(original_metadata, dict): + raise TypeError('We need a dictionary to read') + + experiment = {'stage': {}, + 'detector': {}, + 'microscope': {}, + 'analysis': {}} + + if 'KVolt' in original_metadata: + experiment['microscope']['acceleration_voltage_V'] = original_metadata['KVolt']*100 + if 'KV' in original_metadata: + experiment['microscope']['acceleration_voltage_V'] = original_metadata['KV']*1000 + if 'WorkingDistance' in original_metadata: + experiment['stage']['working_distance'] = original_metadata['WorkingDistance']/1000 + if 'LiveTime' in original_metadata: + experiment['detector']['live_time'] = original_metadata['LiveTime'] + if 'TiltAngle' in original_metadata: + experiment['detector']['tilt_angle'] = original_metadata['TiltAngle'] + if 'TakeOffAngle' in original_metadata: + experiment['detector']['take_off_angle'] = original_metadata['TakeOffAngle'] + if 'DetectorResoultion' in original_metadata: + experiment['detector']['resolution'] = original_metadata['DetectorResoultion'] + if 'AlThickness' in original_metadata: + experiment['detector']['Al_thickness'] = original_metadata['AlThickness'] * 1e-6 # in m + if 'BeWinThickness' in original_metadata: + experiment['detector']['Be_thickness'] = original_metadata['BeWinThickness'] * 1e-6 # in m + if 'ParThickness' in original_metadata: + experiment['detector']['Par_thickness'] = original_metadata['ParThickness'] * 1e-6 # in m + if 'AuThickness' in original_metadata: + experiment['detector']['Au_thickness'] = original_metadata['AuThickness'] * 1e-6 # in m + if 'SiDeadThickness' in original_metadata: + experiment['detector']['Si_dead_layer_thickness'] = original_metadata['SiDeadThickness'] * 1e-6 # in m + if 'SiLiveThickness' in original_metadata: + experiment['detector']['Si_live_thickness'] = original_metadata['SiLiveThickness'] # in m + + if 'XRayIncidenceAngle' in original_metadata: + experiment['detector']['x_ray_incident_angle'] = original_metadata['XRayIncidenceAngle'] + if 'AzimuthAngle' in original_metadata: + experiment['detector']['azimuth_angle'] = original_metadata['AzimuthAngle'] + if 'ElevationAngle' in original_metadata: + experiment['detector']['elevation_angle'] = original_metadata['ElevationAngle'] + if 'BCoefficient' in original_metadata: + experiment['detector']['b_coefficient'] = original_metadata['BCoefficient'] + if 'CCoefficient' in original_metadata: + experiment['detector']['c_coefficient'] = original_metadata['CCoefficient'] + + if 'XTiltAngle' in original_metadata: + experiment['stage']['x_tilt_angle'] = original_metadata['XTiltAngle'] + if 'YTiltAngle' in original_metadata: + experiment['stage']['y_tilt_angle'] = original_metadata['YTiltAngle'] + if 'Tilt' in original_metadata: + experiment['stage']['tilt'] = original_metadata['Tilt']/10 + + if 'AtomicNumberOfPeakIds' in original_metadata: + experiment['analysis']['atomic_numbers'] = original_metadata['AtomicNumberOfPeakIds'] + if 'EnergyOfPeakIds' in original_metadata: + experiment['analysis']['peak_energies'] = original_metadata['EnergyOfPeakIds'] + if 'WeightFraction'in original_metadata: + experiment['analysis']['weight_fractions'] = original_metadata['WeightFraction'] + + self.datasets[key].metadata['experiment'] = experiment + + diff --git a/notebooks/01_using_readers/read_dm3_files.ipynb b/notebooks/01_using_readers/read_dm3_files.ipynb index 21d3dbb..64946a1 100644 --- a/notebooks/01_using_readers/read_dm3_files.ipynb +++ b/notebooks/01_using_readers/read_dm3_files.ipynb @@ -32,17 +32,15 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "You don't have igor2 installed. If you wish to open igor files, you will need to install it (pip install igor2) before attempting.\n", - "You don't have gwyfile installed. If you wish to open .gwy files, you will need to install it (pip install gwyfile) before attempting.\n", - "SciFiReaders version: 0.0.10\n", - "sidpy version: 0.12.0\n" + "SciFiReaders version: 0.0.11\n", + "sidpy version: 0.12.1\n" ] } ], @@ -104,13 +102,13 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "03055b2dca464f1089af6136a5498f8a", + "model_id": "12ab39daffc341bc9d0520252067626e", "version_major": 2, "version_minor": 0 }, @@ -198,7 +196,7 @@ { "data": { "text/plain": [ - "'c:\\\\Users\\\\gduscher\\\\Documents\\\\Github\\\\pyTEMlib\\\\example_data\\\\AL-DFoffset0.00.dm3'" + "'c:\\\\Users\\\\gduscher\\\\Documents\\\\2023-Experiments\\\\09222023eels\\\\gr_LL_0.dm4'" ] }, "execution_count": 4, @@ -264,11 +262,85 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Array Chunk
Bytes 8.00 kiB 8.00 kiB
Shape (2048,) (2048,)
Dask graph 1 chunks in 1 graph layer
Data type float32 numpy.ndarray
\n", + "
\n", + " \n", + "\n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + "\n", + " \n", + " 2048\n", + " 1\n", + "\n", + "
" + ], + "text/plain": [ + "sidpy.Dataset of type SPECTRUM with:\n", + " dask.array\n", + " data contains: intensity (counts)\n", + " and Dimensions: \n", + "energy_loss: energy-loss (eV) of size (2048,)\n", + " with metadata: ['experiment']" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "dataset = datasets[0]\n", + "dataset = datasets['Channel_000']\n", "dataset" ] }, @@ -302,26 +374,29 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 8, "metadata": {}, "outputs": [ { "data": { - "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.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\nmpl.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_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\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\nmpl.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\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\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 'z-index: 2;'\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(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\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;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n '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 /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\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 /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.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 canvas_div.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\nmpl.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\nmpl.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\nmpl.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\nmpl.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\nmpl.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\nmpl.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\nmpl.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\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.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\nmpl.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\nmpl.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.\nmpl.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\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction 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\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * 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 return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.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\nmpl.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\nmpl.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\nvar _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\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar 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\nmpl.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\nmpl.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\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.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\nmpl.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\nmpl.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\nmpl.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\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.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\nmpl.figure.prototype._key_event_extra = function (event, _name) {\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\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.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.\nif (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": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "241d9a283704499b894f22d80ce09bb7", + "version_major": 2, + "version_minor": 0 + }, + "image/png": "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mW699VaNGDFCf/7zn7V06VJJ3F+cO54BxAVzuVyKjY1VWlqaz/bVq1erbdu2NlVVeZmmqXHjxikhIUFLlizRbbfd5vmscePGCg8P97nXBQUFWrNmDff6HA0YMECbN2/Wxo0bPV89e/bUDTfcoI0bN6p58+bc4zIICwtT06ZNPUGk0JYtW9SsWTP+DJdRXl6eTpw4oePHj/tsb9CggQ4fPsz9xXmjA4gyGTJkiBITE9W2bVvFxMRozpw5Sk9P13vvvWd3aZVO//79tXTpUs2fP1/169fX7t27PZ81atRIgwYN0tixY9WwYUOFhYVp5syZkqQ+ffrYVHHlEhERoYiICJ9t4eHhMk1TLVq0kCTucRklJCTo4Ycf1qWXXqpu3bppzZo1+utf/6rXXntNQUFB3N8yCAsL0x133KFHH31UbrdbV1xxhb755hs9//zzeuCBB7i/OH82z0JGJed2u83x48eb9evXN4ODg83WrVubS5YssbusSqlhw4ampBK/TNM0c3JyzEceecSsW7euGRISYnbs2NH86quvbK66cvNeBsY0ucdWmD17ttmqVSvT5XKZzZs3N+fMmeP5jPtbNllZWebjjz9uNmrUyHS5XGaLFi3MGTNmmCdPnjRNk/uL82OY5uklxAEAAOAXeAYQAADAzxAAAQAA/AwBEAAAwM8QAAEAAPwMARAAAMDPEAABAAD8DAEQAADAzxAAAcDL7t27ZRiGNm7caGsdmzdvVmhoqN544w1Lz/vLL78oJiZGY8aMsfS8ACoXAiAAOMzJkyd1zz336P7779eDDz54TsckJiaqfv36KigoKPbZ/v37FRgYqJUrV+riiy/W+++/r+nTp+vzzz+3unQAlQQBEAAcZs6cOfrxxx/13HPPnfMx/fv3108//aTly5cX++ydd95RgwYNdP3110uSrrrqKg0aNEjjxo2zrGYAlQsBEMAFMU1TycnJaty4sUJDQ9W6dWt98MEHns9TU1MVHh6uN998U1dddZVq1qypzp0767vvvvM5z6JFi9SuXTu5XC41btxYSUlJni5W4XDs3//+d916660KCwvTQw89JEn66aefdOeddyo0NFSRkZHq2LGjDMNQZmamjh49qpo1a2rOnDk+13rnnXcUHh6u48ePn9fv+sUXX+jaa6+Vy+VSvXr19OSTT+rkyZOezz///HN17txZtWrV0sUXX6zbb79dO3bskCTt27dPd911l6KiohQeHq5rr71WH3/88VmvN2vWLN17772qW7euz/asrCwNGTJEUVFRCgsLU9euXbV582ZJUvPmzdW+fXu9/fbbxc73zjvv6P7775dhGJ5tf/nLX7RixQqlp6ef170AUDUQAAFckKefflqvvfaaZs6cqfXr12vo0KG6++679fXXX3v2yc7O1vz58zVz5kx99tlnysnJ0SOPPOL5/JNPPtE999yjoUOH6uuvv9ZLL72kV155RS+//LLPtaZMmaKHHnpIX3/9tcaMGaP8/Hx17dpVubm5WrlypZYuXap27dp59q9Vq5b69u2r1157zec8r7/+uu677z7VqFHjnH/Pn3/+Wd27d9dVV12lNWvWKCUlRXPnztXTTz8tSTp8+LBuvfVWXXfddfrqq6/09ttvKzo6Wt9++60kqW/fvvrtt9+0dOlSLVmyRLfddpvWrVtX6vUOHjyoDRs2qG/fvj7bTdPUbbfdpj179ujf//630tLS1KRJE916662eQNu/f38tWLBAJ06c8By3adMmbd68Wffff7/P+Vq3bq0WLVpo8eLF53wvAFQhJgCcp2PHjpkul8tctWqVz/abb77ZHDVqlGmapvmPf/zDDA8P9/k8JSXFDAsL8/z8hz/8wUxKSvLZZ8qUKWa7du1M0zTNXbt2mZLMdevW+ewze/ZsMzw83MzOzvZsW7FihSnJPHLkiGmapvnll1+akswdO3aYpmma3377rWkYhrlp06az/m6F19ywYYNpmqb59NNPm61atTLdbrdnn3/9619mSEiIeeLECXPt2rWmJHPz5s0lnq9GjRrmyy+/fNZreiv8PY4ePeqzffny5WatWrXM48ePe7bl5+ebNWrUMD/66CPTNE0zIyPDrFatmvn222979hk1apTZvn37Eq911113mQMHDjzn2gBUHXQAAZy3bdu2KScnR926dZPL5fJ8LVu2THv27Cn1uKioKGVnZ3t+Xr9+vSZNmuRzjnHjxhU7R2BgoM/P69atU5s2bVSrVq1Sr9W+fXtdeeWVev311yWd6v61b99erVu3liQNHjzY57qDBw8u8Txbt27VNddc4zN8es011yg3N1fff/+9WrdurbZt26pTp07q06ePXnjhBe3cudOz78CBAzVy5Eh169ZN48eP1xdffFFqzZJ06NAh1a5dWzVr1vTZvn79eh07dkx16tTx1FyzZk0dP37cc7/q1Kmj22+/3TMM7Ha7NW/ePPXv37/Ea0VHR+vgwYNnrQdA1RRkdwEAKh+32y1J+uijjxQdHe3z2dlCmXeIKjzP+PHj1bt3b5/tZwa+MwUEBMjlcv1unYMHD9bkyZM1YcIEvfnmm5o6darns0mTJmnUqFGen8PDw3/3fCWpVq2avvzyS/3nP//RZ599pjlz5mjMmDFasmSJbrjhBs2aNUv33HOPli5dqtWrV2vy5MlKSkrS2LFjSzyfaZol/v5ut1sXX3yxVqxYUeyziy++2PN9//791bt3b/3yyy/avHmzfv3112LDyd7nPPP/EwD+gQAI4Ly1bNlSwcHB2rNnj2666aYLPk+bNm2Unp6uFi1anNdxMTExWrhwoUzTPGuAue+++5SQkKAhQ4YoNzdXf/rTnzyf1atXT/Xq1fvda8XGxmrBggU+11qzZo1CQkLUpEkTnTx5UtWqVVOvXr3Uq1cvSVJ8fLw++OADdenSRaZpqlOnTurUqZMk6YknntC8efNKDYAXXXSRMjIy9Ntvvyk0NNSzvU2bNvrll18UGBiopk2bllpvjx49VKdOHc2bN08bNmzQbbfdpjp16pS47759+xQVFfW79wBA1cMQMIDzFhYWphEjRmjUqFF65ZVXtHXrVq1bt06TJk3SzJkzz/k8iYmJmjt3rp544gmtX79eW7duVUpKioYOHXrW4/r166eDBw9qzJgx2rJliz788EM9/vjjxfaLiIhQnz59PLNgq1evft6/61/+8hft2bNHw4cP16ZNm/TRRx9p1KhRevTRRxUaGqqPP/5Yt99+u5YsWaLvvvtO//nPf7Rr1y7Fx8crKytLrVu31ttvv60dO3boyy+/1KpVqxQfH1/q9Vq1aiXDMLRlyxaf7d27d9fVV1+tO+64QwsWLNCOHTv0v//9T4MHD9aqVas8+wUHB6tv376aPXu2FixYUOrwr3RqgkjhkDgAP2PvI4gAKquCggJzxowZZosWLczg4GDzoosuMnv06GGuXr3aNM2SJ4EsWLDAPPM/O4sWLTKvvfZaMzQ01AwLCzM7dOhgvvnmm6ZpFp+Q4e3DDz80GzdubAYHB5txcXFmYmKiKck8ceJEsfPrLJM0zlTSNdPS0swOHTqY1apVMy+++GLziSeeMHNzc03TNM3du3eb/fr1My+77DLT5XKZTZo0MZ9//nnTNE0zLy/PfPzxx83mzZuboaGhZr169cyhQ4eaWVlZZ62hXbt2nsk03jIzM82//OUvZv369c3g4GDzsssuMwcMGGAeOHDAZ79169aZkszIyEgzLy+vxGsUTl5JT08/p/sCoGoxTNM0bcyfAGCJt99+W6NHj9ZPP/3ks33UqFFas2aN0tLSbKrs/KWmpiohIUHff/+9wsLCyuUa999/vw4cOFDiwtEAqj6GgAFUSikpKVq+fLm2b9+uTz75RJMmTdKAAQN89jlx4oRmz579u0PKTnP//ffr4osvVlJSUrmc/4svvtC8efM0efLkcjk/AOdjEgiASmnVqlUaO3asjh07pkaNGql///568sknffZ5++23ZRiG7rrrLpuqvDCBgYGaO3euOnTooPbt26tPnz6WnXvfvn3q3bu3Ro8erY4dO1p2XgCVC0PAAAAAfoYhYAAAAD9DAAQAAPAzBEAAAAA/QwAEAADwMwRAAAAAP0MABAAA8DMEQAAAAD9DAAQAAPAzBEAAAAA/8/8BO29pHGRHGkEAAAAASUVORK5CYII=", "text/html": [ - "" + "\n", + "
\n", + "
\n", + " Figure\n", + "
\n", + " \n", + "
\n", + " " ], "text/plain": [ - "" + "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …" ] }, "metadata": {}, @@ -715,7 +790,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.11.6" }, "toc": { "base_numbering": 1,