{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# LogGabor user guide\n", "\n", "\n", "Table of content\n", "----------------\n", "\n", "* [What is the LogGabor package?](LogGabor.ipynb#What-is-the-LogGabor-package?) \n", "\n", "* [Installing](LogGabor.ipynb#Install) \n", "\n", "* [Importing the library](LogGabor.ipynb#Importing-the-library)\n", "\n", "* [Properties of log-Gabor filters](LogGabor.ipynb#Properties-of-log-Gabor-filters)\n", "\n", "* [Testing filter generation](LogGabor_testing-filter-generation.ipynb) \n", "\n", "* [Testing on a sample image](LogGabor.ipynb#Testing-on-a-sample-image) \n", "\n", "* [Building a pyramid](LogGabor_making-a-pyramid.ipynb) \n", "\n", "* [An example of fitting images with log-Gabor filters](LogGabor_fit-example.ipynb) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What is the LogGabor package?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> Meanwhile biorthogonal wavelets got a very popular image processing tool, alternative multiresolution transforms have been proposed for solving some of their drawbacks, namely the poor selectivity in orientation and the lack of translation invariance due to the aliasing between subbands. These transforms are generally overcomplete and consequently offer huge degrees of freedom in their design. At the same time their optimization get a challenging task. We proposed here a log-Gabor wavelet transform gathering the excellent mathematical properties of the Gabor functions with a carefully construction to maintain the properties of the filters and to permit exact reconstruction. Two major improvements are proposed: first the highest frequency bands are covered by narrowly localized oriented filters. And second, all the frequency bands including the highest and lowest frequencies are uniformly covered so as exact reconstruction is achieved using the same filters in both the direct and the inverse transforms (which means that the transform is self-invertible). The transform is optimized not only mathematically but it also follows as much as possible the knowledge on the receptive field of the simple cells of the Primary Visual Cortex (V1) of primates and on the statistics of natural images. Compared to the state of the art, the log-Gabor wavelets show excellent behavior in their ability to segregate the image information (e.g. the contrast edges) from incoherent Gaussian noise by hard thresholding and to code the image features through a reduced set of coefficients with large magnitude. Such characteristics make the transform a promising tool for general image processing tasks.\n", "\n", "\n", "Code: https://github.com/bicv/LogGabor\n", "\n", "Reference (BibTex Format):\n", "\n", "~~~~{.bibtex}\n", " @article{Fischer07cv,\n", " Author = {Fischer, Sylvain and Sroubek, Filip and Perrinet, Laurent U. and Redondo, Rafael and Crist{\\'o}bal, Gabriel},\n", " Journal = {Int. Journal of Computional Vision},\n", " Keywords = {wavelet transforms, log-Gabor filters, oriented high-pass filters, image denoising, visual system},\n", " Title = {Self-invertible 2{D} log-{G}abor wavelets},\n", " Url = {https://laurentperrinet.github.io/publication/fischer-07-cv},\n", " Year = {2007}}\n", "~~~~" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\"The\n", "**The log-Gabor transform compared to other multiresolution schemes.** *a.* Schematic contours of the log-Gabor filters implented in [Fischer (2007)](https://laurentperrinet.github.io/publication/fischer-07-cv) in the Fourier domain with 5 scales and 8 orientations (only the contours at 78% of the filter maximum are drawn). *b.* The real part of the corresponding filters is drawn in the spatial domain. The two first scales are drawn at the bottom magnified by a factor of 4 for a better visualization. The different scales are arranged in lines and the orientations in columns. The low-pass filter is drawn in the upper-left part. *c.* The corresponding imaginary parts of the filters are shown in the same arrangement. Note that the low-pass filter does not have imaginary part. Insets (b) and (c) show the final filters built through all the processes described in Section II. *d.* In the proposed scheme the elongation of log-Gabor wavelets increases with the number of orientations nt . Here the real parts (left column) and imaginary parts (right column) are drawn for the 3, 4, 6, 8, 10, 12 and 16 orientation schemes. *e.* As a comparison orthogonal wavelet filters ’Db4’ are shown. Horizontal, vertical and diagonal wavelets are arranged on columns (low-pass on top). *f.* As a second comparison, steerable pyramid filters [30] are shown. The arrangement over scales and orientations is the same as for the log-Gabor scheme." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\"The\n", "**The Golden Laplacian Pyramid with log-Gabor filters.** To represent the edges of the image at different levels and orientations, we use a multi-scale approach constructing a set of filters of different scales and according to oriented log-Gabor filters. This is represented here by stacking images on a Golden Rectangle [Perrinet (2008)](https://laurentperrinet.github.io/publication/perrinet-08-spie/), that is where the aspect ratio is the golden section $\\phi = \\frac{1+\\sqrt{5}}{2}$. The level represents coefficients' amplitude, hue corresponds to orientation. We present here the base image on the left and the successive levels of the pyramid in a clockwise fashion (for clarity, we stopped at level $8$). Note that here we also use $\\phi^2$ (that is $\\phi+1$) as the down-scaling factor so that the pixelwise resolution of the pyramid images correspond across scales." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Back to the LogGabor user guide](LogGabor.ipynb#LogGabor-user-guide)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Install\n", "\n", "To install them, use pip:\n", "\n", " pip3 install LogGabor\n", "\n", "This will install the necessary requirements :\n", "\n", "* numpy\n", "* NeuroTools\n", "* SLIP\n", "* matplotlib\n", "\n", "More info and the whole source code may be found @ https://github.com/bicv/LogGabor.\n", "\n", "### Installing notebook dependencies\n", "\n", "To run this notebook, you obviously need jupyter (aka ipython), but also https://github.com/ioam/holoviews for the nice visualization. In short, you need to\n", "\n", "\n", "```\n", "pip3 install -r requirements.txt\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Back to top](#LogGabor-user-guide)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Importing the library" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:10.901005Z", "start_time": "2018-06-20T12:47:09.556712Z" } }, "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", "from LogGabor import LogGabor\n", "parameterfile = 'https://raw.githubusercontent.com/bicv/LogGabor/master/default_param.py'\n", "lg = LogGabor(parameterfile)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:10.928195Z", "start_time": "2018-06-20T12:47:10.902804Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on class LogGabor in module LogGabor.LogGabor:\n", "\n", "class LogGabor(SLIP.SLIP.Image)\n", " | Defines a LogGabor framework by defining a ``loggabor`` function which return the envelope of a log-Gabor filter.\n", " | \n", " | Its envelope is equivalent to a log-normal probability distribution on the frequency axis, and von-mises on the radial axis.\n", " | \n", " | Method resolution order:\n", " | LogGabor\n", " | SLIP.SLIP.Image\n", " | builtins.object\n", " | \n", " | Methods defined here:\n", " | \n", " | __init__(self, pe)\n", " | Initializes the Image class\n", " | \n", " | May take as input:\n", " | \n", " | - a dictionary containing parameters\n", " | - a ``ndarray`` (dimensions ``N_X`` and ``N_Y`` are guessed from this array)\n", " | - a string representing a file or URL pointing to an image file\n", " | - a string pointing to a file or URL containing a dictionary of parameters (or simply the name of the file)\n", " | - a ``NeuroTools.parameters.ParameterSet`` object containing parameters\n", " | \n", " | Parameters are\n", " | \n", " | - N_X and N_Y which are respectively the number of pixels in the vertical and horizontal dimensions respectively (MANDATORY)\n", " | - optional parameters which are used in the various functions such as N_image when handling a database or the whitening parameters.\n", " | \n", " | argmax(self, C)\n", " | Returns the ArgMax from C by returning the\n", " | (x_pos, y_pos, theta, scale) tuple\n", " | \n", " | >>> C = np.random.randn(10, 10, 5, 4)\n", " | >>> x_pos, y_pos, theta, scale = mp.argmax(C)\n", " | >>> C[x_pos][y_pos][theta][scale] = C.max()\n", " | \n", " | band(self, sf_0, B_sf, force=False)\n", " | Returns the radial frequency envelope:\n", " | \n", " | Selects a preferred spatial frequency ``sf_0`` and a bandwidth ``B_sf``.\n", " | \n", " | golden_pyramid(self, z, mask=False, spiral=True, fig_width=13)\n", " | The Golden Laplacian Pyramid.\n", " | To represent the edges of the image at different levels, we may use a simple recursive approach constructing progressively a set of images of decreasing sizes, from a base to the summit of a pyramid. Using simple down-scaling and up-scaling operators we may approximate well a Laplacian operator. This is represented here by stacking images on a Golden Rectangle, that is where the aspect ratio is the golden section $\\phi \\eqdef \f", "rac{1+\\sqrt{5}}{2}$. We present here the base image on the left and the successive levels of the pyramid in a clockwise fashion (for clarity, we stopped at level $8$). Note that here we also use $\\phi^2$ (that is $\\phi+1$) as the down-scaling factor so that the resolution of the pyramid images correspond across scales. Note at last that coefficient are very kurtotic: most are near zero, the distribution of coefficients has long tails.\n", " | \n", " | init(self)\n", " | Initializes different convenient matrices for image processing.\n", " | \n", " | To be called when keeping the same Image object but changing the size of the image.\n", " | \n", " | linear_pyramid(self, image)\n", " | \n", " | loggabor(self, x_pos, y_pos, sf_0, B_sf, theta, B_theta, preprocess=True)\n", " | Returns the envelope of a LogGabor\n", " | \n", " | Note that the convention for coordinates follows that of matrices: the origin is at the top left of the image, and coordinates are first the rows (vertical axis, going down) then the columns (horizontal axis, going right).\n", " | \n", " | loggabor_image(self, x_pos, y_pos, theta, sf_0, phase, B_sf, B_theta)\n", " | \n", " | orientation(self, theta, B_theta, force=False)\n", " | Returns the orientation envelope:\n", " | We use a von-Mises distribution on the orientation:\n", " | - mean orientation is ``theta`` (in radians),\n", " | - ``B_theta`` is the bandwidth (in radians). It is equal to the standard deviation of the Gaussian\n", " | envelope which approximate the distribution for low bandwidths. The Half-Width at Half Height is\n", " | given by approximately np.sqrt(2*B_theta_**2*np.log(2)).\n", " | \n", " | # selecting one direction, theta is the mean direction, B_theta the spread\n", " | # we use a von-mises distribution on the orientation\n", " | # see http://en.wikipedia.org/wiki/Von_Mises_distribution\n", " | \n", " | show_loggabor(self, u, v, sf_0, B_sf, theta, B_theta, title='', phase=0.0)\n", " | \n", " | ----------------------------------------------------------------------\n", " | Methods inherited from SLIP.SLIP.Image:\n", " | \n", " | FTfilter(self, image, FT_filter, full=False)\n", " | Using the ``FTfilter`` function, it is easy to filter an image with a filter defined in Fourier space.\n", " | \n", " | dewhitening(self, white, preprocess=True, center=True, use_max=True)\n", " | Returns the dewhitened image\n", " | \n", " | enveloppe_color(self, alpha)\n", " | \n", " | extract_patches_2d(self, image, patch_size, N_patches)\n", " | Reshape a 2D image into a collection of patches\n", " | \n", " | redundant with self.patch, but similar call as\n", " | https://github.com/scikit-learn/scikit-learn/blob/14031f6/sklearn/feature_extraction/image.py#L300\n", " | \n", " | fourier(self, image, full=True)\n", " | Using the ``fourierr`` function, it is easy to retieve its Fourier transformation.\n", " | \n", " | fourier_grid(self)\n", " | use that function to define a reference frame for envelopes in Fourier space.\n", " | In general, it is more efficient to define dimensions as powers of 2.\n", " | \n", " | frequency_angle(self)\n", " | \n", " | frequency_radius(self)\n", " | \n", " | full_url(self, name_database)\n", " | \n", " | get_imagelist(self, exp, name_database='natural')\n", " | returns an imagelist from a pickled database.\n", " | \n", " | If the stored imagelist does not exist, creates it.\n", " | The ``exp`` string allows to tag the list for a particular experiment.\n", " | \n", " | get_pe(self, pe)\n", " | Guesses the parameters from the init variable\n", " | \n", " | We perform a duck-typing to guess parameters from different possible sources.\n", " | outputs a ParameterSet\n", " | \n", " | get_size(self, im)\n", " | \n", " | hist_radial_frequency(self, FT, N_f=20)\n", " | A simple function to compute a radial histogram in different spatial frequency bands.\n", " | \n", " | imread(self, URL, resize=True)\n", " | \n", " | imshow(self, image, fig=None, ax=None, cmap=, axis=False, norm=True, center=True, xlabel='Y axis', ylabel='X axis', figsize=None, mask=False, vmin=-1, vmax=1)\n", " | Plotting routine to show an image\n", " | \n", " | Place the [0,0] index of the array in the upper left corner of the axes.\n", " | Data limits for the axes. The default assigns zero-based row, column\n", " | indices to the x, y centers of the pixels.\n", " | Note that the convention for coordinates follows that of matrices: the\n", " | origin is at the top left of the image, and coordinates are first the\n", " | rows (vertical axis, going down) then the columns (horizontal axis,\n", " | going right).\n", " | \n", " | init_logging(self, filename='debug.log', name='SLIP')\n", " | \n", " | invert(self, FT_image, full=False)\n", " | # Fourier number crunching\n", " | \n", " | list_database(self, name_database)\n", " | Returns a list of the files in a folder\n", " | \n", " | load_in_database(self, name_database, i_image=None, filename=None, verbose=True)\n", " | Loads a random image from the database ``name_database``.\n", " | \n", " | The strategy is to pick one image in the folder using the list provided by the ``list_database``function.\n", " | \n", " | TODO: it would be useful to be able to load from standard databases such as http://www.cps.utexas.edu/natural_scenes/db.shtml\n", " | \n", " | low_pass(self, f_0, steepness)\n", " | Returns the low_pass filter used by (Olshausen, 98)\n", " | \n", " | parameters from Atick (p.240)\n", " | f_0 = 22 c/deg in primates: the full image is approx 45 deg\n", " | alpha makes the aspect change (1=diamond on the vert and hor, 2 = anisotropic)\n", " | \n", " | from Olshausen 98 (p.11):\n", " | f_0 = 200 cycles / image (512 x 512 images)\n", " | in absolute coordinates : f_0 = 200 / 512 / 2\n", " | \n", " | steepness is to produce a \"fairly sharp cutoff\"\n", " | \n", " | make_imagelist(self, name_database, verbose=False)\n", " | Makes a list of images with no repetition.\n", " | \n", " | Takes as an input the name of a database (the name of a folder in the ``datapath``),\n", " | returns a list of the filenames along with the crop area.\n", " | \n", " | mkdir(self)\n", " | Initializes two folders for storing intermediate matrices and images.\n", " | \n", " | To be called before any operation to store or retrieve a result or figure.\n", " | \n", " | normalize(self, image, center=True, use_max=True)\n", " | \n", " | olshausen_whitening_filt(self)\n", " | Returns the whitening filter used by (Olshausen, 98)\n", " | \n", " | patch(self, name_database, i_image=None, filename=None, croparea=None, threshold=0.2, verbose=True, preprocess=True, center=True, use_max=True, do_whitening=False)\n", " | takes a subimage of size s (a tuple)\n", " | \n", " | does not accept if energy is relatively below a threshold (flat image)\n", " | \n", " | pipeline(self, image, preprocess=True, center=True, use_max=True, do_whitening=False)\n", " | pre-processing pipeline\n", " | \n", " | power_spectrum(self, image)\n", " | \n", " | preprocess(self, image)\n", " | Returns the pre-processed image\n", " | \n", " | From raw pixelized images, we want to keep information that is relevent to the content of\n", " | the objects in the image. In particular, we want to avoid:\n", " | \n", " | - information that would not be uniformly distributed when rotating the image. In\n", " | particular, we discard information outside the unit disk in Fourier space, in particular\n", " | above the Nyquist frequency,\n", " | - information that relates to information of the order the size of the image. This\n", " | involves discarding information at low-level frequencies.\n", " | \n", " | See https://laurentperrinet.github.io/sciblog/posts/2015-05-21-a-simple-pre-processing-filter-for-image-processing.htmll\n", " | for more information.\n", " | \n", " | retina(self, df=0.07, sigma=0.5)\n", " | A parametric description of the envelope of retinal processsing.\n", " | See https://laurentperrinet.github.io/sciblog/posts/2015-05-21-a-simple-pre-processing-filter-for-image-processing.htmll\n", " | for more information.\n", " | \n", " | In digital images, some of the energy in Fourier space is concentrated outside the\n", " | disk corresponding to the Nyquist frequency. Let's design a filter with:\n", " | \n", " | - a sharp cut-off for radial frequencies higher than the Nyquist frequency,\n", " | - times a smooth but sharp transition (implemented with a decaying exponential),\n", " | - times a high-pass filter designed by one minus a gaussian blur.\n", " | \n", " | This filter is rotation invariant.\n", " | \n", " | Note that this filter is defined by two parameters:\n", " | - one for scaling the smoothness of the transition in the high-frequency range,\n", " | - one for the characteristic length of the high-pass filter.\n", " | \n", " | The first is defined relative to the Nyquist frequency (in absolute values) while the second\n", " | is relative to the size of the image in pixels and is given in number of pixels.\n", " | \n", " | savefig(self, fig, fname, figpath='', formats=None, display=True)\n", " | \n", " | set_size(self, im)\n", " | Re-initializes the Image class with the size given in ``im``\n", " | \n", " | May take as input:\n", " | \n", " | - a numpy array,\n", " | - a string representing a file or URL pointing to an image file\n", " | - a tuple\n", " | \n", " | Updated parameters are\n", " | \n", " | - N_X and N_Y which are respectively the number of pixels in the vertical and horizontal dimensions respectively (MANDATORY)\n", " | \n", " | show_FT(self, FT_image, fig=None, figsize=None, a1=None, a2=None, axis=False, title=True, FT_title='Spectrum', im_title='Image', norm=True, vmin=-1.0, vmax=1.0)\n", " | \n", " | show_image_FT(self, image, FT_image, fig=None, figsize=None, a1=None, a2=None, axis=False, title=True, FT_title='Spectrum', im_title='Image', norm=True, vmin=-1.0, vmax=1.0)\n", " | \n", " | show_spectrum(self, image, fig=None, figsize=None, a1=None, a2=None, axis=False, title=True, FT_title='Spectrum', im_title='Image', norm=True, vmin=-1.0, vmax=1.0)\n", " | \n", " | trans(self, u, v)\n", " | \n", " | translate(self, image, vec, preshift=True)\n", " | Translate image by vec (in pixels)\n", " | \n", " | Note that the convention for coordinates follows that of matrices: the origin is at the top left of the image, and coordinates are first the rows (vertical axis, going down) then the columns (horizontal axis, going right).\n", " | \n", " | whitening(self, image)\n", " | Returns the whitened image\n", " | \n", " | whitening_filt(self, recompute=False)\n", " | Returns the envelope of the whitening filter.\n", " | \n", " | if we chose one based on structural assumptions (``struct=True``)\n", " | then we return a 1/f spectrum based on the assumption that the structure of images\n", " | is self-similar and thus that the Fourier spectrum scales a priori in 1/f.\n", " | \n", " | elif we chose to learn,\n", " | returns theaverage correlation filter in FT space.\n", " | \n", " | Computes the average power spectrum = FT of cross-correlation, the mean decorrelation\n", " | is given for instance by (Attick, 92).\n", " | \n", " | else\n", " | we return the parametrization based on Olshausen, 1996\n", " | \n", " | ----------------------------------------------------------------------\n", " | Data descriptors inherited from SLIP.SLIP.Image:\n", " | \n", " | __dict__\n", " | dictionary for instance variables (if defined)\n", " | \n", " | __weakref__\n", " | list of weak references to the object (if defined)\n", "\n" ] } ], "source": [ "help(LogGabor)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To install the dependencies related to running this notebook, see [Installing notebook dependencies](#Installing-notebook-dependencies)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Back to top](#LogGabor-user-guide)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Properties of log-Gabor filters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Gabor filters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An important task in image processing and related areas (such as understanding principles of vision) involves defining a dictionary of templates (or\n", "filters) for detecting edges. This representation should give a generic model of edges parameterized by their shape, orientation, and scale. Moreover the range of these parameters should match with what has been reported for simple-cell responses in macaque primary visual cortex (V1). As such, standard Gabors are classically used as they are well fitted to V1 simple cells (Daugman, 1980).\n", "\n", "Firstly proposed by Dennis Gabor in 1946, the canonical coherent states of the Gabor filters are different versions of a Gaussian-shaped window shifted in time/space and frequency variables. Subsequently Morlet brought them into the wavelet multi-resolution framework and they are called Gabor wavelets as well. Belonging to the large Cohen's class, Gabor's work synthesizes the studies of Nyquist in Communication Theory in 1924 and Heisenberg in Quantum Mechanics in 1927, by which he proposed the Gaussian shape as an optimal envelope for time-frequency representation because it turns the uncertainly principle from inequality into equality.\n", "\n", "Some important characteristics of Gabor wavelets are: \n", "\n", "1. the Gabor transform is obtained through a linear convolution, \n", "1. it may enable complete image recovery, \n", "1. the transform domain contains fully the energy of the signal (Parseval's theorem), 1. it is not orthogonal but an unconditional basis, a frame, \n", "1. the Fourier plane is symmetric and \n", "1. it is invariant to shifting in time/space, frequency and scale. \n", "1. Gabor filters are completely symmetric of both sides (isotropy), monomodal and centered (localization) and smooth and infinitely derivable (regularity). \n", "1. In the case of bidimensional signals (images), the scaled versions even can be rotated. The result is the partition of the Fourier plane into bands modulated in frequency and orientation which discriminate spectral features in multiples directions. In contrast, (bi-)orthogonal wavelets often have well-known difficulties in implementing more than three orientations (horizontal, vertical and diagonal). \n", "1. In addition, the Gaussian envelop is modulated by a complex exponential with odd and even phases, which is effective for analyzing features with different phases as abrupt impulses or steps, i.e. ridges and edges in 2D respectively. The filter results of a symmetric and an antisymmetric filter can be combined in a single complex number whose squared amplitude is called the Gabor-energy. This activity relates to the response of so-called complex cells in the primary visual cortex (see H. Spitzer and S. Hochstein. A complex-cell receptive-field model. *Journal of Neuroscience, 53(5):1266–1286, 1985*).\n", "\n", "See https://en.wikipedia.org/wiki/Gabor_filter for more information.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### log-Gabor filters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Gabor filters are a traditional choice for obtaining localised frequency information. It is worth stressing here other important aspects in that peculiar shape. Despite its Gaussian shape spreads to infinite, it decays rapidly from its center. They offer the best simultaneous localization of spatial and frequency information. However they have two main limitations. The maximum bandwidth of a Gabor filter is limited to approximately one octave and Gabor filters are not optimal if one is seeking broad spectral information with maximal spatial localization.\n", "\n", "\n", "\n", "\n", "These limitations are well illustrated by focusing on the DC component. Indeed, one cannot construct Gabor functions of arbitrarily wide bandwidth and still maintain a reasonably small DC component in the even-symmetric filter. This difficulty can be seen if we look at the transfer function of an even-symmetric Gabor filter in the frequency domain. The transfer function is the sum of two Gaussians centred at plus and minus the centre frequency. If the standard deviation of these Gaussians becomes more than about one third of the centre frequency the tails of the two Gaussians will start to overlap excessively at the origin, resulting in a nonzero DC component.\n", "Transfer function of a high bandwidth even-symmetric Gabor filter. The two Gaussians that make up the function overlap at the origin, resulting in a significant DC component.\n", "\n", "An alternative to the Gabor function is the Log-Gabor function proposed by Field [1987]. Log-Gabor filters can be constructed with arbitrary bandwidth and the bandwidth can be optimised to produce a filter with minimal spatial extent. \n", "We use a log-Gabor representation, which is well suited to represent\n", "a wide range of natural images~\\citep{Fischer07}.\n", "Like Gabors, these filters are defined by Gaussians in Fourier space,\n", "but their specificity is that log-Gabors have Gaussians envelopes in log-polar frequency space.\n", "This is consistent with physiological measurements which indicate that V1 cell responses are symmetric on the log frequency scale.\n", "They have multiple advantages over Gaussians:\n", "In particular, they have no DC component,\n", "and more generally, their envelopes more broadly cover the frequency space~\\citep{Fischer07cv}.\n", "In this chapter, we set the bandwidth of the Fourier representation of the filters\n", "to $1$ and $\\pi/8$ respectively in the log-frequency and polar coordinates\n", "to get a family of relatively elongated (and thus selective) filters\n", "(see~\\citet{Fischer07cv} and sigure above for examples of such edges).\n", "Prior to the analysis of each image, we used the spectral whitening filter\n", "described by~\\citet{Olshausen97} to provide\n", "a good balance of the energy of output coefficients~\\citep{Perrinet03ieee,Fischer07}.\n", "Such a representation is implemented in this package.\n", "\n", "\n", "\n", "\n", "\n", "\n", " * see this excellent presentation of log-Gabor filters: http://www.csse.uwa.edu.au/~pk/research/matlabfns/PhaseCongruency/Docs/convexpl.html\n", " * see more articles using Log-Gabor filters: http://www.citeulike.org/tag/log-gabor" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "What do log Gabor functions look like in the spatial domain? Unfortunately due to the singularity in the log function at the origin one cannot construct an analytic expression for the shape of the log Gabor function in the spatial domain. One is reduced to designing the filters in the frequency domain and then performing a numerical inverse Fourier Transform to see what they look like. Their appearance is similar to Gabor functions though their shape becomes much \"sharper\" as the bandwidth is increased. The shapes of log Gabor and Gabor functions are almost identical for bandwidths less than one octave. \t\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Back to top](#LogGabor-user-guide)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## testing coordinates in Fourier space" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:12.087798Z", "start_time": "2018-06-20T12:47:10.930312Z" } }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "
\n", "\n", "\n", "\n", "\n", "\n", " \n", " \n", "\n", "\n", "
\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "function HoloViewsWidget() {\n", "}\n", "\n", "HoloViewsWidget.prototype.init_slider = function(init_val){\n", " if(this.load_json) {\n", " this.from_json()\n", " } else {\n", " this.update_cache();\n", " }\n", "}\n", "\n", "HoloViewsWidget.prototype.populate_cache = function(idx){\n", " this.cache[idx].innerHTML = this.frames[idx];\n", " if (this.embed) {\n", " delete this.frames[idx];\n", " }\n", "}\n", "\n", "HoloViewsWidget.prototype.process_error = function(msg){\n", "}\n", "\n", "HoloViewsWidget.prototype.from_json = function() {\n", " var data_url = this.json_path + this.id + '.json';\n", " $.getJSON(data_url, $.proxy(function(json_data) {\n", " this.frames = json_data;\n", " this.update_cache();\n", " this.update(0);\n", " }, this));\n", "}\n", "\n", "HoloViewsWidget.prototype.dynamic_update = function(current){\n", " if (current === undefined) {\n", " return\n", " }\n", " this.current = current;\n", " if (this.comm) {\n", " var msg = {comm_id: this.id+'_client', content: current}\n", " this.comm.send(msg);\n", " }\n", "}\n", "\n", "HoloViewsWidget.prototype.update_cache = function(force){\n", " var frame_len = Object.keys(this.frames).length;\n", " for (var i=0; i 0) {\n", " that.time = Date.now();\n", " that.dynamic_update(that.queue[that.queue.length-1]);\n", " that.queue = [];\n", " } else {\n", " that.wait = false;\n", " }\n", " if ((msg.msg_type == \"Ready\") && msg.content) {\n", " console.log(\"Python callback returned following output:\", msg.content);\n", " } else if (msg.msg_type == \"Error\") {\n", " console.log(\"Python failed with the following traceback:\", msg['traceback'])\n", " }\n", " }\n", " var comm = HoloViews.comm_manager.get_client_comm(this.plot_id, this.id+'_client', ack_callback);\n", " return comm\n", " }\n", "}\n", "\n", "HoloViewsWidget.prototype.process_msg = function(msg) {\n", "}\n", "\n", "function SelectionWidget(frames, id, slider_ids, keyMap, dim_vals, notFound, load_json, mode, cached, json_path, dynamic, plot_id){\n", " this.frames = frames;\n", " this.id = id;\n", " this.plot_id = plot_id;\n", " this.slider_ids = slider_ids;\n", " this.keyMap = keyMap\n", " this.current_frame = 0;\n", " this.current_vals = dim_vals;\n", " this.load_json = load_json;\n", " this.mode = mode;\n", " this.notFound = notFound;\n", " this.cached = cached;\n", " this.dynamic = dynamic;\n", " this.cache = {};\n", " this.json_path = json_path;\n", " this.init_slider(this.current_vals[0]);\n", " this.queue = [];\n", " this.wait = false;\n", " if (!this.cached || this.dynamic) {\n", " this.comm = this.init_comms();\n", " }\n", "}\n", "\n", "SelectionWidget.prototype = new HoloViewsWidget;\n", "\n", "\n", "SelectionWidget.prototype.get_key = function(current_vals) {\n", " var key = \"(\";\n", " for (var i=0; i Date.now()))) {\n", " this.queue.push(key);\n", " return\n", " }\n", " this.queue = [];\n", " this.time = Date.now();\n", " this.current_frame = key;\n", " this.wait = true;\n", " this.dynamic_update(key)\n", " } else if (key !== undefined) {\n", " this.update(key)\n", " }\n", "}\n", "\n", "\n", "/* Define the ScrubberWidget class */\n", "function ScrubberWidget(frames, num_frames, id, interval, load_json, mode, cached, json_path, dynamic, plot_id){\n", " this.slider_id = \"_anim_slider\" + id;\n", " this.loop_select_id = \"_anim_loop_select\" + id;\n", " this.id = id;\n", " this.plot_id = plot_id;\n", " this.interval = interval;\n", " this.current_frame = 0;\n", " this.direction = 0;\n", " this.dynamic = dynamic;\n", " this.timer = null;\n", " this.load_json = load_json;\n", " this.mode = mode;\n", " this.cached = cached;\n", " this.frames = frames;\n", " this.cache = {};\n", " this.length = num_frames;\n", " this.json_path = json_path;\n", " document.getElementById(this.slider_id).max = this.length - 1;\n", " this.init_slider(0);\n", " this.wait = false;\n", " this.queue = [];\n", " if (!this.cached || this.dynamic) {\n", " this.comm = this.init_comms()\n", " }\n", "}\n", "\n", "ScrubberWidget.prototype = new HoloViewsWidget;\n", "\n", "ScrubberWidget.prototype.set_frame = function(frame){\n", " this.current_frame = frame;\n", " var widget = document.getElementById(this.slider_id);\n", " if (widget === null) {\n", " this.pause_animation();\n", " return\n", " }\n", " widget.value = this.current_frame;\n", " if (this.dynamic || !this.cached) {\n", " if ((this.time !== undefined) && ((this.wait) && ((this.time + 10000) > Date.now()))) {\n", " this.queue.push(frame);\n", " return\n", " }\n", " this.queue = [];\n", " this.time = Date.now();\n", " this.wait = true;\n", " this.dynamic_update(frame)\n", " } else {\n", " this.update(frame)\n", " }\n", "}\n", "\n", "ScrubberWidget.prototype.get_loop_state = function(){\n", " var button_group = document[this.loop_select_id].state;\n", " for (var i = 0; i < button_group.length; i++) {\n", " var button = button_group[i];\n", " if (button.checked) {\n", " return button.value;\n", " }\n", " }\n", " return undefined;\n", "}\n", "\n", "\n", "ScrubberWidget.prototype.next_frame = function() {\n", " this.set_frame(Math.min(this.length - 1, this.current_frame + 1));\n", "}\n", "\n", "ScrubberWidget.prototype.previous_frame = function() {\n", " this.set_frame(Math.max(0, this.current_frame - 1));\n", "}\n", "\n", "ScrubberWidget.prototype.first_frame = function() {\n", " this.set_frame(0);\n", "}\n", "\n", "ScrubberWidget.prototype.last_frame = function() {\n", " this.set_frame(this.length - 1);\n", "}\n", "\n", "ScrubberWidget.prototype.slower = function() {\n", " this.interval /= 0.7;\n", " if(this.direction > 0){this.play_animation();}\n", " else if(this.direction < 0){this.reverse_animation();}\n", "}\n", "\n", "ScrubberWidget.prototype.faster = function() {\n", " this.interval *= 0.7;\n", " if(this.direction > 0){this.play_animation();}\n", " else if(this.direction < 0){this.reverse_animation();}\n", "}\n", "\n", "ScrubberWidget.prototype.anim_step_forward = function() {\n", " if(this.current_frame < this.length - 1){\n", " this.next_frame();\n", " }else{\n", " var loop_state = this.get_loop_state();\n", " if(loop_state == \"loop\"){\n", " this.first_frame();\n", " }else if(loop_state == \"reflect\"){\n", " this.last_frame();\n", " this.reverse_animation();\n", " }else{\n", " this.pause_animation();\n", " this.last_frame();\n", " }\n", " }\n", "}\n", "\n", "ScrubberWidget.prototype.anim_step_reverse = function() {\n", " if(this.current_frame > 0){\n", " this.previous_frame();\n", " } else {\n", " var loop_state = this.get_loop_state();\n", " if(loop_state == \"loop\"){\n", " this.last_frame();\n", " }else if(loop_state == \"reflect\"){\n", " this.first_frame();\n", " this.play_animation();\n", " }else{\n", " this.pause_animation();\n", " this.first_frame();\n", " }\n", " }\n", "}\n", "\n", "ScrubberWidget.prototype.pause_animation = function() {\n", " this.direction = 0;\n", " if (this.timer){\n", " clearInterval(this.timer);\n", " this.timer = null;\n", " }\n", "}\n", "\n", "ScrubberWidget.prototype.play_animation = function() {\n", " this.pause_animation();\n", " this.direction = 1;\n", " var t = this;\n", " if (!this.timer) this.timer = setInterval(function(){t.anim_step_forward();}, this.interval);\n", "}\n", "\n", "ScrubberWidget.prototype.reverse_animation = function() {\n", " this.pause_animation();\n", " this.direction = -1;\n", " var t = this;\n", " if (!this.timer) this.timer = setInterval(function(){t.anim_step_reverse();}, this.interval);\n", "}\n", "\n", "function extend(destination, source) {\n", " for (var k in source) {\n", " if (source.hasOwnProperty(k)) {\n", " destination[k] = source[k];\n", " }\n", " }\n", " return destination;\n", "}\n", "\n", "function update_widget(widget, values) {\n", " if (widget.hasClass(\"ui-slider\")) {\n", " widget.slider('option', {\n", " min: 0,\n", " max: values.length-1,\n", " dim_vals: values,\n", " value: 0,\n", " dim_labels: values\n", " })\n", " widget.slider('option', 'slide').call(widget, event, {value: 0})\n", " } else {\n", " widget.empty();\n", " for (var i=0; i\", {\n", " value: i,\n", " text: values[i]\n", " }))\n", " };\n", " widget.data('values', values);\n", " widget.data('value', 0);\n", " widget.trigger(\"change\");\n", " };\n", "}\n", "\n", "function init_slider(id, plot_id, dim, values, next_vals, labels, dynamic, step, value, next_dim,\n", " dim_idx, delay, jQueryUI_CDN, UNDERSCORE_CDN) {\n", " // Slider JS Block START\n", " function loadcssfile(filename){\n", " var fileref=document.createElement(\"link\")\n", " fileref.setAttribute(\"rel\", \"stylesheet\")\n", " fileref.setAttribute(\"type\", \"text/css\")\n", " fileref.setAttribute(\"href\", filename)\n", " document.getElementsByTagName(\"head\")[0].appendChild(fileref)\n", " }\n", " loadcssfile(\"https://code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css\");\n", " /* Check if jQuery and jQueryUI have been loaded\n", " otherwise load with require.js */\n", " var jQuery = window.jQuery,\n", " // check for old versions of jQuery\n", " oldjQuery = jQuery && !!jQuery.fn.jquery.match(/^1\\.[0-4](\\.|$)/),\n", " jquery_path = '',\n", " paths = {},\n", " noConflict;\n", " var jQueryUI = jQuery.ui;\n", " // check for jQuery\n", " if (!jQuery || oldjQuery) {\n", " // load if it's not available or doesn't meet min standards\n", " paths.jQuery = jQuery;\n", " noConflict = !!oldjQuery;\n", " } else {\n", " // register the current jQuery\n", " define('jquery', [], function() { return jQuery; });\n", " }\n", " if (!jQueryUI) {\n", " paths.jQueryUI = jQueryUI_CDN.slice(null, -3);\n", " } else {\n", " define('jQueryUI', [], function() { return jQuery.ui; });\n", " }\n", " paths.underscore = UNDERSCORE_CDN.slice(null, -3);\n", " var jquery_require = {\n", " paths: paths,\n", " shim: {\n", " \"jQueryUI\": {\n", " exports:\"$\",\n", " deps: ['jquery']\n", " },\n", " \"underscore\": {\n", " exports: '_'\n", " }\n", " }\n", " }\n", " require.config(jquery_require);\n", " require([\"jQueryUI\", \"underscore\"], function(jUI, _){\n", " if (noConflict) $.noConflict(true);\n", " var vals = values;\n", " if (dynamic && vals.constructor === Array) {\n", " var default_value = parseFloat(value);\n", " var min = parseFloat(vals[0]);\n", " var max = parseFloat(vals[vals.length-1]);\n", " var wstep = step;\n", " var wlabels = [default_value];\n", " var init_label = default_value;\n", " } else {\n", " var min = 0;\n", " if (dynamic) {\n", " var max = Object.keys(vals).length - 1;\n", " var init_label = labels[value];\n", " var default_value = values[value];\n", " } else {\n", " var max = vals.length - 1;\n", " var init_label = labels[value];\n", " var default_value = value;\n", " }\n", " var wstep = 1;\n", " var wlabels = labels;\n", " }\n", " function adjustFontSize(text) {\n", " var width_ratio = (text.parent().width()/8)/text.val().length;\n", " var size = Math.min(0.9, Math.max(0.6, width_ratio))+'em';\n", " text.css('font-size', size);\n", " }\n", " var slider = $('#_anim_widget'+id+'_'+dim);\n", " slider.slider({\n", " animate: \"fast\",\n", " min: min,\n", " max: max,\n", " step: wstep,\n", " value: default_value,\n", " dim_vals: vals,\n", " dim_labels: wlabels,\n", " next_vals: next_vals,\n", " slide: function(event, ui) {\n", " var vals = slider.slider(\"option\", \"dim_vals\");\n", " var next_vals = slider.slider(\"option\", \"next_vals\");\n", " var dlabels = slider.slider(\"option\", \"dim_labels\");\n", " if (dynamic) {\n", " var dim_val = ui.value;\n", " if (vals.constructor === Array) {\n", " var label = ui.value;\n", " } else {\n", " var label = dlabels[ui.value];\n", " }\n", " } else {\n", " var dim_val = vals[ui.value];\n", " var label = dlabels[ui.value];\n", " }\n", " var text = $('#textInput'+id+'_'+dim);\n", " text.val(label);\n", " adjustFontSize(text);\n", " HoloViews.index[plot_id].set_frame(dim_val, dim_idx);\n", " if (Object.keys(next_vals).length > 0) {\n", " var new_vals = next_vals[dim_val];\n", " var next_widget = $('#_anim_widget'+id+'_'+next_dim);\n", " update_widget(next_widget, new_vals);\n", " }\n", " }\n", " });\n", " slider.keypress(function(event) {\n", " if (event.which == 80 || event.which == 112) {\n", " var start = slider.slider(\"option\", \"value\");\n", " var stop = slider.slider(\"option\", \"max\");\n", " for (var i=start; i<=stop; i++) {\n", " var delay = i*delay;\n", " $.proxy(function doSetTimeout(i) { setTimeout($.proxy(function() {\n", " var val = {value:i};\n", " slider.slider('value',i);\n", " slider.slider(\"option\", \"slide\")(null, val);\n", " }, slider), delay);}, slider)(i);\n", " }\n", " }\n", " if (event.which == 82 || event.which == 114) {\n", " var start = slider.slider(\"option\", \"value\");\n", " var stop = slider.slider(\"option\", \"min\");\n", " var count = 0;\n", " for (var i=start; i>=stop; i--) {\n", " var delay = count*delay;\n", " count = count + 1;\n", " $.proxy(function doSetTimeout(i) { setTimeout($.proxy(function() {\n", " var val = {value:i};\n", " slider.slider('value',i);\n", " slider.slider(\"option\", \"slide\")(null, val);\n", " }, slider), delay);}, slider)(i);\n", " }\n", " }\n", " });\n", " var textInput = $('#textInput'+id+'_'+dim)\n", " textInput.val(init_label);\n", " adjustFontSize(textInput);\n", " });\n", "}\n", "\n", "function init_dropdown(id, plot_id, dim, vals, value, next_vals, labels, next_dim, dim_idx, dynamic) {\n", " var widget = $(\"#_anim_widget\"+id+'_'+dim);\n", " widget.data('values', vals)\n", " for (var i=0; i\", {\n", " value: val,\n", " text: labels[i]\n", " }));\n", " };\n", " widget.data(\"next_vals\", next_vals);\n", " widget.val(value);\n", " widget.on('change', function(event, ui) {\n", " if (dynamic) {\n", " var dim_val = parseInt(this.value);\n", " } else {\n", " var dim_val = $.data(this, 'values')[this.value];\n", " }\n", " var next_vals = $.data(this, \"next_vals\");\n", " if (Object.keys(next_vals).length > 0) {\n", " var new_vals = next_vals[dim_val];\n", " var next_widget = $('#_anim_widget'+id+'_'+next_dim);\n", " update_widget(next_widget, new_vals);\n", " }\n", " var widgets = HoloViews.index[plot_id]\n", " if (widgets) {\n", " widgets.set_frame(dim_val, dim_idx);\n", " }\n", " });\n", "}\n", "\n", "\n", "if (window.HoloViews === undefined) {\n", " window.HoloViews = {}\n", " window.PyViz = window.HoloViews\n", "} else if (window.PyViz === undefined) {\n", " window.PyViz = window.HoloViews\n", "}\n", "\n", "\n", "var _namespace = {\n", " init_slider: init_slider,\n", " init_dropdown: init_dropdown,\n", " comms: {},\n", " comm_status: {},\n", " index: {},\n", " plot_index: {},\n", " kernels: {},\n", " receivers: {}\n", "}\n", "\n", "for (var k in _namespace) {\n", " if (!(k in window.HoloViews)) {\n", " window.HoloViews[k] = _namespace[k];\n", " }\n", "}\n", "\n", "var JS_MIME_TYPE = 'application/javascript';\n", "var HTML_MIME_TYPE = 'text/html';\n", "var EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\n", "var CLASS_NAME = 'output';\n", "\n", "/**\n", " * Render data to the DOM node\n", " */\n", "function render(props, node) {\n", " var div = document.createElement(\"div\");\n", " var script = document.createElement(\"script\");\n", " node.appendChild(div);\n", " node.appendChild(script);\n", "}\n", "\n", "/**\n", " * Handle when a new output is added\n", " */\n", "function handle_add_output(event, handle) {\n", " var output_area = handle.output_area;\n", " var output = handle.output;\n", " if (!output.data.hasOwnProperty(EXEC_MIME_TYPE)) {\n", " return\n", " }\n", " var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n", " var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n", " if (id !== undefined) {\n", " var nchildren = toinsert.length;\n", " toinsert[nchildren-1].children[0].innerHTML = output.data[HTML_MIME_TYPE];\n", " toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n", " output_area._hv_plot_id = id;\n", " if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n", " HoloViews.plot_index[id] = Bokeh.index[id];\n", " } else {\n", " HoloViews.plot_index[id] = null;\n", " }\n", " }\n", "}\n", "\n", "/**\n", " * Handle when an output is cleared or removed\n", " */\n", "function handle_clear_output(event, handle) {\n", " var id = handle.cell.output_area._hv_plot_id;\n", " if ((id === undefined) || !(id in HoloViews.plot_index)) { return; }\n", " var comm = window.HoloViews.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n", " if (comm !== null) {\n", " comm.send({event_type: 'delete', 'id': id});\n", " }\n", " delete HoloViews.plot_index[id];\n", " if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n", " window.Bokeh.index[id].model.document.clear();\n", " delete Bokeh.index[id];\n", " }\n", "}\n", "\n", "/**\n", " * Handle kernel restart event\n", " */\n", "function handle_kernel_cleanup(event, handle) {\n", " delete HoloViews.comms[\"hv-extension-comm\"];\n", " window.HoloViews.plot_index = {}\n", "}\n", "\n", "/**\n", " * Handle update_display_data messages\n", " */\n", "function handle_update_output(event, handle) {\n", " handle_clear_output(event, {cell: {output_area: handle.output_area}})\n", " handle_add_output(event, handle)\n", "}\n", "\n", "function register_renderer(events, OutputArea) {\n", " function append_mime(data, metadata, element) {\n", " // create a DOM node to render to\n", " var toinsert = this.create_output_subarea(\n", " metadata,\n", " CLASS_NAME,\n", " EXEC_MIME_TYPE\n", " );\n", " this.keyboard_manager.register_events(toinsert);\n", " // Render to node\n", " var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n", " render(props, toinsert[0]);\n", " element.append(toinsert);\n", " return toinsert\n", " }\n", "\n", " events.on('output_added.OutputArea', handle_add_output);\t\n", " events.on('output_updated.OutputArea', handle_update_output);\n", " events.on('clear_output.CodeCell', handle_clear_output);\n", " events.on('delete.Cell', handle_clear_output);\n", " events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n", "\n", " OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n", " safe: true,\n", " index: 0\n", " });\n", "}\n", "\n", "if (window.Jupyter !== undefined) {\n", " try {\n", " var events = require('base/js/events');\n", " var OutputArea = require('notebook/js/outputarea').OutputArea;\n", " if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n", " register_renderer(events, OutputArea);\n", " }\n", " } catch(err) {\n", " }\n", "}\n", "\n", "// Define MPL specific subclasses\n", "function MPLSelectionWidget() {\n", " SelectionWidget.apply(this, arguments);\n", "}\n", "\n", "function MPLScrubberWidget() {\n", " ScrubberWidget.apply(this, arguments);\n", "}\n", "\n", "// Let them inherit from the baseclasses\n", "MPLSelectionWidget.prototype = Object.create(SelectionWidget.prototype);\n", "MPLScrubberWidget.prototype = Object.create(ScrubberWidget.prototype);\n", "\n", "// Define methods to override on widgets\n", "var MPLMethods = {\n", " init_slider : function(init_val){\n", " if(this.load_json) {\n", " this.from_json()\n", " } else {\n", " this.update_cache();\n", " }\n", " if (this.dynamic | !this.cached | (this.current_vals === undefined)) {\n", " this.update(0)\n", " } else {\n", " this.set_frame(this.current_vals[0], 0)\n", " }\n", " },\n", " process_msg : function(msg) {\n", " var data = msg.content.data;\n", " this.frames[this.current] = data;\n", " this.update_cache(true);\n", " this.update(this.current);\n", " }\n", "}\n", "// Extend MPL widgets with backend specific methods\n", "extend(MPLSelectionWidget.prototype, MPLMethods);\n", "extend(MPLScrubberWidget.prototype, MPLMethods);\n", "\n", "window.HoloViews.MPLSelectionWidget = MPLSelectionWidget\n", "window.HoloViews.MPLScrubberWidget = MPLScrubberWidget\n", "\n", " function JupyterCommManager() {\n", " }\n", "\n", " JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n", " if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n", " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n", " comm_manager.register_target(comm_id, function(comm) {\n", " comm.on_msg(msg_handler);\n", " });\n", " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n", " window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n", " comm.onMsg = msg_handler;\n", " });\n", " }\n", " }\n", "\n", " JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n", " if (comm_id in window.PyViz.comms) {\n", " return window.PyViz.comms[comm_id];\n", " } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n", " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n", " var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n", " if (msg_handler) {\n", " comm.on_msg(msg_handler);\n", " }\n", " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n", " var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n", " comm.open();\n", " if (msg_handler) {\n", " comm.onMsg = msg_handler;\n", " }\n", " }\n", "\n", " window.PyViz.comms[comm_id] = comm;\n", " return comm;\n", " }\n", "\n", " window.PyViz.comm_manager = new JupyterCommManager();\n", " " ], "application/vnd.holoviews_load.v0+json": "function HoloViewsWidget() {\n}\n\nHoloViewsWidget.prototype.init_slider = function(init_val){\n if(this.load_json) {\n this.from_json()\n } else {\n this.update_cache();\n }\n}\n\nHoloViewsWidget.prototype.populate_cache = function(idx){\n this.cache[idx].innerHTML = this.frames[idx];\n if (this.embed) {\n delete this.frames[idx];\n }\n}\n\nHoloViewsWidget.prototype.process_error = function(msg){\n}\n\nHoloViewsWidget.prototype.from_json = function() {\n var data_url = this.json_path + this.id + '.json';\n $.getJSON(data_url, $.proxy(function(json_data) {\n this.frames = json_data;\n this.update_cache();\n this.update(0);\n }, this));\n}\n\nHoloViewsWidget.prototype.dynamic_update = function(current){\n if (current === undefined) {\n return\n }\n this.current = current;\n if (this.comm) {\n var msg = {comm_id: this.id+'_client', content: current}\n this.comm.send(msg);\n }\n}\n\nHoloViewsWidget.prototype.update_cache = function(force){\n var frame_len = Object.keys(this.frames).length;\n for (var i=0; i 0) {\n that.time = Date.now();\n that.dynamic_update(that.queue[that.queue.length-1]);\n that.queue = [];\n } else {\n that.wait = false;\n }\n if ((msg.msg_type == \"Ready\") && msg.content) {\n console.log(\"Python callback returned following output:\", msg.content);\n } else if (msg.msg_type == \"Error\") {\n console.log(\"Python failed with the following traceback:\", msg['traceback'])\n }\n }\n var comm = HoloViews.comm_manager.get_client_comm(this.plot_id, this.id+'_client', ack_callback);\n return comm\n }\n}\n\nHoloViewsWidget.prototype.process_msg = function(msg) {\n}\n\nfunction SelectionWidget(frames, id, slider_ids, keyMap, dim_vals, notFound, load_json, mode, cached, json_path, dynamic, plot_id){\n this.frames = frames;\n this.id = id;\n this.plot_id = plot_id;\n this.slider_ids = slider_ids;\n this.keyMap = keyMap\n this.current_frame = 0;\n this.current_vals = dim_vals;\n this.load_json = load_json;\n this.mode = mode;\n this.notFound = notFound;\n this.cached = cached;\n this.dynamic = dynamic;\n this.cache = {};\n this.json_path = json_path;\n this.init_slider(this.current_vals[0]);\n this.queue = [];\n this.wait = false;\n if (!this.cached || this.dynamic) {\n this.comm = this.init_comms();\n }\n}\n\nSelectionWidget.prototype = new HoloViewsWidget;\n\n\nSelectionWidget.prototype.get_key = function(current_vals) {\n var key = \"(\";\n for (var i=0; i Date.now()))) {\n this.queue.push(key);\n return\n }\n this.queue = [];\n this.time = Date.now();\n this.current_frame = key;\n this.wait = true;\n this.dynamic_update(key)\n } else if (key !== undefined) {\n this.update(key)\n }\n}\n\n\n/* Define the ScrubberWidget class */\nfunction ScrubberWidget(frames, num_frames, id, interval, load_json, mode, cached, json_path, dynamic, plot_id){\n this.slider_id = \"_anim_slider\" + id;\n this.loop_select_id = \"_anim_loop_select\" + id;\n this.id = id;\n this.plot_id = plot_id;\n this.interval = interval;\n this.current_frame = 0;\n this.direction = 0;\n this.dynamic = dynamic;\n this.timer = null;\n this.load_json = load_json;\n this.mode = mode;\n this.cached = cached;\n this.frames = frames;\n this.cache = {};\n this.length = num_frames;\n this.json_path = json_path;\n document.getElementById(this.slider_id).max = this.length - 1;\n this.init_slider(0);\n this.wait = false;\n this.queue = [];\n if (!this.cached || this.dynamic) {\n this.comm = this.init_comms()\n }\n}\n\nScrubberWidget.prototype = new HoloViewsWidget;\n\nScrubberWidget.prototype.set_frame = function(frame){\n this.current_frame = frame;\n var widget = document.getElementById(this.slider_id);\n if (widget === null) {\n this.pause_animation();\n return\n }\n widget.value = this.current_frame;\n if (this.dynamic || !this.cached) {\n if ((this.time !== undefined) && ((this.wait) && ((this.time + 10000) > Date.now()))) {\n this.queue.push(frame);\n return\n }\n this.queue = [];\n this.time = Date.now();\n this.wait = true;\n this.dynamic_update(frame)\n } else {\n this.update(frame)\n }\n}\n\nScrubberWidget.prototype.get_loop_state = function(){\n var button_group = document[this.loop_select_id].state;\n for (var i = 0; i < button_group.length; i++) {\n var button = button_group[i];\n if (button.checked) {\n return button.value;\n }\n }\n return undefined;\n}\n\n\nScrubberWidget.prototype.next_frame = function() {\n this.set_frame(Math.min(this.length - 1, this.current_frame + 1));\n}\n\nScrubberWidget.prototype.previous_frame = function() {\n this.set_frame(Math.max(0, this.current_frame - 1));\n}\n\nScrubberWidget.prototype.first_frame = function() {\n this.set_frame(0);\n}\n\nScrubberWidget.prototype.last_frame = function() {\n this.set_frame(this.length - 1);\n}\n\nScrubberWidget.prototype.slower = function() {\n this.interval /= 0.7;\n if(this.direction > 0){this.play_animation();}\n else if(this.direction < 0){this.reverse_animation();}\n}\n\nScrubberWidget.prototype.faster = function() {\n this.interval *= 0.7;\n if(this.direction > 0){this.play_animation();}\n else if(this.direction < 0){this.reverse_animation();}\n}\n\nScrubberWidget.prototype.anim_step_forward = function() {\n if(this.current_frame < this.length - 1){\n this.next_frame();\n }else{\n var loop_state = this.get_loop_state();\n if(loop_state == \"loop\"){\n this.first_frame();\n }else if(loop_state == \"reflect\"){\n this.last_frame();\n this.reverse_animation();\n }else{\n this.pause_animation();\n this.last_frame();\n }\n }\n}\n\nScrubberWidget.prototype.anim_step_reverse = function() {\n if(this.current_frame > 0){\n this.previous_frame();\n } else {\n var loop_state = this.get_loop_state();\n if(loop_state == \"loop\"){\n this.last_frame();\n }else if(loop_state == \"reflect\"){\n this.first_frame();\n this.play_animation();\n }else{\n this.pause_animation();\n this.first_frame();\n }\n }\n}\n\nScrubberWidget.prototype.pause_animation = function() {\n this.direction = 0;\n if (this.timer){\n clearInterval(this.timer);\n this.timer = null;\n }\n}\n\nScrubberWidget.prototype.play_animation = function() {\n this.pause_animation();\n this.direction = 1;\n var t = this;\n if (!this.timer) this.timer = setInterval(function(){t.anim_step_forward();}, this.interval);\n}\n\nScrubberWidget.prototype.reverse_animation = function() {\n this.pause_animation();\n this.direction = -1;\n var t = this;\n if (!this.timer) this.timer = setInterval(function(){t.anim_step_reverse();}, this.interval);\n}\n\nfunction extend(destination, source) {\n for (var k in source) {\n if (source.hasOwnProperty(k)) {\n destination[k] = source[k];\n }\n }\n return destination;\n}\n\nfunction update_widget(widget, values) {\n if (widget.hasClass(\"ui-slider\")) {\n widget.slider('option', {\n min: 0,\n max: values.length-1,\n dim_vals: values,\n value: 0,\n dim_labels: values\n })\n widget.slider('option', 'slide').call(widget, event, {value: 0})\n } else {\n widget.empty();\n for (var i=0; i\", {\n value: i,\n text: values[i]\n }))\n };\n widget.data('values', values);\n widget.data('value', 0);\n widget.trigger(\"change\");\n };\n}\n\nfunction init_slider(id, plot_id, dim, values, next_vals, labels, dynamic, step, value, next_dim,\n dim_idx, delay, jQueryUI_CDN, UNDERSCORE_CDN) {\n // Slider JS Block START\n function loadcssfile(filename){\n var fileref=document.createElement(\"link\")\n fileref.setAttribute(\"rel\", \"stylesheet\")\n fileref.setAttribute(\"type\", \"text/css\")\n fileref.setAttribute(\"href\", filename)\n document.getElementsByTagName(\"head\")[0].appendChild(fileref)\n }\n loadcssfile(\"https://code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css\");\n /* Check if jQuery and jQueryUI have been loaded\n otherwise load with require.js */\n var jQuery = window.jQuery,\n // check for old versions of jQuery\n oldjQuery = jQuery && !!jQuery.fn.jquery.match(/^1\\.[0-4](\\.|$)/),\n jquery_path = '',\n paths = {},\n noConflict;\n var jQueryUI = jQuery.ui;\n // check for jQuery\n if (!jQuery || oldjQuery) {\n // load if it's not available or doesn't meet min standards\n paths.jQuery = jQuery;\n noConflict = !!oldjQuery;\n } else {\n // register the current jQuery\n define('jquery', [], function() { return jQuery; });\n }\n if (!jQueryUI) {\n paths.jQueryUI = jQueryUI_CDN.slice(null, -3);\n } else {\n define('jQueryUI', [], function() { return jQuery.ui; });\n }\n paths.underscore = UNDERSCORE_CDN.slice(null, -3);\n var jquery_require = {\n paths: paths,\n shim: {\n \"jQueryUI\": {\n exports:\"$\",\n deps: ['jquery']\n },\n \"underscore\": {\n exports: '_'\n }\n }\n }\n require.config(jquery_require);\n require([\"jQueryUI\", \"underscore\"], function(jUI, _){\n if (noConflict) $.noConflict(true);\n var vals = values;\n if (dynamic && vals.constructor === Array) {\n var default_value = parseFloat(value);\n var min = parseFloat(vals[0]);\n var max = parseFloat(vals[vals.length-1]);\n var wstep = step;\n var wlabels = [default_value];\n var init_label = default_value;\n } else {\n var min = 0;\n if (dynamic) {\n var max = Object.keys(vals).length - 1;\n var init_label = labels[value];\n var default_value = values[value];\n } else {\n var max = vals.length - 1;\n var init_label = labels[value];\n var default_value = value;\n }\n var wstep = 1;\n var wlabels = labels;\n }\n function adjustFontSize(text) {\n var width_ratio = (text.parent().width()/8)/text.val().length;\n var size = Math.min(0.9, Math.max(0.6, width_ratio))+'em';\n text.css('font-size', size);\n }\n var slider = $('#_anim_widget'+id+'_'+dim);\n slider.slider({\n animate: \"fast\",\n min: min,\n max: max,\n step: wstep,\n value: default_value,\n dim_vals: vals,\n dim_labels: wlabels,\n next_vals: next_vals,\n slide: function(event, ui) {\n var vals = slider.slider(\"option\", \"dim_vals\");\n var next_vals = slider.slider(\"option\", \"next_vals\");\n var dlabels = slider.slider(\"option\", \"dim_labels\");\n if (dynamic) {\n var dim_val = ui.value;\n if (vals.constructor === Array) {\n var label = ui.value;\n } else {\n var label = dlabels[ui.value];\n }\n } else {\n var dim_val = vals[ui.value];\n var label = dlabels[ui.value];\n }\n var text = $('#textInput'+id+'_'+dim);\n text.val(label);\n adjustFontSize(text);\n HoloViews.index[plot_id].set_frame(dim_val, dim_idx);\n if (Object.keys(next_vals).length > 0) {\n var new_vals = next_vals[dim_val];\n var next_widget = $('#_anim_widget'+id+'_'+next_dim);\n update_widget(next_widget, new_vals);\n }\n }\n });\n slider.keypress(function(event) {\n if (event.which == 80 || event.which == 112) {\n var start = slider.slider(\"option\", \"value\");\n var stop = slider.slider(\"option\", \"max\");\n for (var i=start; i<=stop; i++) {\n var delay = i*delay;\n $.proxy(function doSetTimeout(i) { setTimeout($.proxy(function() {\n var val = {value:i};\n slider.slider('value',i);\n slider.slider(\"option\", \"slide\")(null, val);\n }, slider), delay);}, slider)(i);\n }\n }\n if (event.which == 82 || event.which == 114) {\n var start = slider.slider(\"option\", \"value\");\n var stop = slider.slider(\"option\", \"min\");\n var count = 0;\n for (var i=start; i>=stop; i--) {\n var delay = count*delay;\n count = count + 1;\n $.proxy(function doSetTimeout(i) { setTimeout($.proxy(function() {\n var val = {value:i};\n slider.slider('value',i);\n slider.slider(\"option\", \"slide\")(null, val);\n }, slider), delay);}, slider)(i);\n }\n }\n });\n var textInput = $('#textInput'+id+'_'+dim)\n textInput.val(init_label);\n adjustFontSize(textInput);\n });\n}\n\nfunction init_dropdown(id, plot_id, dim, vals, value, next_vals, labels, next_dim, dim_idx, dynamic) {\n var widget = $(\"#_anim_widget\"+id+'_'+dim);\n widget.data('values', vals)\n for (var i=0; i\", {\n value: val,\n text: labels[i]\n }));\n };\n widget.data(\"next_vals\", next_vals);\n widget.val(value);\n widget.on('change', function(event, ui) {\n if (dynamic) {\n var dim_val = parseInt(this.value);\n } else {\n var dim_val = $.data(this, 'values')[this.value];\n }\n var next_vals = $.data(this, \"next_vals\");\n if (Object.keys(next_vals).length > 0) {\n var new_vals = next_vals[dim_val];\n var next_widget = $('#_anim_widget'+id+'_'+next_dim);\n update_widget(next_widget, new_vals);\n }\n var widgets = HoloViews.index[plot_id]\n if (widgets) {\n widgets.set_frame(dim_val, dim_idx);\n }\n });\n}\n\n\nif (window.HoloViews === undefined) {\n window.HoloViews = {}\n window.PyViz = window.HoloViews\n} else if (window.PyViz === undefined) {\n window.PyViz = window.HoloViews\n}\n\n\nvar _namespace = {\n init_slider: init_slider,\n init_dropdown: init_dropdown,\n comms: {},\n comm_status: {},\n index: {},\n plot_index: {},\n kernels: {},\n receivers: {}\n}\n\nfor (var k in _namespace) {\n if (!(k in window.HoloViews)) {\n window.HoloViews[k] = _namespace[k];\n }\n}\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if (!output.data.hasOwnProperty(EXEC_MIME_TYPE)) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n toinsert[nchildren-1].children[0].innerHTML = output.data[HTML_MIME_TYPE];\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n HoloViews.plot_index[id] = Bokeh.index[id];\n } else {\n HoloViews.plot_index[id] = null;\n }\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n if ((id === undefined) || !(id in HoloViews.plot_index)) { return; }\n var comm = window.HoloViews.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete HoloViews.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n window.Bokeh.index[id].model.document.clear();\n delete Bokeh.index[id];\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete HoloViews.comms[\"hv-extension-comm\"];\n window.HoloViews.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\t\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n\n// Define MPL specific subclasses\nfunction MPLSelectionWidget() {\n SelectionWidget.apply(this, arguments);\n}\n\nfunction MPLScrubberWidget() {\n ScrubberWidget.apply(this, arguments);\n}\n\n// Let them inherit from the baseclasses\nMPLSelectionWidget.prototype = Object.create(SelectionWidget.prototype);\nMPLScrubberWidget.prototype = Object.create(ScrubberWidget.prototype);\n\n// Define methods to override on widgets\nvar MPLMethods = {\n init_slider : function(init_val){\n if(this.load_json) {\n this.from_json()\n } else {\n this.update_cache();\n }\n if (this.dynamic | !this.cached | (this.current_vals === undefined)) {\n this.update(0)\n } else {\n this.set_frame(this.current_vals[0], 0)\n }\n },\n process_msg : function(msg) {\n var data = msg.content.data;\n this.frames[this.current] = data;\n this.update_cache(true);\n this.update(this.current);\n }\n}\n// Extend MPL widgets with backend specific methods\nextend(MPLSelectionWidget.prototype, MPLMethods);\nextend(MPLScrubberWidget.prototype, MPLMethods);\n\nwindow.HoloViews.MPLSelectionWidget = MPLSelectionWidget\nwindow.HoloViews.MPLScrubberWidget = MPLScrubberWidget\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n }\n\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n\n window.PyViz.comm_manager = new JupyterCommManager();\n " }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "The autoreload extension is already loaded. To reload it, use:\n", " %reload_ext autoreload\n" ] } ], "source": [ "import holoviews as hv\n", "#hv.notebook_extension('bokeh')\n", "%load_ext holoviews.ipython\n", "%output size=150 dpi=120\n", "%load_ext autoreload\n", "%autoreload 2\n", "import os\n", "import numpy as np\n", "%matplotlib inline\n", "import matplotlib.pyplot as plt\n", "fig_width = 12\n", "figsize=(fig_width, .618*fig_width)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Defining input image as Lena:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:12.118254Z", "start_time": "2018-06-20T12:47:12.089862Z" } }, "outputs": [], "source": [ "import numpy as np\n", "np.set_printoptions(precision=2, suppress=True)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:13.710918Z", "start_time": "2018-06-20T12:47:12.120121Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Mean intensity: 0.5346490691900253 - standard deviation: 0.22045908846780704\n" ] } ], "source": [ "from LogGabor import imread\n", "image = imread('https://github.com/bicv/SLIP/raw/master/database/yelmo512.png')\n", "print('Mean intensity: ', image.mean(), ' - standard deviation: ', image.std())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Defining image processing framework by creating a ``LogGabor`` object:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:14.002659Z", "start_time": "2018-06-20T12:47:13.713550Z" } }, "outputs": [], "source": [ "lg = LogGabor(parameterfile)\n", "lg.set_size(image)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Rectifying image:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:14.034060Z", "start_time": "2018-06-20T12:47:14.004610Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Mean intensity: -0.042770279037368776 - standard deviation: 0.394707489264721\n" ] } ], "source": [ "image = lg.normalize(image, center=True)\n", "print('Mean intensity: ', image.mean(), ' - standard deviation: ', image.std())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Back to top](#LogGabor-user-guide)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Testing on a sample image" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:15.440597Z", "start_time": "2018-06-20T12:47:14.035914Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'N_image': 100, 'seed': None, 'N_X': 512, 'N_Y': 512, 'noise': 0.1, 'do_mask': True, 'mask_exponent': 3.0, 'do_whitening': True, 'white_name_database': 'kodakdb', 'white_n_learning': 0, 'white_N': 0.07, 'white_N_0': 0.0, 'white_f_0': 0.4, 'white_alpha': 1.4, 'white_steepness': 4.0, 'white_recompute': False, 'base_levels': 1.618, 'n_theta': 24, 'B_sf': 0.4, 'B_theta': 0.17453277777777776, 'use_cache': True, 'figpath': 'results', 'edgefigpath': 'results/edges', 'matpath': 'cache_dir', 'edgematpath': 'cache_dir/edges', 'datapath': 'database/', 'ext': '.pdf', 'figsize': 14.0, 'formats': ['pdf', 'svg', 'jpg'], 'dpi': 450, 'verbose': 0}\n" ] } ], "source": [ "lg = LogGabor(parameterfile)\n", "image = imread('https://github.com/bicv/SLIP/raw/master/database/yelmo512.png')\n", "lg.set_size(image)\n", "lg.pe.datapath = 'database/'\n", "print(lg.pe)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will use the filters [tested in the notebook](LogGabor_testing-filter-generation.ipynb) and apply them on a sample image:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:15.967904Z", "start_time": "2018-06-20T12:47:15.443111Z" } }, "outputs": [ { "data": { "application/javascript": [ "\n", "// Ugly hack - see #2574 for more information\n", "if (!(document.getElementById('4346157488')) && !(document.getElementById('_anim_imgNone'))) {\n", " console.log(\"Creating DOM nodes dynamically for assumed nbconvert export. To generate clean HTML output set HV_DOC_HTML as an environment variable.\")\n", " var htmlObject = document.createElement('div');\n", " htmlObject.innerHTML = `
`;\n", " var scriptTags = document.getElementsByTagName('script');\n", " var parentTag = scriptTags[scriptTags.length-1].parentNode;\n", " parentTag.append(htmlObject)\n", "}\n" ], "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
" ], "text/plain": [ ":Image [x,y] (z)" ] }, "execution_count": 9, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": 4346157488 } }, "output_type": "execute_result" } ], "source": [ "%%opts Image (cmap='gray') \n", "hv.Image(image)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:16.383199Z", "start_time": "2018-06-20T12:47:15.969795Z" } }, "outputs": [ { "data": { "application/javascript": [ "\n", "// Ugly hack - see #2574 for more information\n", "if (!(document.getElementById('4498953496')) && !(document.getElementById('_anim_imgNone'))) {\n", " console.log(\"Creating DOM nodes dynamically for assumed nbconvert export. To generate clean HTML output set HV_DOC_HTML as an environment variable.\")\n", " var htmlObject = document.createElement('div');\n", " htmlObject.innerHTML = `
`;\n", " var scriptTags = document.getElementsByTagName('script');\n", " var parentTag = scriptTags[scriptTags.length-1].parentNode;\n", " parentTag.append(htmlObject)\n", "}\n" ], "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
" ], "text/plain": [ ":Image [x,y] (z)" ] }, "execution_count": 10, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": 4498953496 } }, "output_type": "execute_result" } ], "source": [ "%%opts Image (cmap='gray') \n", "image = lg.whitening(image)*lg.mask\n", "hv.Image(image)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:16.479988Z", "start_time": "2018-06-20T12:47:16.386177Z" } }, "outputs": [], "source": [ "%opts Image.Phase_domain (cmap='hsv')\n", "%opts Image.Amplitude_domain (cmap='hot')" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:16.514117Z", "start_time": "2018-06-20T12:47:16.482833Z" } }, "outputs": [], "source": [ "# a function to explore these parameters:\n", "sf_0 = .02 # TODO .1 cycle / pixel (Geisler)\n", "params= {'sf_0':sf_0, 'B_sf': lg.pe.B_sf, 'theta':0., 'B_theta': lg.pe.B_theta}\n", "def filter_explore(image, param_name, param_range, verb=False, angle=False, movie=True):\n", " if movie:\n", " amp_map, phase_map = hv.HoloMap(), hv.HoloMap()\n", " else:\n", " ims = []\n", " for param_ in param_range:\n", " if angle:\n", " title = np.str(param_*180/np.pi) + r'$^0$'\n", " else:\n", " title = np.str(param_)\n", " if param_name=='phase':\n", " FT_phase = np.exp(-1j*param_)\n", " params_=params.copy()\n", " else:\n", " FT_phase = 1\n", " params_=params.copy()\n", " params_.update({param_name:param_})\n", " FT_lg = lg.loggabor(0, 0, **params_)\n", " im_ = lg.FTfilter(image, FT_lg, full=True)\n", " \n", " amp = hv.Image(lg.normalize(np.absolute(im_), center=False), \n", " group='Amplitude domain',\n", " key_dimensions=[r'$f_x$', r'$f_y$'],\n", " value_dimensions=[hv.Dimension('Amplitude', range=(0,1))])\n", " phase = hv.Image(np.angle(im_)/np.pi,\n", " group='Phase domain',\n", " key_dimensions=[r'$x$', r'$y$'],\n", " value_dimensions=[hv.Dimension('Phase', range=(-1,1))])\n", " if movie:\n", " amp_map[param_] = amp\n", " phase_map[param_] = phase\n", " else:\n", " ims.append((amp+phase))\n", " if movie:\n", " return amp_map + phase_map\n", " else:\n", " return np.sum(sv).cols(2)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:28.683446Z", "start_time": "2018-06-20T12:47:16.516082Z" } }, "outputs": [ { "data": { "application/javascript": [ "\n", "// Ugly hack - see #2574 for more information\n", "if (!(document.getElementById('4496467000')) && !(document.getElementById('_anim_img7cb851e4197c430282bc44da2e02d561'))) {\n", " console.log(\"Creating DOM nodes dynamically for assumed nbconvert export. To generate clean HTML output set HV_DOC_HTML as an environment variable.\")\n", " var htmlObject = document.createElement('div');\n", " htmlObject.innerHTML = `
\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", " var scriptTags = document.getElementsByTagName('script');\n", " var parentTag = scriptTags[scriptTags.length-1].parentNode;\n", " parentTag.append(htmlObject)\n", "}\n", "/* Instantiate the MPLSelectionWidget class. */\n", "/* The IDs given should match those used in the template above. */\n", "var widget_ids = new Array(1);\n", "\n", "\n", "widget_ids[0] = \"_anim_widget7cb851e4197c430282bc44da2e02d561_Default\";\n", "\n", "\n", "function create_widget() {\n", " var frame_data = {\"0\": \"\", \"1\": \"\", \"2\": \"\", \"3\": \"\", \"4\": \"\", \"5\": \"\", \"6\": \"\", \"7\": \"\", \"8\": \"\", \"9\": \"\", \"10\": \"\", \"11\": \"\", \"12\": \"\", \"13\": \"\", \"14\": \"\", \"15\": \"\", \"16\": \"\", \"17\": \"\", \"18\": \"\", \"19\": \"\", \"20\": \"\", \"21\": \"\", \"22\": \"\", \"23\": \"\"};\n", " var dim_vals = ['-1.439896632'];\n", " var keyMap = {\"('-1.439896632',)\": 0, \"('-1.308996939',)\": 1, \"('-1.178097245',)\": 2, \"('-1.047197551',)\": 3, \"('-0.916297857',)\": 4, \"('-0.785398163',)\": 5, \"('-0.654498469',)\": 6, \"('-0.523598775',)\": 7, \"('-0.392699081',)\": 8, \"('-0.261799387',)\": 9, \"('-0.130899693',)\": 10, \"('0.0',)\": 11, \"('0.130899693',)\": 12, \"('0.261799387',)\": 13, \"('0.392699081',)\": 14, \"('0.523598775',)\": 15, \"('0.654498469',)\": 16, \"('0.785398163',)\": 17, \"('0.916297857',)\": 18, \"('1.047197551',)\": 19, \"('1.178097245',)\": 20, \"('1.308996939',)\": 21, \"('1.439896632',)\": 22, \"('1.570796326',)\": 23};\n", " var notFound = \"

\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", "" ], "text/plain": [ ":Layout\n", " .Amplitude_domain.I :HoloMap [Default]\n", " :Image [x,y] (z)\n", " .Phase_domain.I :HoloMap [Default]\n", " :Image [x,y] (z)" ] }, "execution_count": 13, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": 4496467000 } }, "output_type": "execute_result" } ], "source": [ "# explore parameters of LogGabors on the filtering:\n", "filter_explore(image, param_name='theta', param_range=lg.theta)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:31.788079Z", "start_time": "2018-06-20T12:47:28.685567Z" } }, "outputs": [ { "data": { "application/javascript": [ "\n", "// Ugly hack - see #2574 for more information\n", "if (!(document.getElementById('4497773960')) && !(document.getElementById('_anim_img60f78fd194914a7b97e76d62d46efe11'))) {\n", " console.log(\"Creating DOM nodes dynamically for assumed nbconvert export. To generate clean HTML output set HV_DOC_HTML as an environment variable.\")\n", " var htmlObject = document.createElement('div');\n", " htmlObject.innerHTML = `
\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", " var scriptTags = document.getElementsByTagName('script');\n", " var parentTag = scriptTags[scriptTags.length-1].parentNode;\n", " parentTag.append(htmlObject)\n", "}\n", "/* Instantiate the MPLSelectionWidget class. */\n", "/* The IDs given should match those used in the template above. */\n", "var widget_ids = new Array(1);\n", "\n", "\n", "widget_ids[0] = \"_anim_widget60f78fd194914a7b97e76d62d46efe11_Default\";\n", "\n", "\n", "function create_widget() {\n", " var frame_data = {\"0\": \"\", \"1\": \"\", \"2\": \"\", \"3\": \"\", \"4\": \"\", \"5\": \"\"};\n", " var dim_vals = ['0.087473600'];\n", " var keyMap = {\"('0.087473600',)\": 0, \"('0.110122738',)\": 1, \"('0.138636313',)\": 2, \"('0.174532777',)\": 3, \"('0.219723749',)\": 4, \"('0.276615811',)\": 5};\n", " var notFound = \"

\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", "" ], "text/plain": [ ":Layout\n", " .Amplitude_domain.I :HoloMap [Default]\n", " :Image [x,y] (z)\n", " .Phase_domain.I :HoloMap [Default]\n", " :Image [x,y] (z)" ] }, "execution_count": 14, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": 4497773960 } }, "output_type": "execute_result" } ], "source": [ "filter_explore(image, param_name='B_theta', param_range=lg.pe.B_theta*np.logspace(-.3, .2, 6))" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:38.018608Z", "start_time": "2018-06-20T12:47:31.790057Z" } }, "outputs": [ { "data": { "application/javascript": [ "\n", "// Ugly hack - see #2574 for more information\n", "if (!(document.getElementById('4497343208')) && !(document.getElementById('_anim_imgc008ea068028495b86b5ecc18c7f7b5b'))) {\n", " console.log(\"Creating DOM nodes dynamically for assumed nbconvert export. To generate clean HTML output set HV_DOC_HTML as an environment variable.\")\n", " var htmlObject = document.createElement('div');\n", " htmlObject.innerHTML = `
\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", " var scriptTags = document.getElementsByTagName('script');\n", " var parentTag = scriptTags[scriptTags.length-1].parentNode;\n", " parentTag.append(htmlObject)\n", "}\n", "/* Instantiate the MPLSelectionWidget class. */\n", "/* The IDs given should match those used in the template above. */\n", "var widget_ids = new Array(1);\n", "\n", "\n", "widget_ids[0] = \"_anim_widgetc008ea068028495b86b5ecc18c7f7b5b_Default\";\n", "\n", "\n", "function create_widget() {\n", " var frame_data = {\"0\": \"\", \"1\": \"\", \"2\": \"\", \"3\": \"\", \"4\": \"\", \"5\": \"\", \"6\": \"\", \"7\": \"\", \"8\": \"\", \"9\": \"\", \"10\": \"\", \"11\": \"\"};\n", " var dim_vals = ['0.003580811'];\n", " var keyMap = {\"('0.003580811',)\": 0, \"('0.005566023',)\": 1, \"('0.008651843',)\": 2, \"('0.013448452',)\": 3, \"('0.020904315',)\": 4, \"('0.032493732',)\": 5, \"('0.050508356',)\": 6, \"('0.078510344',)\": 7, \"('0.122036718',)\": 8, \"('0.189694247',)\": 9, \"('0.294861318',)\": 10, \"('0.458333333',)\": 11};\n", " var notFound = \"

\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", "" ], "text/plain": [ ":Layout\n", " .Amplitude_domain.I :HoloMap [Default]\n", " :Image [x,y] (z)\n", " .Phase_domain.I :HoloMap [Default]\n", " :Image [x,y] (z)" ] }, "execution_count": 15, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": 4497343208 } }, "output_type": "execute_result" } ], "source": [ "filter_explore(image, param_name='sf_0', param_range=lg.sf_0, verb=True)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:42.703783Z", "start_time": "2018-06-20T12:47:38.020700Z" } }, "outputs": [ { "data": { "application/javascript": [ "\n", "// Ugly hack - see #2574 for more information\n", "if (!(document.getElementById('4498595792')) && !(document.getElementById('_anim_imgb7823a4c49594b8582136f19fde4e243'))) {\n", " console.log(\"Creating DOM nodes dynamically for assumed nbconvert export. To generate clean HTML output set HV_DOC_HTML as an environment variable.\")\n", " var htmlObject = document.createElement('div');\n", " htmlObject.innerHTML = `
\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", " var scriptTags = document.getElementsByTagName('script');\n", " var parentTag = scriptTags[scriptTags.length-1].parentNode;\n", " parentTag.append(htmlObject)\n", "}\n", "/* Instantiate the MPLSelectionWidget class. */\n", "/* The IDs given should match those used in the template above. */\n", "var widget_ids = new Array(1);\n", "\n", "\n", "widget_ids[0] = \"_anim_widgetb7823a4c49594b8582136f19fde4e243_Default\";\n", "\n", "\n", "function create_widget() {\n", " var frame_data = {\"0\": \"\", \"1\": \"\", \"2\": \"\", \"3\": \"\", \"4\": \"\", \"5\": \"\", \"6\": \"\", \"7\": \"\", \"8\": \"\"};\n", " var dim_vals = ['0.100475457'];\n", " var keyMap = {\"('0.100475457',)\": 0, \"('0.141925355',)\": 1, \"('0.200474893',)\": 2, \"('0.283178313',)\": 3, \"('0.400000000',)\": 4, \"('0.565015017',)\": 5, \"('0.798104926',)\": 6, \"('1.127353172',)\": 7, \"('1.592428682',)\": 8};\n", " var notFound = \"

\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", "" ], "text/plain": [ ":Layout\n", " .Amplitude_domain.I :HoloMap [Default]\n", " :Image [x,y] (z)\n", " .Phase_domain.I :HoloMap [Default]\n", " :Image [x,y] (z)" ] }, "execution_count": 16, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": 4498595792 } }, "output_type": "execute_result" } ], "source": [ "filter_explore(image, param_name='B_sf', param_range=lg.pe.B_sf*np.logspace(-.6, .6, 9))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Back to top](#LogGabor-user-guide)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## more book keeping" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:42.771552Z", "start_time": "2018-06-20T12:47:42.706149Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2018-06-20T14:47:42+02:00\n", "\n", "CPython 3.6.5\n", "IPython 6.4.0\n", "\n", "compiler : GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)\n", "system : Darwin\n", "release : 17.6.0\n", "machine : x86_64\n", "processor : i386\n", "CPU cores : 36\n", "interpreter: 64bit\n" ] } ], "source": [ "%load_ext watermark\n", "%watermark" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "ExecuteTime": { "end_time": "2018-06-20T12:47:42.843329Z", "start_time": "2018-06-20T12:47:42.776566Z" } }, "outputs": [ { "data": { "application/json": { "Software versions": [ { "module": "Python", "version": "3.6.5 64bit [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)]" }, { "module": "IPython", "version": "6.4.0" }, { "module": "OS", "version": "Darwin 17.6.0 x86_64 i386 64bit" }, { "module": "numpy", "version": "1.14.5" }, { "module": "scipy", "version": "1.1.0" }, { "module": "matplotlib", "version": "2.2.2" }, { "module": "SLIP", "version": "20171205" }, { "module": "LogGabor", "version": "2017-12-05.binder" } ] }, "text/html": [ "
SoftwareVersion
Python3.6.5 64bit [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)]
IPython6.4.0
OSDarwin 17.6.0 x86_64 i386 64bit
numpy1.14.5
scipy1.1.0
matplotlib2.2.2
SLIP20171205
LogGabor2017-12-05.binder
Wed Jun 20 14:47:42 2018 CEST
" ], "text/latex": [ "\\begin{tabular}{|l|l|}\\hline\n", "{\\bf Software} & {\\bf Version} \\\\ \\hline\\hline\n", "Python & 3.6.5 64bit [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)] \\\\ \\hline\n", "IPython & 6.4.0 \\\\ \\hline\n", "OS & Darwin 17.6.0 x86\\_64 i386 64bit \\\\ \\hline\n", "numpy & 1.14.5 \\\\ \\hline\n", "scipy & 1.1.0 \\\\ \\hline\n", "matplotlib & 2.2.2 \\\\ \\hline\n", "SLIP & 20171205 \\\\ \\hline\n", "LogGabor & 2017-12-05.binder \\\\ \\hline\n", "\\hline \\multicolumn{2}{|l|}{Wed Jun 20 14:47:42 2018 CEST} \\\\ \\hline\n", "\\end{tabular}\n" ], "text/plain": [ "Software versions\n", "Python 3.6.5 64bit [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)]\n", "IPython 6.4.0\n", "OS Darwin 17.6.0 x86_64 i386 64bit\n", "numpy 1.14.5\n", "scipy 1.1.0\n", "matplotlib 2.2.2\n", "SLIP 20171205\n", "LogGabor 2017-12-05.binder\n", "Wed Jun 20 14:47:42 2018 CEST" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%load_ext version_information\n", "%version_information numpy, scipy, matplotlib, SLIP, LogGabor" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Back to top](#LogGabor-user-guide)" ] } ], "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.7.6" }, "nteract": { "version": "0.21.0" }, "toc": { "base_numbering": 1, "nav_menu": { "height": "279px", "width": "252px" }, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": "block", "toc_window_display": false } }, "nbformat": 4, "nbformat_minor": 4 }