Skip to content

Commit

Permalink
update animations to use pillow
Browse files Browse the repository at this point in the history
  • Loading branch information
gboeing committed Dec 27, 2016
1 parent c629bdd commit 1cd8637
Show file tree
Hide file tree
Showing 21 changed files with 255 additions and 137 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
examples/images/animation/*/*.png
examples/images/phase-animate/*/*.png
examples/images/cobweb-animate/*.png
.temp
run_tests.bat
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

**Python package for modeling, simulating, visualizing, and animating discrete nonlinear dynamical systems and chaos**

It uses pandas, numpy, and numba for fast simulation, and matplotlib for beautiful visualizations and animations to explore system behavior. Compatible with Python 2/3.
pynamical uses pandas, numpy, and numba for fast simulation, and matplotlib for beautiful visualizations and animations to explore system behavior. Compatible with Python 2+3.

You can read/cite the journal article about pynamical: Boeing, G. 2016. "[Visual Analysis of Nonlinear Dynamical Systems: Chaos, Fractals, Self-Similarity and the Limits of Prediction](http://geoffboeing.com/publications/nonlinear-chaos-fractals-prediction/)." *Systems*, 4 (4), 37. doi:10.3390/systems4040037.

Expand All @@ -28,7 +28,7 @@ pip install pynamical

First load pynamical. Then simulate some model and visualize its bifurcation diagram in just 2 lines of code:

```
```python
from pynamical import logistic_map, simulate, bifurcation_plot
pops = simulate(model=logistic_map, num_gens=100, rate_min=0, rate_max=4, num_rates=1000, num_discard=100)
bifurcation_plot(pops)
Expand All @@ -38,15 +38,15 @@ bifurcation_plot(pops)

Zoom into a slice of this bifurcation diagram to see its [fractal structure](examples/pynamical-demo-logistic-model.ipynb):

```
```python
pops = simulate(model=logistic_map, num_gens=100, rate_min=3.7, rate_max=3.9, num_rates=1000, num_discard=100)
bifurcation_plot(pops, xmin=3.7, xmax=3.9)
```
![](examples/images/readme/logistic-map-bifurcation-3.png)

Plot a two-dimensional [phase diagram](examples/pynamical-demo-phase-diagrams.ipynb) of the logistic map:

```
```python
from pynamical import phase_diagram
pops = simulate(model=logistic_map, num_gens=4000, rate_min=3.6, rate_max=4.0, num_rates=50, num_discard=100)
phase_diagram(pops, xmin=0.25, xmax=0.75, ymin=0.8, ymax=1.01, size=7, color='viridis')
Expand All @@ -56,7 +56,7 @@ phase_diagram(pops, xmin=0.25, xmax=0.75, ymin=0.8, ymax=1.01, size=7, color='vi

Or a three-dimensional phase diagram of the [cubic map](examples/pynamical-demo-other-models.ipynb):

```
```python
from pynamical import cubic_map, phase_diagram_3d
pops = simulate(model=cubic_map, num_gens=3000, rate_min=3.5, num_rates=30, num_discard=100)
phase_diagram_3d(pops, xmin=-1, xmax=1, ymin=-1, ymax=1, zmin=-1, zmax=1, alpha=0.2, color='viridis', azim=330)
Expand All @@ -66,11 +66,11 @@ phase_diagram_3d(pops, xmin=-1, xmax=1, ymin=-1, ymax=1, zmin=-1, zmax=1, alpha=

Animate the 3D phase diagram of the logistic map [to reveal](examples/pynamical-demo-3d-animation.ipynb) the strange attractor's structure:

![](examples/images/animation/logistic-3d-phase-diagram-chaotic-regime.gif)
![](examples/images/phase-animate/05-logistic-3d-phase-diagram-chaotic-regime.gif)

Animate a cobweb plot of the logistic map's parameter space [to explore](examples/pynamical-demo-cobweb-plots.ipynb) sensitivity and behavior:

![](examples/images/cobweb-animate/animated-logistic-cobweb.gif)
![](examples/images/animated-logistic-cobweb.gif)

## For more info:
- [Read the journal article](http://geoffboeing.com/publications/nonlinear-chaos-fractals-prediction/)
Expand Down
Binary file added examples/images/animated-logistic-cobweb.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed examples/images/animation/pan-rotate-zoom-demo.gif
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
149 changes: 91 additions & 58 deletions examples/pynamical-demo-3d-animation.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@
"\n",
"Author: Geoff Boeing\n",
"\n",
"\n",
"Journal article: http://geoffboeing.com/publications/nonlinear-chaos-fractals-prediction/\n",
"Journal article: Boeing, G. 2016. \"[Visual Analysis of Nonlinear Dynamical Systems: Chaos, Fractals, Self-Similarity and the Limits of Prediction](http://geoffboeing.com/publications/nonlinear-chaos-fractals-prediction/).\" *Systems*, 4 (4), 37. doi:10.3390/systems4040037.\n",
"\n",
"Blog post: http://geoffboeing.com/2015/04/animated-3d-plots-python/\n",
"\n",
"This notebook demonstrates how to make animated GIFs that pan and zoom around 3-D phase diagrams to visualize fractal data sets, strange attractors, and chaos. If you get errors from images2gif, check out [this page](https://stackoverflow.com/questions/19149643/error-in-images2gif-py-with-globalpalette) or replace images2.gif.py with [this version](https://github.com/rec/echomesh/blob/master/code/python/external/images2gif.py). If you're on Python 3 you may need [this version](https://github.com/isaacgerg/images2gif) of images2gif."
"This notebook demonstrates how to make animated GIFs that pan and zoom around 3-D phase diagrams to visualize fractal data sets, strange attractors, and chaos."
]
},
{
Expand All @@ -25,9 +24,8 @@
"outputs": [],
"source": [
"from pynamical import simulate, phase_diagram_3d, title_font, label_font\n",
"import pandas as pd, numpy as np, matplotlib.pyplot as plt, random, glob, IPython.display as IPdisplay\n",
"from PIL import Image as PIL_Image\n",
"from images2gif import writeGif\n",
"import pandas as pd, numpy as np, matplotlib.pyplot as plt, random, glob, os, IPython.display as IPdisplay\n",
"from PIL import Image\n",
"%matplotlib inline"
]
},
Expand All @@ -39,7 +37,7 @@
},
"outputs": [],
"source": [
"save_folder = 'images/animation/'"
"save_folder = 'images/phase-animate'"
]
},
{
Expand All @@ -59,7 +57,7 @@
{
"data": {
"text/html": [
"<img src=\"images/animation/pan-rotate-zoom-demo.gif\"/>"
"<img src=\"images/phase-animate/01-pan-rotate-zoom-demo.gif\"/>"
],
"text/plain": [
"<IPython.core.display.Image object>"
Expand All @@ -72,7 +70,11 @@
],
"source": [
"# set a filename, run the logistic model, and create the plot\n",
"gif_filename = 'pan-rotate-zoom-demo'\n",
"gif_filename = '01-pan-rotate-zoom-demo'\n",
"working_folder = '{}/{}'.format(save_folder, gif_filename)\n",
"if not os.path.exists(working_folder):\n",
" os.makedirs(working_folder)\n",
" \n",
"pops = simulate(num_gens=1000, rate_min=3.99, num_rates=1)\n",
"fig, ax = phase_diagram_3d(pops, remove_ticks=False, show=False, save=False)\n",
"\n",
Expand All @@ -99,17 +101,20 @@
" ax.dist = dist_range[int(azimuth/(360./steps))]\n",
" \n",
" # set the figure title to the viewing perspective, and save each figure as a .png\n",
" fig.suptitle('elev=' + str(round(ax.elev,1)) + ', azim=' + str(round(ax.azim,1)) + ', dist=' + str(round(ax.dist,1)))\n",
" plt.savefig(save_folder + gif_filename + '/img' + str(azimuth).zfill(3) + '.png')\n",
" fig.suptitle('elev={:.1f}, azim={:.1f}, dist={:.1f}'.format(ax.elev, ax.azim, ax.dist))\n",
" plt.savefig('{}/{}/img{:03d}.png'.format(save_folder, gif_filename, azimuth))\n",
" \n",
"# don't display the static plot...\n",
"plt.close()\n",
"\n",
"# ...instead, create an animated gif of all the frames, then display it inline\n",
"images = [PIL_Image.open(image) for image in glob.glob(save_folder + gif_filename + '/*.png')]\n",
"file_path_name = save_folder + gif_filename + '.gif'\n",
"writeGif(file_path_name, images, duration=0.2)\n",
"IPdisplay.Image(url=file_path_name)"
"# load all the static images into a list then save as an animated gif\n",
"gif_filepath = '{}/{}.gif'.format(save_folder, gif_filename)\n",
"images = [Image.open(image) for image in glob.glob('{}/*.png'.format(working_folder))]\n",
"gif = images[0]\n",
"gif.info['duration'] = 20 #milliseconds per frame\n",
"gif.info['loop'] = 0 #how many times to loop (0=infinite)\n",
"gif.save(fp=gif_filepath, format='gif', save_all=True, append_images=images[1:])\n",
"IPdisplay.Image(url=gif_filepath)"
]
},
{
Expand All @@ -129,7 +134,7 @@
{
"data": {
"text/html": [
"<img src=\"images/animation/pan-rotate-logistic-phase-diagram.gif\"/>"
"<img src=\"images/phase-animate/02-pan-rotate-logistic-phase-diagram.gif\"/>"
],
"text/plain": [
"<IPython.core.display.Image object>"
Expand All @@ -142,7 +147,11 @@
],
"source": [
"# set a filename, run the logistic model, and create the plot\n",
"gif_filename = 'pan-rotate-logistic-phase-diagram'\n",
"gif_filename = '02-pan-rotate-logistic-phase-diagram'\n",
"working_folder = '{}/{}'.format(save_folder, gif_filename)\n",
"if not os.path.exists(working_folder):\n",
" os.makedirs(working_folder)\n",
" \n",
"pops = simulate(num_gens=1000, rate_min=3.99, num_rates=1)\n",
"fig, ax = phase_diagram_3d(pops, color='#003399', xlabel='Population (t)', ylabel='Population (t + 1)', zlabel='', \n",
" show=False, save=False)\n",
Expand Down Expand Up @@ -179,16 +188,19 @@
" \n",
" # add a figure title to each plot then save the figure to the disk\n",
" fig.suptitle('Logistic Map, r=3.99', fontsize=16, x=0.5, y=0.85)\n",
" plt.savefig(save_folder + gif_filename + '/img' + str(n).zfill(3) + '.png', bbox_inches='tight')\n",
" plt.savefig('{}/{}/img{:03d}.png'.format(save_folder, gif_filename, n), bbox_inches='tight')\n",
"\n",
"# don't display the static plot\n",
"plt.close()\n",
"\n",
"# create an animated gif of all the 3-D plot perspectives then display it inline\n",
"images = [PIL_Image.open(image) for image in glob.glob(save_folder + gif_filename + '/*.png')]\n",
"file_path_name = save_folder + gif_filename + '.gif'\n",
"writeGif(file_path_name, images, duration=0.1)\n",
"IPdisplay.Image(url=file_path_name)"
"# load all the static images into a list then save as an animated gif\n",
"gif_filepath = '{}/{}.gif'.format(save_folder, gif_filename)\n",
"images = [Image.open(image) for image in glob.glob('{}/*.png'.format(working_folder))]\n",
"gif = images[0]\n",
"gif.info['duration'] = 10 #milliseconds per frame\n",
"gif.info['loop'] = 0 #how many times to loop (0=infinite)\n",
"gif.save(fp=gif_filepath, format='gif', save_all=True, append_images=images[1:])\n",
"IPdisplay.Image(url=gif_filepath)"
]
},
{
Expand Down Expand Up @@ -221,39 +233,39 @@
" <tr>\n",
" <th>995</th>\n",
" <td>0.247214</td>\n",
" <td>0.324087</td>\n",
" <td>0.563377</td>\n",
" </tr>\n",
" <tr>\n",
" <th>996</th>\n",
" <td>0.742535</td>\n",
" <td>0.091102</td>\n",
" <td>0.453418</td>\n",
" </tr>\n",
" <tr>\n",
" <th>997</th>\n",
" <td>0.762795</td>\n",
" <td>0.001077</td>\n",
" <td>0.384585</td>\n",
" </tr>\n",
" <tr>\n",
" <th>998</th>\n",
" <td>0.721947</td>\n",
" <td>0.206058</td>\n",
" <td>0.304216</td>\n",
" </tr>\n",
" <tr>\n",
" <th>999</th>\n",
" <td>0.800951</td>\n",
" <td>0.786185</td>\n",
" <td>0.272667</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" chaos random\n",
"995 0.247214 0.324087\n",
"996 0.742535 0.091102\n",
"997 0.762795 0.001077\n",
"998 0.721947 0.206058\n",
"999 0.800951 0.786185"
"995 0.247214 0.563377\n",
"996 0.742535 0.453418\n",
"997 0.762795 0.384585\n",
"998 0.721947 0.304216\n",
"999 0.800951 0.272667"
]
},
"execution_count": 5,
Expand All @@ -280,7 +292,7 @@
{
"data": {
"text/html": [
"<img src=\"images/animation/pan-rotate-logistic-random.gif\"/>"
"<img src=\"images/phase-animate/03-pan-rotate-logistic-random.gif\"/>"
],
"text/plain": [
"<IPython.core.display.Image object>"
Expand All @@ -293,7 +305,11 @@
],
"source": [
"# set a filename and then create the plot\n",
"gif_filename = 'pan-rotate-logistic-random'\n",
"gif_filename = '03-pan-rotate-logistic-random'\n",
"working_folder = '{}/{}'.format(save_folder, gif_filename)\n",
"if not os.path.exists(working_folder):\n",
" os.makedirs(working_folder)\n",
" \n",
"fig, ax = phase_diagram_3d(pops, color=['#003399','#cc0000'], xlabel='Population (t)', ylabel='Population (t + 1)', \n",
" zlabel='', legend=True, legend_bbox_to_anchor=(0.94, 0.96), show=False, save=False)\n",
"\n",
Expand Down Expand Up @@ -329,16 +345,19 @@
" \n",
" # add a figure title to each plot then save the figure to the disk\n",
" fig.suptitle(u'3-D phase diagram, chaos vs random', fontsize=16, x=0.5, y=0.85)\n",
" plt.savefig(save_folder + gif_filename + '/img' + str(n).zfill(3) + '.png', bbox_inches='tight')\n",
" plt.savefig('{}/{}/img{:03d}.png'.format(save_folder, gif_filename, n), bbox_inches='tight')\n",
"\n",
"# don't display the static plot\n",
"plt.close()\n",
"\n",
"# create an animated gif of all the 3-D plot perspectives then display it inline\n",
"images = [PIL_Image.open(image) for image in glob.glob(save_folder + gif_filename + '/*.png')]\n",
"file_path_name = save_folder + gif_filename + '.gif'\n",
"writeGif(file_path_name, images, duration=0.1)\n",
"IPdisplay.Image(url=file_path_name)"
"# load all the static images into a list then save as an animated gif\n",
"gif_filepath = '{}/{}.gif'.format(save_folder, gif_filename)\n",
"images = [Image.open(image) for image in glob.glob('{}/*.png'.format(working_folder))]\n",
"gif = images[0]\n",
"gif.info['duration'] = 10 #milliseconds per frame\n",
"gif.info['loop'] = 0 #how many times to loop (0=infinite)\n",
"gif.save(fp=gif_filepath, format='gif', save_all=True, append_images=images[1:])\n",
"IPdisplay.Image(url=gif_filepath)"
]
},
{
Expand Down Expand Up @@ -370,7 +389,7 @@
{
"data": {
"text/html": [
"<img src=\"images/animation/pan-rotate-chaotic-regime.gif\"/>"
"<img src=\"images/phase-animate/04-pan-rotate-chaotic-regime.gif\"/>"
],
"text/plain": [
"<IPython.core.display.Image object>"
Expand All @@ -383,7 +402,11 @@
],
"source": [
"# set a filename and create the plot\n",
"gif_filename = 'pan-rotate-chaotic-regime'\n",
"gif_filename = '04-pan-rotate-chaotic-regime'\n",
"working_folder = '{}/{}'.format(save_folder, gif_filename)\n",
"if not os.path.exists(working_folder):\n",
" os.makedirs(working_folder)\n",
" \n",
"fig, ax = phase_diagram_3d(pops, color='viridis', color_reverse=False,\n",
" xlabel='Population (t)', ylabel='Population (t + 1)', zlabel='', \n",
" show=False, save=False)\n",
Expand Down Expand Up @@ -421,16 +444,19 @@
" \n",
" # add a figure title to each plot then save the figure to the disk\n",
" fig.suptitle('Logistic Map, r=3.6 to r=4.0', fontsize=16, x=0.5, y=0.85)\n",
" plt.savefig(save_folder + gif_filename + '/img' + str(n).zfill(3) + '.png', bbox_inches='tight')\n",
" plt.savefig('{}/{}/img{:03d}.png'.format(save_folder, gif_filename, n), bbox_inches='tight')\n",
"\n",
"# don't display the static plot\n",
"plt.close()\n",
"\n",
"# create an animated gif of all the 3-D plot perspectives then display it inline\n",
"images = [PIL_Image.open(image) for image in glob.glob(save_folder + gif_filename + '/*.png')]\n",
"file_path_name = save_folder + gif_filename + '.gif'\n",
"writeGif(file_path_name, images, duration=0.1)\n",
"IPdisplay.Image(url=file_path_name)"
"# load all the static images into a list then save as an animated gif\n",
"gif_filepath = '{}/{}.gif'.format(save_folder, gif_filename)\n",
"images = [Image.open(image) for image in glob.glob('{}/*.png'.format(working_folder))]\n",
"gif = images[0]\n",
"gif.info['duration'] = 10 #milliseconds per frame\n",
"gif.info['loop'] = 0 #how many times to loop (0=infinite)\n",
"gif.save(fp=gif_filepath, format='gif', save_all=True, append_images=images[1:])\n",
"IPdisplay.Image(url=gif_filepath)"
]
},
{
Expand Down Expand Up @@ -465,7 +491,7 @@
{
"data": {
"text/html": [
"<img src=\"images/animation/logistic-3d-phase-diagram-chaotic-regime.gif\"/>"
"<img src=\"images/phase-animate/05-logistic-3d-phase-diagram-chaotic-regime.gif\"/>"
],
"text/plain": [
"<IPython.core.display.Image object>"
Expand All @@ -478,7 +504,11 @@
],
"source": [
"# set a filename and create the plot\n",
"gif_filename = 'logistic-3d-phase-diagram-chaotic-regime'\n",
"gif_filename = '05-logistic-3d-phase-diagram-chaotic-regime'\n",
"working_folder = '{}/{}'.format(save_folder, gif_filename)\n",
"if not os.path.exists(working_folder):\n",
" os.makedirs(working_folder)\n",
" \n",
"fig, ax = phase_diagram_3d(pops, color='viridis', color_reverse=False, show=False, save=False)\n",
"\n",
"# configure the initial viewing perspective\n",
Expand Down Expand Up @@ -507,16 +537,19 @@
" \n",
" # add a figure title to each plot then save the figure to the disk\n",
" fig.suptitle('Logistic Map, r=3.6 to r=4.0', fontsize=16, x=0.5, y=0.85)\n",
" plt.savefig(save_folder + gif_filename + '/img' + str(n).zfill(3) + '.png', bbox_inches='tight')\n",
" plt.savefig('{}/{}/img{:03d}.png'.format(save_folder, gif_filename, n), bbox_inches='tight')\n",
"\n",
"# don't display the static plot\n",
"plt.close()\n",
"\n",
"# create an animated gif of all the 3-D plot perspectives then display it inline\n",
"images = [PIL_Image.open(image) for image in glob.glob(save_folder + gif_filename + '/*.png')]\n",
"file_path_name = save_folder + gif_filename + '.gif'\n",
"writeGif(file_path_name, images, duration=0.1)\n",
"IPdisplay.Image(url=file_path_name)"
"# load all the static images into a list then save as an animated gif\n",
"gif_filepath = '{}/{}.gif'.format(save_folder, gif_filename)\n",
"images = [Image.open(image) for image in glob.glob('{}/*.png'.format(working_folder))]\n",
"gif = images[0]\n",
"gif.info['duration'] = 10 #milliseconds per frame\n",
"gif.info['loop'] = 0 #how many times to loop (0=infinite)\n",
"gif.save(fp=gif_filepath, format='gif', save_all=True, append_images=images[1:])\n",
"IPdisplay.Image(url=gif_filepath)"
]
},
{
Expand Down
Loading

0 comments on commit 1cd8637

Please sign in to comment.