├── test ├── __init__.py └── test_imcmc.py ├── requirements-dev.txt ├── requirements.txt ├── examples ├── civic.gif ├── civic.png ├── imcmc.gif ├── imcmc.png ├── pymc3.gif ├── python.gif ├── python.png ├── scipy.gif ├── scipy.png ├── matplotlib.gif ├── matplotlib.png └── pymc3-logo.png ├── .gitignore ├── imcmc ├── __init__.py └── imcmc.py ├── .travis.yml ├── LICENSE ├── setup.py ├── README.md └── .pylintrc /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pylint==1.8.2 2 | pytest==3.3.2 3 | pytest-cov==2.5.1 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib>=2.1.2 2 | Pillow>=5.0.0 3 | pymc3>=3.5 4 | scipy>=1.0.0 5 | -------------------------------------------------------------------------------- /examples/civic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/imcmc/master/examples/civic.gif -------------------------------------------------------------------------------- /examples/civic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/imcmc/master/examples/civic.png -------------------------------------------------------------------------------- /examples/imcmc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/imcmc/master/examples/imcmc.gif -------------------------------------------------------------------------------- /examples/imcmc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/imcmc/master/examples/imcmc.png -------------------------------------------------------------------------------- /examples/pymc3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/imcmc/master/examples/pymc3.gif -------------------------------------------------------------------------------- /examples/python.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/imcmc/master/examples/python.gif -------------------------------------------------------------------------------- /examples/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/imcmc/master/examples/python.png -------------------------------------------------------------------------------- /examples/scipy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/imcmc/master/examples/scipy.gif -------------------------------------------------------------------------------- /examples/scipy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/imcmc/master/examples/scipy.png -------------------------------------------------------------------------------- /examples/matplotlib.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/imcmc/master/examples/matplotlib.gif -------------------------------------------------------------------------------- /examples/matplotlib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/imcmc/master/examples/matplotlib.png -------------------------------------------------------------------------------- /examples/pymc3-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/imcmc/master/examples/pymc3-logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.egg-info/ 3 | *__pycache__/ 4 | .cache/ 5 | .coverage 6 | htmlcov/ 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /imcmc/__init__.py: -------------------------------------------------------------------------------- 1 | from .imcmc import load_image, sample_grayscale, sample_color, plot_multitrace,\ 2 | plot_multitrace_color, make_gif, make_color_gif 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | python: 4 | - "3.5" 5 | - "3.6" 6 | install: 7 | - pip install . 8 | - pip install pytest pylint pytest-cov coveralls 9 | before_script: # configure a headless display to test plot generation 10 | - "export DISPLAY=:99.0" 11 | - "sh -e /etc/init.d/xvfb start" 12 | - sleep 3 # give xvfb some time to start 13 | script: 14 | - pylint imcmc 15 | - py.test -v --cov=imcmc test/ 16 | after_success: 17 | - coveralls 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Colin Carroll 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from codecs import open 2 | from os.path import join, abspath, dirname 3 | from setuptools import setup, find_packages 4 | 5 | here = abspath(dirname(__file__)) 6 | 7 | # Get the long description from the README file 8 | with open(join(here, 'README.md'), encoding='utf-8') as buff: 9 | long_description = buff.read() 10 | 11 | requirements_file = join(here, 'requirements.txt') 12 | 13 | with open(requirements_file) as f: 14 | install_reqs = f.read().splitlines() 15 | 16 | setup( 17 | name='imcmc', 18 | version='0.1.0', 19 | description='Turn images into probability distributions, and then sample from them', 20 | long_description=long_description, 21 | author='Colin Carroll', 22 | author_email='colcarroll@gmail.com', 23 | url='https://github.com/ColCarroll/imcmc', 24 | license='MIT', 25 | classifiers=[ 26 | 'Development Status :: 3 - Alpha', 27 | 'Intended Audience :: Developers', 28 | 'License :: OSI Approved :: MIT License', 29 | 'Programming Language :: Python :: 3', 30 | 'Programming Language :: Python :: 3.6', 31 | ], 32 | packages=find_packages(exclude=['test']), 33 | install_requires=install_reqs, 34 | include_package_data=True, 35 | ) 36 | -------------------------------------------------------------------------------- /test/test_imcmc.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | 4 | import imcmc 5 | 6 | HERE = os.path.dirname(os.path.abspath(__file__)) 7 | EXAMPLE_DIR = os.path.join(os.path.dirname(HERE), 'examples') 8 | 9 | EXAMPLE = os.path.join(EXAMPLE_DIR, 'pymc3-logo.png') 10 | 11 | 12 | def test_load_gray_image(): 13 | im = imcmc.load_image(EXAMPLE, 'L') 14 | assert im.ndim == 2 15 | 16 | 17 | def test_load_color_image(): 18 | im = imcmc.load_image(EXAMPLE) 19 | assert im.ndim == 3 20 | 21 | 22 | def test_sample_grayscale(): 23 | im = imcmc.load_image(EXAMPLE, 'L') 24 | trace = imcmc.sample_grayscale(im, samples=1000, nchains=4) 25 | assert len(trace['image']) == 1000 * 4 26 | 27 | 28 | def test_plot_multitrace(): 29 | im = imcmc.load_image(EXAMPLE, 'L') 30 | trace = imcmc.sample_grayscale(im, samples=1000, nchains=4) 31 | imcmc.plot_multitrace(trace, im) 32 | 33 | 34 | def test_make_gif(): 35 | im = imcmc.load_image(EXAMPLE, 'L') 36 | trace = imcmc.sample_grayscale(im, samples=1000, nchains=4) 37 | with tempfile.TemporaryDirectory() as tmpdirname: 38 | filename = os.path.join(tmpdirname, 'test_gif.gif') 39 | assert not os.path.exists(filename) 40 | imcmc.make_gif(trace, im, steps=2, filename=filename) 41 | assert os.path.exists(filename) 42 | 43 | 44 | def test_sample_color(): 45 | im = imcmc.load_image(EXAMPLE) 46 | trace = imcmc.sample_color(im, samples=1000, nchains=2) 47 | for color in ('red', 'blue', 'green'): 48 | assert len(trace[color]) == 1000 * 2 49 | 50 | 51 | def test_plot_multitrace_color(): 52 | # just making sure it runs 53 | im = imcmc.load_image(EXAMPLE) 54 | trace = imcmc.sample_color(im, samples=1000, nchains=2) 55 | imcmc.plot_multitrace_color(trace, im) 56 | 57 | 58 | def test_make_color_gif(): 59 | # just making sure it runs 60 | im = imcmc.load_image(EXAMPLE) 61 | trace = imcmc.sample_color(im, samples=1000, nchains=2) 62 | with tempfile.TemporaryDirectory() as tmpdirname: 63 | filename = os.path.join(tmpdirname, 'test_gif.gif') 64 | assert not os.path.exists(filename) 65 | imcmc.make_color_gif(trace, im, steps=2, filename=filename) 66 | assert os.path.exists(filename) 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![imcmc](examples/imcmc.gif) 2 | 3 | [![Build Status](https://travis-ci.org/ColCarroll/imcmc.svg?branch=master)](https://travis-ci.org/ColCarroll/imcmc) 4 | [![Coverage Status](https://coveralls.io/repos/github/ColCarroll/imcmc/badge.svg?branch=master)](https://coveralls.io/github/ColCarroll/imcmc?branch=master) 5 | 6 | *It probably makes art.* 7 | 8 | `imcmc` (*im-sea-em-sea*) is a small library for turning 2d images into probability distributions 9 | and then sampling from them to create images and gifs. Right now it is best at logos and shape based images. 10 | 11 | 12 | Installation 13 | ------------ 14 | This is actually `pip` installable from git! 15 | 16 | ``` 17 | pip install git+https://github.com/ColCarroll/imcmc 18 | ``` 19 | 20 | Quickstart 21 | ---------- 22 | 23 | See [imcmc.ipynb](examples/imcmc.ipynb) for a few working examples as well. 24 | 25 | ``` 26 | import imcmc 27 | 28 | 29 | image = imcmc.load_image('python.png', 'L') 30 | 31 | # This call is random -- rerun adjusting parameters until the image looks good 32 | trace = imcmc.sample_grayscale(image, samples=1000, tune=500, nchains=6) 33 | 34 | # Lots of plotting options! 35 | imcmc.plot_multitrace(trace, image, marker='o', markersize=10, 36 | colors=['#0000FF', '#FFFF00'], alpha=0.9); 37 | 38 | # Save as a gif, with the same arguments as above, plus some more 39 | imcmc.make_gif(trace, image, dpi=40, marker='o', markersize=10, 40 | colors=['#0000FF', '#FFFF00'], alpha=0.9, 41 | filename='example.gif') 42 | ``` 43 | 44 | Built with 45 | ---------- 46 | 47 | `Pillow` does not have a logo, but the other tools do! 48 | 49 | 50 | ![PyMC3](examples/pymc3.gif) 51 | 52 | ![matplotlib](examples/matplotlib.gif) 53 | 54 | ![scipy](examples/scipy.gif) 55 | 56 | ![Python](examples/python.gif) 57 | 58 | 59 | Here's a tricky one whose support I appreciate 60 | ---------------------------------------------- 61 | 62 | I get to do lots of open source work for [The Center for Civic Media](https://civic.mit.edu/) at 63 | MIT. Even better, they have a super multi-modal logo that I needed to use 98 chains to sample from! 64 | 65 | ![Center for Civic Media](examples/civic.gif) 66 | 67 | 68 | Further work 69 | ------------ 70 | 71 | There are some functions in there to sample from the RGB channels of real images, but the reconstructed 72 | images just look blurry, and the gifs just look like they are awkward fades. Still working on it! 73 | -------------------------------------------------------------------------------- /imcmc/imcmc.py: -------------------------------------------------------------------------------- 1 | from cycler import cycler 2 | import matplotlib.pyplot as plt 3 | from matplotlib.animation import FuncAnimation 4 | import numpy as np 5 | from PIL import Image 6 | import pymc3 as pm 7 | import theano 8 | import theano.tensor as tt 9 | from tqdm import tqdm 10 | import scipy 11 | 12 | 13 | def get_rainbow(): 14 | """Creates a rainbow color cycle""" 15 | return cycler('color', [ 16 | '#FF0000', 17 | '#FF7F00', 18 | '#FFFF00', 19 | '#00FF00', 20 | '#0000FF', 21 | '#4B0082', 22 | '#9400D3', 23 | ]) 24 | 25 | 26 | def load_image(image_file, mode=None): 27 | """Load filename into a numpy array, filling in transparency with 0's. 28 | 29 | Parameters 30 | ---------- 31 | image_file : str 32 | File to load. Usually works with .jpg and .png. 33 | 34 | Returns 35 | ------- 36 | numpy.ndarray of resulting image. Has shape (w, h), (w, h, 3), or (w, h, 4) 37 | if black and white, color, or color with alpha channel, respectively. 38 | """ 39 | image = Image.open(image_file) 40 | if mode is None: 41 | mode = image.mode 42 | alpha = image.convert('RGBA').split()[-1] 43 | background = Image.new("RGBA", image.size, (255, 255, 255, 255,)) 44 | background.paste(image, mask=alpha) 45 | img = np.flipud(np.asarray(background.convert(mode))) 46 | img = img / 255 47 | if mode == 'L': # I don't know how images work, but .png's are inverted 48 | img = 1 - img 49 | return img 50 | 51 | 52 | class ImageLikelihood(theano.Op): 53 | """ 54 | Custom theano op for turning a 2d intensity matrix into a density 55 | distribution. 56 | """ 57 | 58 | itypes = [tt.dvector] 59 | otypes = [tt.dvector] 60 | 61 | def __init__(self, img): 62 | self.width, self.height = img.shape 63 | self.density = scipy.interpolate.RectBivariateSpline( 64 | x=np.arange(self.width), 65 | y=np.arange(self.height), 66 | z=img) 67 | 68 | def perform(self, node, inputs, output_storage): 69 | """Evaluates the density of the image at the given point.""" 70 | x, y = inputs[0] 71 | if x < 0 or x > self.width or y < 0 or y > self.height: 72 | output_storage[0][0] = np.array([np.log(0)]) 73 | else: 74 | output_storage[0][0] = np.log(self.density(x, y))[0] 75 | 76 | 77 | def sample_grayscale(image, samples=5000, tune=100, nchains=4, threshold=0.2): 78 | """Run MCMC on a 1 color image. Works best on logos or text. 79 | 80 | Parameters 81 | ---------- 82 | image : numpy.ndarray 83 | Image array from `load_image`. Should have `image.ndims == 2`. 84 | 85 | samples : int 86 | Number of samples to draw from the image 87 | 88 | tune : int 89 | Number of tuning steps to take. Note that this adjusts the step size: 90 | if you want smaller steps, make tune closer to 0. 91 | 92 | nchains : int 93 | Number of chains to sample with. This will later turn into the number 94 | of colors in your plot. Note that you get `samples * nchains` of total 95 | points in your final scatter. 96 | 97 | threshold : float 98 | Float between 0 and 1. It looks nicer when an image is binarized, and 99 | this will do that. Use `None` to not binarize. In theory you should get 100 | fewer samples from lighter areas, but your mileage may vary. 101 | 102 | Returns 103 | ------- 104 | pymc3.MultiTrace of samples from the image. Each sample is an (x, y) float 105 | of indices that were sampled, with the variable name 'image'. 106 | """ 107 | # preprocess 108 | image_copy = image.copy() 109 | if threshold != -1: 110 | image_copy[image < threshold] = 0 111 | image_copy[image >= threshold] = 1 112 | 113 | # need an active pixel to start on 114 | active_pixels = np.array(list(zip(*np.where(image_copy == image_copy.max())))) 115 | idx = np.random.randint(0, len(active_pixels), nchains) 116 | start = active_pixels[idx] 117 | 118 | with pm.Model(): 119 | pm.DensityDist('image', ImageLikelihood(image_copy), shape=2) 120 | trace = pm.sample(samples, 121 | tune=tune, 122 | chains=nchains, step=pm.Metropolis(), 123 | start=[{'image': x} for x in start], 124 | ) 125 | return trace 126 | 127 | 128 | def sample_color(image, samples=5000, tune=1000, nchains=4): 129 | """Run MCMC on a color image. EXPERIMENTAL! 130 | 131 | Parameters 132 | ---------- 133 | image : numpy.ndarray 134 | Image array from `load_image`. Should have `image.ndims == 2`. 135 | 136 | samples : int 137 | Number of samples to draw from the image 138 | 139 | tune : int 140 | All chains start at the same spot, so it is good to let them wander 141 | apart a bit before beginning 142 | 143 | Returns 144 | ------- 145 | pymc3.MultiTrace of samples from the image. Each sample is an (x, y) float 146 | of indices that were sampled, with three variables named 'red', 147 | 'green', 'blue'. 148 | """ 149 | 150 | with pm.Model(): 151 | pm.DensityDist('red', ImageLikelihood(image[:, :, 0]), shape=2) 152 | pm.DensityDist('green', ImageLikelihood(image[:, :, 1]), shape=2) 153 | pm.DensityDist('blue', ImageLikelihood(image[:, :, 2]), shape=2) 154 | 155 | trace = pm.sample(samples, chains=nchains, tune=tune, step=pm.Metropolis()) 156 | return trace 157 | 158 | 159 | def plot_multitrace(trace, image, max_size=10, colors=None, **plot_kwargs): 160 | """Plot an image of the grayscale trace. 161 | 162 | Parameters 163 | ---------- 164 | trace : pymc3.MultiTrace 165 | Get this from sample_grayscale 166 | 167 | image : numpy.ndarray 168 | Image array from `load_image`, used to produce the trace. 169 | 170 | max_size : float 171 | Used to set the figsize for the image, maintaining the aspect ratio. 172 | In inches! 173 | 174 | colors : iterable 175 | You can set custom colors to cycle through! Default is the rainbow. 176 | 177 | plot_kwargs : 178 | Other keyword arguments passed to the trace plotting. Some useful 179 | examples are marker='.' in case you sampled lots of points, alpha=0.3 180 | to add transparency to the points, or linestyle='-', so you can see the 181 | actual path the chains took. 182 | 183 | Returns 184 | ------- 185 | (figure, axis) 186 | The matplotlib figure and axis with the plot 187 | """ 188 | default_kwargs = {'marker': 'o', 'linestyle': '', 'alpha': 0.4} 189 | default_kwargs.update(plot_kwargs) 190 | if colors is None: 191 | colors = get_rainbow() 192 | else: 193 | colors = cycler('color', colors) 194 | 195 | vals = [trace.get_values('image', chains=chain) for chain in trace.chains] 196 | 197 | fig, ax = plt.subplots(figsize=get_figsize(image, max_size)) 198 | ax.set_prop_cycle(colors) 199 | ax.set_xlim((0, image.shape[1])) 200 | ax.set_ylim((0, image.shape[0])) 201 | ax.axis('off') 202 | 203 | for val in vals: 204 | ax.plot(val[:, 1], val[:, 0], **default_kwargs) 205 | return fig, ax 206 | 207 | 208 | def make_gif(trace, image, steps=200, 209 | leading_point=True, leading_point_size=20, 210 | filename='output.gif', max_size=10, interval=30, dpi=20, 211 | colors=None, **plot_kwargs): 212 | """Make a gif of the grayscale trace. 213 | 214 | Parameters 215 | ---------- 216 | trace : pymc3.MultiTrace 217 | Get this from sample_grayscale 218 | 219 | image : numpy.ndarray 220 | Image array from `load_image`, used to produce the trace. 221 | 222 | steps : int 223 | Number of frames in the resulting .gif 224 | 225 | leading_point : bool 226 | If true, adds a large point at the head of each chain, so you can 227 | follow the path easier. 228 | 229 | filename : str 230 | Place to save the resulting .gif to 231 | 232 | max_size : float 233 | Used to set the figsize for the image, maintaining the aspect ratio. 234 | In inches! 235 | 236 | interval : int 237 | How long each frame lasts. Pretty sure this is hundredths of seconds 238 | 239 | dpi : int 240 | Quality of the resulting .gif Seems like larger values make the gif 241 | bigger too. 242 | 243 | colors : iterable 244 | You can set custom colors to cycle through! Default is the rainbow. 245 | 246 | plot_kwargs : 247 | Other keyword arguments passed to the trace plotting. Some useful 248 | examples are marker='.' in case you sampled lots of points, alpha=0.3 249 | to add transparency to the points, or linestyle='-', so you can see the 250 | actual path the chains took. 251 | 252 | Returns 253 | ------- 254 | str 255 | filename where the gif was saved 256 | """ 257 | default_kwargs = {'marker': 'o', 'linestyle': '', 'alpha': 0.4} 258 | default_kwargs.update(plot_kwargs) 259 | if colors is None: 260 | colors = get_rainbow() 261 | else: 262 | colors = cycler('color', colors) 263 | 264 | vals = [trace.get_values('image', chains=chain) for chain in trace.chains] 265 | intervals = np.linspace(0, vals[0].shape[0] - 1, num=steps + 1, dtype=int)[1:] # noqa 266 | 267 | fig, ax = plt.subplots(figsize=get_figsize(image, max_size)) 268 | ax.set_prop_cycle(colors) 269 | ax.set_xlim((0, image.shape[1])) 270 | ax.set_ylim((0, image.shape[0])) 271 | ax.axis('off') 272 | 273 | lines, points = [], [] 274 | for _ in vals: 275 | lines.append(ax.plot([], [], **default_kwargs)[0]) 276 | if leading_point: 277 | points.append(ax.plot([], [], 'o', c=lines[-1].get_color(), markersize=leading_point_size)[0]) # noqa 278 | else: 279 | points.append(None) 280 | 281 | def update(idx): 282 | if idx < len(intervals): 283 | for pts, lns, val in zip(points, lines, vals): 284 | lns.set_data(val[:intervals[idx], 1], val[:intervals[idx], 0]) 285 | if leading_point: 286 | pts.set_data(val[intervals[idx], 1], val[intervals[idx], 0]) 287 | elif idx == len(intervals) and leading_point: 288 | for pts in points: 289 | pts.set_data([], []) 290 | 291 | return ax 292 | 293 | anim = FuncAnimation(fig, update, frames=np.arange(steps + 20), interval=interval) # noqa 294 | anim.save(filename, dpi=dpi, writer='imagemagick') 295 | return filename 296 | 297 | 298 | def get_figsize(image, max_size=10): 299 | """Helper to scale figures""" 300 | scale = max_size / max(image.shape) 301 | return (scale * image.shape[1], scale * image.shape[0]) 302 | 303 | 304 | def _process_image_trace(trace, image, blur): 305 | """Adds Gaussian blur""" 306 | w, h = image.shape[:2] 307 | colors = ('red', 'green', 'blue') 308 | channels = [np.zeros((w, h)) for color in colors] 309 | for color, channel in zip(colors, channels): 310 | for idx in np.array(np.round(trace[color]), dtype=int): 311 | x, y = idx 312 | channel[min(x, w - 1), min(y, h - 1)] += 1 313 | return [scipy.ndimage.filters.gaussian_filter(channel, blur) for channel in channels] # noqa 314 | 315 | 316 | def plot_multitrace_color(trace, image, blur=8, channel_max=None): 317 | """Plot the trace from a color image 318 | 319 | Does additive blending of the three channels using Pillow. Higher `blur` 320 | make the colors look right, but the image look blurrier. 321 | 322 | Parameters 323 | ---------- 324 | trace : pymc3.MultiTrace 325 | Get this from sample_color 326 | 327 | image : numpy.ndarray 328 | Image array from `load_image`, used to produce the trace. 329 | 330 | blur : float 331 | Each point only colors in a single pixel, but a gaussian blur makes the 332 | samples blend well. This typically must be tuned by eye. 333 | 334 | channel_max : list or None 335 | This is used internally to normalize channels for making a gif 336 | 337 | Returns 338 | ------- 339 | PIL.Image 340 | RGB image of the samples 341 | """ 342 | smoothed = _process_image_trace(trace, image, blur) 343 | if channel_max is None: 344 | channel_max = [channel.max() for channel in smoothed] 345 | 346 | pils = [] 347 | for channel, c_max in zip(smoothed, channel_max): 348 | pils.append(Image.fromarray(np.uint8(255 * np.flipud(channel / c_max)))) 349 | return Image.merge('RGB', pils) 350 | 351 | 352 | def make_color_gif(trace, image, blur=8, steps=200, max_size=10, filename='output.gif', 353 | interval=30, dpi=20): 354 | """Make a gif of the color trace. SUPER EXPERIMENTAL! 355 | 356 | Tries to grab portions of the trace from 357 | 358 | 359 | Parameters 360 | ---------- 361 | trace : pymc3.MultiTrace 362 | Get this from sample_grayscale 363 | 364 | image : numpy.ndarray 365 | Image array from `load_image`, used to produce the trace. 366 | 367 | blur : float 368 | Each point only colors in a single pixel, but a gaussian blur makes the 369 | samples blend well. This typically must be tuned by eye. 370 | steps : int 371 | Number of frames in the resulting .gif 372 | 373 | max_size : float 374 | Used to set the figsize for the image, maintaining the aspect ratio. In 375 | inches! 376 | 377 | leading_point : bool 378 | If true, adds a large point at the head of each chain, so you can 379 | follow the path easier. 380 | 381 | filename : str 382 | Place to save the resulting .gif to 383 | 384 | interval : int 385 | How long each frame lasts. Pretty sure this is hundredths of seconds 386 | 387 | dpi : int 388 | Quality of the resulting .gif Seems like larger values make the gif 389 | bigger too. 390 | 391 | Returns 392 | ------- 393 | str 394 | filename where the gif was saved 395 | """ 396 | figsize = get_figsize(image, max_size=max_size) 397 | 398 | intervals = np.linspace(0, len(trace) - 1, num=steps + 1, dtype=int)[1:] 399 | 400 | fig, ax = plt.subplots(figsize=figsize) 401 | ax.imshow(np.zeros_like(image)) 402 | ax.axis('off') 403 | channel_max = [channel.max() for channel in _process_image_trace(trace, image, blur)] # noqa 404 | 405 | with tqdm(total=steps) as pbar: 406 | def update(idx): 407 | color_image = plot_multitrace_color(trace[:intervals[idx]], image, blur=blur, 408 | channel_max=channel_max) 409 | ax.imshow(color_image) 410 | pbar.update(1) 411 | return ax 412 | 413 | anim = FuncAnimation(fig, update, frames=np.arange(steps), interval=interval) # noqa 414 | anim.save(filename, dpi=dpi, writer='imagemagick') 415 | return filename 416 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code 6 | extension-pkg-whitelist= 7 | 8 | # Add files or directories to the blacklist. They should be base names, not 9 | # paths. 10 | ignore=CVS 11 | 12 | # Add files or directories matching the regex patterns to the blacklist. The 13 | # regex matches against base names, not paths. 14 | ignore-patterns= 15 | 16 | # Python code to execute, usually for sys.path manipulation such as 17 | # pygtk.require(). 18 | #init-hook= 19 | 20 | # Use multiple processes to speed up Pylint. 21 | jobs=1 22 | 23 | # List of plugins (as comma separated values of python modules names) to load, 24 | # usually to register additional checkers. 25 | load-plugins= 26 | 27 | # Pickle collected data for later comparisons. 28 | persistent=yes 29 | 30 | # Specify a configuration file. 31 | #rcfile= 32 | 33 | # When enabled, pylint would attempt to guess common misconfiguration and emit 34 | # user-friendly hints instead of false-positive error messages 35 | suggestion-mode=yes 36 | 37 | # Allow loading of arbitrary C extensions. Extensions are imported into the 38 | # active Python interpreter and may run arbitrary code. 39 | unsafe-load-any-extension=no 40 | 41 | 42 | [MESSAGES CONTROL] 43 | 44 | # Only show warnings with the listed confidence levels. Leave empty to show 45 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 46 | confidence= 47 | 48 | # Disable the message, report, category or checker with the given id(s). You 49 | # can either give multiple identifiers separated by comma (,) or put this 50 | # option multiple times (only on the command line, not in the configuration 51 | # file where it should appear only once).You can also use "--disable=all" to 52 | # disable everything first and then reenable specific checks. For example, if 53 | # you want to run only the similarities checker, you can use "--disable=all 54 | # --enable=similarities". If you want to run only the classes checker, but have 55 | # no Warning level messages displayed, use"--disable=all --enable=classes 56 | # --disable=W" 57 | disable=missing-docstring, 58 | abstract-method, 59 | arguments-differ 60 | 61 | # Enable the message, report, category or checker with the given id(s). You can 62 | # either give multiple identifier separated by comma (,) or put this option 63 | # multiple time (only on the command line, not in the configuration file where 64 | # it should appear only once). See also the "--disable" option for examples. 65 | enable=c-extension-no-member 66 | 67 | 68 | [REPORTS] 69 | 70 | # Python expression which should return a note less than 10 (10 is the highest 71 | # note). You have access to the variables errors warning, statement which 72 | # respectively contain the number of errors / warnings messages and the total 73 | # number of statements analyzed. This is used by the global evaluation report 74 | # (RP0004). 75 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 76 | 77 | # Template used to display messages. This is a python new-style format string 78 | # used to format the message information. See doc for all details 79 | #msg-template= 80 | 81 | # Set the output format. Available formats are text, parseable, colorized, json 82 | # and msvs (visual studio).You can also give a reporter class, eg 83 | # mypackage.mymodule.MyReporterClass. 84 | output-format=text 85 | 86 | # Tells whether to display a full report or only the messages 87 | reports=no 88 | 89 | # Activate the evaluation score. 90 | score=yes 91 | 92 | 93 | [REFACTORING] 94 | 95 | # Maximum number of nested blocks for function / method body 96 | max-nested-blocks=5 97 | 98 | # Complete name of functions that never returns. When checking for 99 | # inconsistent-return-statements if a never returning function is called then 100 | # it will be considered as an explicit return statement and no message will be 101 | # printed. 102 | never-returning-functions=optparse.Values,sys.exit 103 | 104 | 105 | [LOGGING] 106 | 107 | # Logging modules to check that the string format arguments are in logging 108 | # function parameter format 109 | logging-modules=logging 110 | 111 | 112 | [SPELLING] 113 | 114 | # Limits count of emitted suggestions for spelling mistakes 115 | max-spelling-suggestions=4 116 | 117 | # Spelling dictionary name. Available dictionaries: none. To make it working 118 | # install python-enchant package. 119 | spelling-dict= 120 | 121 | # List of comma separated words that should not be checked. 122 | spelling-ignore-words= 123 | 124 | # A path to a file that contains private dictionary; one word per line. 125 | spelling-private-dict-file= 126 | 127 | # Tells whether to store unknown words to indicated private dictionary in 128 | # --spelling-private-dict-file option instead of raising a message. 129 | spelling-store-unknown-words=no 130 | 131 | 132 | [MISCELLANEOUS] 133 | 134 | # List of note tags to take in consideration, separated by a comma. 135 | notes=FIXME, 136 | XXX, 137 | TODO 138 | 139 | 140 | [TYPECHECK] 141 | 142 | # List of decorators that produce context managers, such as 143 | # contextlib.contextmanager. Add to this list to register other decorators that 144 | # produce valid context managers. 145 | contextmanager-decorators=contextlib.contextmanager 146 | 147 | # List of members which are set dynamically and missed by pylint inference 148 | # system, and so shouldn't trigger E1101 when accessed. Python regular 149 | # expressions are accepted. 150 | generated-members= 151 | 152 | # Tells whether missing members accessed in mixin class should be ignored. A 153 | # mixin class is detected if its name ends with "mixin" (case insensitive). 154 | ignore-mixin-members=yes 155 | 156 | # This flag controls whether pylint should warn about no-member and similar 157 | # checks whenever an opaque object is returned when inferring. The inference 158 | # can return multiple potential results while evaluating a Python object, but 159 | # some branches might not be evaluated, which results in partial inference. In 160 | # that case, it might be useful to still emit no-member and other checks for 161 | # the rest of the inferred objects. 162 | ignore-on-opaque-inference=yes 163 | 164 | # List of class names for which member attributes should not be checked (useful 165 | # for classes with dynamically set attributes). This supports the use of 166 | # qualified names. 167 | ignored-classes=optparse.Values,thread._local,_thread._local 168 | 169 | # List of module names for which member attributes should not be checked 170 | # (useful for modules/projects where namespaces are manipulated during runtime 171 | # and thus existing member attributes cannot be deduced by static analysis. It 172 | # supports qualified module names, as well as Unix pattern matching. 173 | ignored-modules= 174 | 175 | # Show a hint with possible names when a member name was not found. The aspect 176 | # of finding the hint is based on edit distance. 177 | missing-member-hint=yes 178 | 179 | # The minimum edit distance a name should have in order to be considered a 180 | # similar match for a missing member name. 181 | missing-member-hint-distance=1 182 | 183 | # The total number of similar names that should be taken in consideration when 184 | # showing a hint for a missing member. 185 | missing-member-max-choices=1 186 | 187 | 188 | [VARIABLES] 189 | 190 | # List of additional names supposed to be defined in builtins. Remember that 191 | # you should avoid to define new builtins when possible. 192 | additional-builtins= 193 | 194 | # Tells whether unused global variables should be treated as a violation. 195 | allow-global-unused-variables=yes 196 | 197 | # List of strings which can identify a callback function by name. A callback 198 | # name must start or end with one of those strings. 199 | callbacks=cb_, 200 | _cb 201 | 202 | # A regular expression matching the name of dummy variables (i.e. expectedly 203 | # not used). 204 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 205 | 206 | # Argument names that match this expression will be ignored. Default to name 207 | # with leading underscore 208 | ignored-argument-names=_.*|^ignored_|^unused_ 209 | 210 | # Tells whether we should check for unused import in __init__ files. 211 | init-import=no 212 | 213 | # List of qualified module names which can have objects that can redefine 214 | # builtins. 215 | redefining-builtins-modules=six.moves,past.builtins,future.builtins 216 | 217 | 218 | [FORMAT] 219 | 220 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 221 | expected-line-ending-format= 222 | 223 | # Regexp for a line that is allowed to be longer than the limit. 224 | ignore-long-lines=^\s*(# )??$ 225 | 226 | # Number of spaces of indent required inside a hanging or continued line. 227 | indent-after-paren=4 228 | 229 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 230 | # tab). 231 | indent-string=' ' 232 | 233 | # Maximum number of characters on a single line. 234 | max-line-length=100 235 | 236 | # Maximum number of lines in a module 237 | max-module-lines=1000 238 | 239 | # List of optional constructs for which whitespace checking is disabled. `dict- 240 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 241 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 242 | # `empty-line` allows space-only lines. 243 | no-space-check=trailing-comma, 244 | dict-separator 245 | 246 | # Allow the body of a class to be on the same line as the declaration if body 247 | # contains single statement. 248 | single-line-class-stmt=no 249 | 250 | # Allow the body of an if to be on the same line as the test if there is no 251 | # else. 252 | single-line-if-stmt=no 253 | 254 | 255 | [SIMILARITIES] 256 | 257 | # Ignore comments when computing similarities. 258 | ignore-comments=yes 259 | 260 | # Ignore docstrings when computing similarities. 261 | ignore-docstrings=yes 262 | 263 | # Ignore imports when computing similarities. 264 | ignore-imports=no 265 | 266 | # Minimum lines number of a similarity. 267 | min-similarity-lines=4 268 | 269 | 270 | [BASIC] 271 | 272 | # Naming style matching correct argument names 273 | argument-naming-style=snake_case 274 | 275 | # Regular expression matching correct argument names. Overrides argument- 276 | # naming-style 277 | #argument-rgx= 278 | 279 | # Naming style matching correct attribute names 280 | attr-naming-style=snake_case 281 | 282 | # Regular expression matching correct attribute names. Overrides attr-naming- 283 | # style 284 | #attr-rgx= 285 | 286 | # Bad variable names which should always be refused, separated by a comma 287 | bad-names=foo, 288 | bar, 289 | baz, 290 | toto, 291 | tutu, 292 | tata 293 | 294 | # Naming style matching correct class attribute names 295 | class-attribute-naming-style=any 296 | 297 | # Regular expression matching correct class attribute names. Overrides class- 298 | # attribute-naming-style 299 | #class-attribute-rgx= 300 | 301 | # Naming style matching correct class names 302 | class-naming-style=PascalCase 303 | 304 | # Regular expression matching correct class names. Overrides class-naming-style 305 | #class-rgx= 306 | 307 | # Naming style matching correct constant names 308 | const-naming-style=UPPER_CASE 309 | 310 | # Regular expression matching correct constant names. Overrides const-naming- 311 | # style 312 | #const-rgx= 313 | 314 | # Minimum line length for functions/classes that require docstrings, shorter 315 | # ones are exempt. 316 | docstring-min-length=-1 317 | 318 | # Naming style matching correct function names 319 | function-naming-style=snake_case 320 | 321 | # Regular expression matching correct function names. Overrides function- 322 | # naming-style 323 | #function-rgx= 324 | 325 | # Good variable names which should always be accepted, separated by a comma 326 | good-names=_, 327 | fig, 328 | ax, 329 | w, 330 | h, 331 | x, 332 | y 333 | 334 | # Include a hint for the correct naming format with invalid-name 335 | include-naming-hint=no 336 | 337 | # Naming style matching correct inline iteration names 338 | inlinevar-naming-style=any 339 | 340 | # Regular expression matching correct inline iteration names. Overrides 341 | # inlinevar-naming-style 342 | #inlinevar-rgx= 343 | 344 | # Naming style matching correct method names 345 | method-naming-style=snake_case 346 | 347 | # Regular expression matching correct method names. Overrides method-naming- 348 | # style 349 | #method-rgx= 350 | 351 | # Naming style matching correct module names 352 | module-naming-style=snake_case 353 | 354 | # Regular expression matching correct module names. Overrides module-naming- 355 | # style 356 | #module-rgx= 357 | 358 | # Colon-delimited sets of names that determine each other's naming style when 359 | # the name regexes allow several styles. 360 | name-group= 361 | 362 | # Regular expression which should only match function or class names that do 363 | # not require a docstring. 364 | no-docstring-rgx=^_ 365 | 366 | # List of decorators that produce properties, such as abc.abstractproperty. Add 367 | # to this list to register other decorators that produce valid properties. 368 | property-classes=abc.abstractproperty 369 | 370 | # Naming style matching correct variable names 371 | variable-naming-style=snake_case 372 | 373 | # Regular expression matching correct variable names. Overrides variable- 374 | # naming-style 375 | #variable-rgx= 376 | 377 | 378 | [IMPORTS] 379 | 380 | # Allow wildcard imports from modules that define __all__. 381 | allow-wildcard-with-all=no 382 | 383 | # Analyse import fallback blocks. This can be used to support both Python 2 and 384 | # 3 compatible code, which means that the block might have code that exists 385 | # only in one or another interpreter, leading to false positives when analysed. 386 | analyse-fallback-blocks=no 387 | 388 | # Deprecated modules which should not be used, separated by a comma 389 | deprecated-modules=optparse,tkinter.tix 390 | 391 | # Create a graph of external dependencies in the given file (report RP0402 must 392 | # not be disabled) 393 | ext-import-graph= 394 | 395 | # Create a graph of every (i.e. internal and external) dependencies in the 396 | # given file (report RP0402 must not be disabled) 397 | import-graph= 398 | 399 | # Create a graph of internal dependencies in the given file (report RP0402 must 400 | # not be disabled) 401 | int-import-graph= 402 | 403 | # Force import order to recognize a module as part of the standard 404 | # compatibility libraries. 405 | known-standard-library= 406 | 407 | # Force import order to recognize a module as part of a third party library. 408 | known-third-party=enchant 409 | 410 | 411 | [CLASSES] 412 | 413 | # List of method names used to declare (i.e. assign) instance attributes. 414 | defining-attr-methods=__init__, 415 | __new__, 416 | setUp 417 | 418 | # List of member names, which should be excluded from the protected access 419 | # warning. 420 | exclude-protected=_asdict, 421 | _fields, 422 | _replace, 423 | _source, 424 | _make 425 | 426 | # List of valid names for the first argument in a class method. 427 | valid-classmethod-first-arg=cls 428 | 429 | # List of valid names for the first argument in a metaclass class method. 430 | valid-metaclass-classmethod-first-arg=mcs 431 | 432 | 433 | [DESIGN] 434 | 435 | # Maximum number of arguments for function / method 436 | max-args=9 437 | 438 | # Maximum number of attributes for a class (see R0902). 439 | max-attributes=7 440 | 441 | # Maximum number of boolean expressions in a if statement 442 | max-bool-expr=5 443 | 444 | # Maximum number of branch for function / method body 445 | max-branches=12 446 | 447 | # Maximum number of locals for function / method body 448 | max-locals=20 449 | 450 | # Maximum number of parents for a class (see R0901). 451 | max-parents=7 452 | 453 | # Maximum number of public methods for a class (see R0904). 454 | max-public-methods=20 455 | 456 | # Maximum number of return / yield for function / method body 457 | max-returns=6 458 | 459 | # Maximum number of statements in function / method body 460 | max-statements=50 461 | 462 | # Minimum number of public methods for a class (see R0903). 463 | min-public-methods=2 464 | 465 | 466 | [EXCEPTIONS] 467 | 468 | # Exceptions that will emit a warning when being caught. Defaults to 469 | # "Exception" 470 | overgeneral-exceptions=Exception 471 | --------------------------------------------------------------------------------