├── ipythonblocks
├── test
│ ├── __init__.py
│ ├── test_misc.py
│ ├── test_colors.py
│ ├── test_pixel.py
│ ├── test_images.py
│ ├── test_imagegrid.py
│ ├── test_block.py
│ ├── test_ipborg.py
│ └── test_blockgrid.py
├── __init__.py
└── ipythonblocks.py
├── setup.cfg
├── MANIFEST.in
├── .coveragerc
├── test_requirements.txt
├── .travis.yml
├── LICENSE.txt
├── setup.py
├── CODE_OF_CONDUCT.md
├── demos
├── learning_colors.ipynb
├── ipythonblocks_animation.ipynb
└── ipythonblocks_imagegrid.ipynb
├── .gitignore
├── CHANGES.rst
├── README.rst
└── ez_setup.py
/ipythonblocks/test/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal=1
3 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst
2 | include ez_setup.py
3 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | include = ipythonblocks/ipythonblocks.py
3 |
--------------------------------------------------------------------------------
/test_requirements.txt:
--------------------------------------------------------------------------------
1 | mock>=1.0
2 | responses>=0.1
3 | pillow>=2.4
4 | pytest>=2.3
5 |
--------------------------------------------------------------------------------
/ipythonblocks/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | ipythonblocks provides a BlockGrid class that displays a colored grid in the
3 | IPython Notebook. The colors can be manipulated, making it useful for
4 | practicing control flow stuctures and quickly seeing the results.
5 |
6 | """
7 |
8 | from .ipythonblocks import *
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: python
3 | python:
4 | - "2.7"
5 | - "3.4"
6 | - "3.5"
7 | - "3.6"
8 | install:
9 | - easy_install -U setuptools
10 | - pip install "ipython<6.0"
11 | - pip install .
12 | - pip install -r test_requirements.txt
13 | - pip install pytest-cov
14 | - pip install coveralls
15 | script:
16 | - py.test --cov ipythonblocks --cov-report term-missing
17 | after_success:
18 | - coveralls
19 |
--------------------------------------------------------------------------------
/ipythonblocks/test/test_misc.py:
--------------------------------------------------------------------------------
1 | """
2 | Test miscellaneous utility functions in the ipythonblocks module.
3 |
4 | """
5 |
6 | from .. import ipythonblocks
7 |
8 |
9 | def test_flatten():
10 | # single thing
11 | for x in ipythonblocks._flatten(1):
12 | assert x == 1
13 |
14 | # simple list
15 | thing = range(5)
16 | for i, x in enumerate(ipythonblocks._flatten(thing)):
17 | assert x == i
18 |
19 | # nested lists
20 | thing = [[0], [1, 2], [3], [4, 5, [6]]]
21 | for i, x in enumerate(ipythonblocks._flatten(thing)):
22 | assert x == i
23 |
24 |
--------------------------------------------------------------------------------
/ipythonblocks/test/test_colors.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests for ipythonblocks' color conveniences.
3 |
4 | """
5 |
6 | import pytest
7 |
8 | from .. import ipythonblocks
9 |
10 |
11 | def test_color():
12 | color = ipythonblocks.Color(1, 2, 3)
13 |
14 | assert color == (1, 2, 3)
15 | assert color.red == 1
16 | assert color.green == 2
17 | assert color.blue == 3
18 |
19 |
20 | @pytest.mark.parametrize(
21 | 'colors, color_name',
22 | [
23 | (ipythonblocks.colors, 'Crimson'),
24 | (ipythonblocks.fui_colors, 'Carrot'),
25 | ]
26 | )
27 | def test_colors(colors, color_name):
28 | assert isinstance(colors, ipythonblocks._ColorBunch)
29 | assert color_name in colors
30 | assert hasattr(colors, color_name)
31 | assert isinstance(colors[color_name], ipythonblocks.Color)
32 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | ipythonblocks
2 | Copyright (c) 2012 Matt Davis
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software
5 | and associated documentation files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
8 | Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all copies or
11 | substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from ez_setup import use_setuptools
3 | use_setuptools()
4 |
5 | from setuptools import setup, find_packages
6 |
7 | with open('README.rst') as f:
8 | long_description = f.read()
9 |
10 | setup(name='ipythonblocks',
11 | version='1.10.dev',
12 | description='Practice Python with colored grids in the IPython Notebook',
13 | long_description=long_description,
14 | author='Matt Davis',
15 | author_email='jiffyclub@gmail.com',
16 | url='https://github.com/jiffyclub/ipythonblocks/',
17 | packages=find_packages(exclude=['*.test']),
18 | classifiers=['License :: OSI Approved :: MIT License',
19 | 'Programming Language :: Python',
20 | 'Programming Language :: Python :: 2',
21 | 'Programming Language :: Python :: 2.7',
22 | 'Programming Language :: Python :: 3',
23 | 'Intended Audience :: Education',
24 | 'Topic :: Education'],
25 | install_requires=[
26 | 'ipython>=4.0',
27 | 'notebook>=4.0',
28 | 'requests>=1.0',
29 | ])
30 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 | ## Version 0.4
3 |
4 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
5 |
6 | If any participant in this project has issues or takes exception with a contribution, they are obligated to provide constructive feedback and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
7 |
8 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
9 |
10 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
11 |
12 | We promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, ability or disability, ethnicity, religion, or level of experience.
13 |
--------------------------------------------------------------------------------
/ipythonblocks/test/test_pixel.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pytest
3 |
4 | from .. import ipythonblocks
5 |
6 |
7 | @pytest.fixture
8 | def basic_pixel():
9 | return ipythonblocks.Pixel(5, 6, 7, size=20)
10 |
11 |
12 | def test_xy(basic_pixel):
13 | """
14 | Test the .x and .y attributes.
15 |
16 | """
17 | bp = basic_pixel
18 |
19 | assert bp.x is None
20 | assert bp.y is None
21 |
22 | bp._row = 1
23 | bp._col = 2
24 |
25 | assert bp.x == 2
26 | assert bp.y == 1
27 |
28 |
29 | def test_td(basic_pixel):
30 | """
31 | Test the Pixel._td proerty that returns an HTML table cell.
32 |
33 | """
34 | bp = basic_pixel
35 |
36 | bp._row = 1
37 | bp._col = 2
38 |
39 | title = ipythonblocks._TITLE.format(bp._col, bp._row,
40 | bp.red, bp.green, bp.blue)
41 | rgb = ipythonblocks._RGB.format(bp.red, bp.green, bp.blue)
42 | td = ipythonblocks._TD.format(title, bp.size, rgb)
43 |
44 | assert bp._td == td
45 |
46 |
47 | def test_str1(basic_pixel):
48 | """
49 | Test the Block.__str__ method used with print.
50 |
51 | """
52 | bp = basic_pixel
53 |
54 | s = os.linesep.join(['Pixel', 'Color: (5, 6, 7)'])
55 |
56 | assert bp.__str__() == s
57 |
58 |
59 | def test_str2(basic_pixel):
60 | """
61 | Test the Block.__str__ method used with print.
62 |
63 | """
64 | bp = basic_pixel
65 | bp._row = 8
66 | bp._col = 9
67 |
68 | s = os.linesep.join(['Pixel [9, 8]', 'Color: (5, 6, 7)'])
69 |
70 | assert bp.__str__() == s
71 |
--------------------------------------------------------------------------------
/ipythonblocks/test/test_images.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests for the ability to save images of grids.
3 |
4 | """
5 | import os
6 | import tempfile
7 |
8 | import mock
9 | import pytest
10 | from PIL import Image
11 |
12 | from ..ipythonblocks import BlockGrid, ImageGrid, colors
13 |
14 |
15 | @pytest.fixture
16 | def bg():
17 | grid = BlockGrid(2, 2, block_size=1, lines_on=False)
18 | grid[0, 0] = colors.Yellow
19 | grid[0, 1] = colors.Blue
20 | grid[1, 0] = colors.Red
21 | grid[1, 1] = colors.Green
22 | return grid
23 |
24 |
25 | @pytest.fixture
26 | def ig():
27 | grid = ImageGrid(2, 2, block_size=1, lines_on=False, origin='lower-left')
28 | grid[0, 0] = colors.Red
29 | grid[0, 1] = colors.Yellow
30 | grid[1, 0] = colors.Green
31 | grid[1, 1] = colors.Blue
32 | return grid
33 |
34 |
35 | @pytest.fixture
36 | def bg_png(request):
37 | name = tempfile.NamedTemporaryFile(suffix='.png').name
38 |
39 | def fin():
40 | os.remove(name)
41 | request.addfinalizer(fin)
42 |
43 | return name
44 |
45 |
46 | @pytest.fixture
47 | def ig_png(request):
48 | name = tempfile.NamedTemporaryFile(suffix='.png').name
49 |
50 | def fin():
51 | os.remove(name)
52 | request.addfinalizer(fin)
53 |
54 | return name
55 |
56 |
57 | def test_fixtures_equal(bg, ig):
58 | assert bg == ig
59 |
60 |
61 | def test_save_image(bg, ig, bg_png, ig_png):
62 | bg.save_image(bg_png)
63 | ig.save_image(ig_png)
64 |
65 | bg_im = Image.open(bg_png)
66 | ig_im = Image.open(ig_png)
67 |
68 | for im in (bg_im, ig_im):
69 | assert im.format == 'PNG'
70 | assert im.size == (bg.width, bg.height)
71 |
72 | for bg_block, bg_pix, ig_pix in zip(bg, bg_im.getdata(), ig_im.getdata()):
73 | assert bg_block.rgb == bg_pix
74 | assert bg_block.rgb == ig_pix
75 |
76 |
77 | def test_show_image(bg, ig):
78 | from .. import ipythonblocks
79 |
80 | with mock.patch.object(ipythonblocks, 'display') as display:
81 | bg.show_image()
82 | display.assert_called_once()
83 |
84 | with mock.patch.object(ipythonblocks, 'display') as display:
85 | ig.show_image()
86 | display.assert_called_once()
87 |
--------------------------------------------------------------------------------
/demos/learning_colors.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": {
3 | "name": "learning_colors"
4 | },
5 | "nbformat": 3,
6 | "nbformat_minor": 0,
7 | "worksheets": [
8 | {
9 | "cells": [
10 | {
11 | "cell_type": "heading",
12 | "level": 1,
13 | "metadata": {},
14 | "source": [
15 | "Learning Colors with show_color and embed_colorpicker"
16 | ]
17 | },
18 | {
19 | "cell_type": "markdown",
20 | "metadata": {},
21 | "source": [
22 | "A prerequisite to playing with [`ipythonblocks`](https://github.com/jiffyclub/ipythonblocks) is understanding how RGB colors work. To make it easy for students to experiment with different combinations of RGB `ipythonblocks` includes a `show_color` function that just shows a big stripe of a given color."
23 | ]
24 | },
25 | {
26 | "cell_type": "code",
27 | "collapsed": false,
28 | "input": [
29 | "from ipythonblocks import show_color, embed_colorpicker"
30 | ],
31 | "language": "python",
32 | "metadata": {},
33 | "outputs": [],
34 | "prompt_number": 1
35 | },
36 | {
37 | "cell_type": "code",
38 | "collapsed": false,
39 | "input": [
40 | "show_color(12, 123, 234)"
41 | ],
42 | "language": "python",
43 | "metadata": {},
44 | "outputs": [
45 | {
46 | "html": [
47 | "
"
48 | ],
49 | "output_type": "display_data",
50 | "text": [
51 | ""
52 | ]
53 | }
54 | ],
55 | "prompt_number": 2
56 | },
57 | {
58 | "cell_type": "markdown",
59 | "metadata": {},
60 | "source": [
61 | "If students are expected to have an internet connection the `embed_colorpicker` function can be used to embed the website [ColorPicker.com](http://www.colorpicker.com/) in the Notebook."
62 | ]
63 | },
64 | {
65 | "cell_type": "code",
66 | "collapsed": false,
67 | "input": [
68 | "embed_colorpicker()"
69 | ],
70 | "language": "python",
71 | "metadata": {},
72 | "outputs": [
73 | {
74 | "html": [
75 | ""
76 | ],
77 | "output_type": "display_data",
78 | "text": [
79 | ""
80 | ]
81 | }
82 | ],
83 | "prompt_number": 3
84 | },
85 | {
86 | "cell_type": "code",
87 | "collapsed": false,
88 | "input": [],
89 | "language": "python",
90 | "metadata": {},
91 | "outputs": []
92 | }
93 | ],
94 | "metadata": {}
95 | }
96 | ]
97 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/python,visualstudiocode,jupyternotebooks
3 | # Edit at https://www.gitignore.io/?templates=python,visualstudiocode,jupyternotebooks
4 |
5 | ### JupyterNotebooks ###
6 | # gitignore template for Jupyter Notebooks
7 | # website: http://jupyter.org/
8 |
9 | .ipynb_checkpoints
10 | */.ipynb_checkpoints/*
11 |
12 | # Remove previous ipynb_checkpoints
13 | # git rm -r .ipynb_checkpoints/
14 | #
15 |
16 | ### Python ###
17 | # Byte-compiled / optimized / DLL files
18 | __pycache__/
19 | *.py[cod]
20 | *$py.class
21 |
22 | # C extensions
23 | *.so
24 |
25 | # Distribution / packaging
26 | .Python
27 | build/
28 | develop-eggs/
29 | dist/
30 | downloads/
31 | eggs/
32 | .eggs/
33 | lib/
34 | lib64/
35 | parts/
36 | sdist/
37 | var/
38 | wheels/
39 | pip-wheel-metadata/
40 | share/python-wheels/
41 | *.egg-info/
42 | .installed.cfg
43 | *.egg
44 | MANIFEST
45 |
46 | # PyInstaller
47 | # Usually these files are written by a python script from a template
48 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
49 | *.manifest
50 | *.spec
51 |
52 | # Installer logs
53 | pip-log.txt
54 | pip-delete-this-directory.txt
55 |
56 | # Unit test / coverage reports
57 | htmlcov/
58 | .tox/
59 | .nox/
60 | .coverage
61 | .coverage.*
62 | .cache
63 | nosetests.xml
64 | coverage.xml
65 | *.cover
66 | .hypothesis/
67 | .pytest_cache/
68 |
69 | # Translations
70 | *.mo
71 | *.pot
72 |
73 | # Django stuff:
74 | *.log
75 | local_settings.py
76 | db.sqlite3
77 | db.sqlite3-journal
78 |
79 | # Flask stuff:
80 | instance/
81 | .webassets-cache
82 |
83 | # Scrapy stuff:
84 | .scrapy
85 |
86 | # Sphinx documentation
87 | docs/_build/
88 |
89 | # PyBuilder
90 | target/
91 |
92 | # Jupyter Notebook
93 |
94 | # IPython
95 | profile_default/
96 | ipython_config.py
97 |
98 | # pyenv
99 | .python-version
100 |
101 | # pipenv
102 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
103 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
104 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
105 | # install all needed dependencies.
106 | #Pipfile.lock
107 |
108 | # celery beat schedule file
109 | celerybeat-schedule
110 |
111 | # SageMath parsed files
112 | *.sage.py
113 |
114 | # Environments
115 | .env
116 | .venv
117 | env/
118 | venv/
119 | ENV/
120 | env.bak/
121 | venv.bak/
122 |
123 | # Spyder project settings
124 | .spyderproject
125 | .spyproject
126 |
127 | # Rope project settings
128 | .ropeproject
129 |
130 | # mkdocs documentation
131 | /site
132 |
133 | # mypy
134 | .mypy_cache/
135 | .dmypy.json
136 | dmypy.json
137 |
138 | # Pyre type checker
139 | .pyre/
140 |
141 | ### VisualStudioCode ###
142 | .vscode/*
143 | !.vscode/settings.json
144 | !.vscode/tasks.json
145 | !.vscode/launch.json
146 | !.vscode/extensions.json
147 |
148 | ### VisualStudioCode Patch ###
149 | # Ignore all local history of files
150 | .history
151 |
152 | # End of https://www.gitignore.io/api/python,visualstudiocode,jupyternotebooks
153 |
--------------------------------------------------------------------------------
/CHANGES.rst:
--------------------------------------------------------------------------------
1 | v 1.9
2 | =====
3 |
4 | * Added ``show_color_triple`` function as a convenience for displaying
5 | (red, green, blue) triplets without breaking them out into components
6 | * Added ``Color`` wrapper for colors in the ``colors`` and ``fui_colors``
7 | dictionaries to support ``.red``, ``.green``, and ``.blue`` attribute
8 | access for the component colors
9 |
10 | v 1.8
11 | =====
12 |
13 | * Add ``clear`` function
14 | * Use ``clear_output(wait=True)`` to avoid flickering in animations
15 |
16 | v 1.7
17 | =====
18 |
19 | * Added ``show_image`` and ``save_image`` for embedding an image in the
20 | Notebook and saving an image to a file, respectively.
21 |
22 | v 1.6
23 | =====
24 |
25 | * Added ``post_to_web`` and ``from_web`` methods for for communication
26 | with ipythonblocks.org.
27 | * Added a ``display_time`` keyword to the ``flash()`` method.
28 | * Changed the ``animate`` property to a method so that it can take
29 | a ``stop_time`` keyword to control the amount of time between loop steps.
30 | * Fixed an error with improperly set attributes on views of grids.
31 | * Fixed negative indexing in lower-left origin ImageGrids.
32 | * Fixed an error that allowed too-large indices in lower-left origin ImageGrids.
33 | * Allow color assignment from Blocks/Pixels, not just tuples. (Thanks @wolever.)
34 |
35 | v 1.5
36 | =====
37 |
38 | * Fixed an issue with an index of -1 in a 2D slice.
39 | * Fixed a display issue with show_color on Firefox.
40 | * Added a dictionary of HTML colors called ``colors``
41 |
42 | v 1.4
43 | =====
44 |
45 | * The lines between cells can be toggled on and off. Turning the lines
46 | off can sometimes improve the aesthetics of grids, especially at small
47 | block sizes.
48 | * Added a ``BlockGrid.to_text`` method for easily sending grid data to a file.
49 | * Added a ``show_color`` function that just shows a stripe of a given color.
50 | * Added an ``embed_colorpicker`` function that embeds the website
51 | http://www.colorpicker.com/ in the Notebook.
52 |
53 | v 1.3
54 | =====
55 |
56 | * Added ``BlockGrid.animate`` attribute and ``BlockGrid.flash`` method
57 | to facilitate animation of grid changes in the Notebook.
58 | * Added `Pixel` and `ImageGrid` subclasses that behave more like image
59 | manipulation libraries.
60 | * Added cell titles so hover the mouse over a block displays the block
61 | index and color.
62 |
63 | v 1.2
64 | =====
65 |
66 | * ``Block.row`` and ``Block.col`` are now set by their containing
67 | ``BlockGrid`` at the time the ``Block`` is returned to the user.
68 | This means that while iterating over a subgrid the first ``Block``
69 | will have coordinates (0, 0).
70 | * Getting a single row from a ``BlockGrid`` will return a new ``BlockGrid``
71 | * Raise a TypeError when indexing a ``BlockGrid`` with a length one tuple
72 | * Added a ``Block.colors`` property
73 |
74 | v 1.1.1
75 | =======
76 |
77 | * Convert assigned floating point numbers to integers for HTML compatibility
78 | * Added the ability to change the size of individual blocks
79 |
80 | v 1.1
81 | =====
82 |
83 | * Python 3 support (thanks to Thomas Kluyver)
84 | * ``for block in grid`` style iteration
85 | * ``Block`` now has an HTML representation for displaying individual blocks
86 | * ``__str__`` methods on ``Block`` and ``BlockGrid``
87 | * Ability to control block size via ``block_size`` keyword and attribute
88 | on ``BlockGrid``
89 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ``ipythonblocks``
2 | =================
3 |
4 | .. image:: https://travis-ci.org/jiffyclub/ipythonblocks.png?branch=master
5 | :alt: Travis-CI
6 | :target: https://travis-ci.org/jiffyclub/ipythonblocks
7 |
8 | .. image:: https://coveralls.io/repos/jiffyclub/ipythonblocks/badge.png
9 | :alt: Coveralls
10 | :target: https://coveralls.io/r/jiffyclub/ipythonblocks
11 |
12 | .. image:: https://img.shields.io/pypi/v/ipythonblocks.svg
13 | :alt: PyPI
14 | :target: https://pypi.python.org/pypi/ipythonblocks
15 |
16 | ``ipythonblocks`` is a teaching tool for use with the IPython_ Notebook.
17 | It provides a ``BlockGrid`` object whose representation is an HTML table.
18 | Individual table cells are represented by ``Block`` objects that have ``.red``,
19 | ``.green``, and ``.blue`` attributes by which the color of that cell can be
20 | specified.
21 |
22 | ``ipythonblocks`` allows students to experiment with Python flow control concepts
23 | and immediately see the effects of their code represented in a colorful,
24 | attractive way. ``BlockGrid`` objects can be indexed and sliced like 2D NumPy
25 | arrays making them good practice for learning how to access arrays.
26 |
27 | See these demo notebooks for more on using ``ipythonblocks``:
28 |
29 | * `Practicing with RGB`_
30 | * `Basic usage`_ with explanations
31 | * `Fun examples`_
32 | * `Animation`_
33 | * `ImageGrid`_
34 | * Going from a `JPEG to BlockGrid`_ and text
35 | * A programatic `firework animation`_
36 | * `Using ipythonblocks.org`_
37 |
38 | Install
39 | -------
40 |
41 | ``ipythonblocks`` can be installed with ``pip``::
42 |
43 | pip install ipythonblocks
44 |
45 | However, the package is contained in a single ``.py`` file and if you prefer
46 | you can just grab `ipythonblocks.py`_ and copy it to wherever you
47 | want to use it (useful for packaging with other teaching materials).
48 |
49 | Dependencies
50 | ------------
51 |
52 | Required dependencies:
53 |
54 | * Python_ >= 2.7
55 | * IPython_
56 |
57 | Optional dependencies:
58 |
59 | * requests_ >= 1.0 (for posting and getting to/from `ipythonblocks.org`_)
60 | * Pillow_ (for creating images)
61 |
62 | Testing dependencies:
63 |
64 | * pytest_ >= 2.3, (for the test suite, see below)
65 | * responses_ >= 0.1
66 | * mock_ (dependency of responses)
67 |
68 | Demo dependencies:
69 |
70 | * PIL_ (for ``starry_night_to_text.ipynb``)
71 |
72 |
73 | Testing
74 | -------
75 |
76 | The test suite is written using pytest_, so you can run the test suite
77 | with::
78 |
79 | py.test
80 |
81 | .. _IPython: http://ipython.org
82 | .. _Practicing with RGB: http://nbviewer.ipython.org/urls/raw.github.com/jiffyclub/ipythonblocks/master/demos/learning_colors.ipynb
83 | .. _Basic usage: http://nbviewer.ipython.org/urls/raw.github.com/jiffyclub/ipythonblocks/master/demos/ipythonblocks_demo.ipynb
84 | .. _Fun examples: http://nbviewer.ipython.org/urls/raw.github.com/jiffyclub/ipythonblocks/master/demos/ipythonblocks_fun.ipynb
85 | .. _Animation: http://nbviewer.ipython.org/urls/raw.github.com/jiffyclub/ipythonblocks/master/demos/ipythonblocks_animation.ipynb
86 | .. _ImageGrid: http://nbviewer.ipython.org/urls/raw.github.com/jiffyclub/ipythonblocks/master/demos/ipythonblocks_imagegrid.ipynb
87 | .. _JPEG to BlockGrid: http://nbviewer.ipython.org/urls/raw.github.com/jiffyclub/ipythonblocks/master/demos/starry_night_to_text.ipynb
88 | .. _firework animation: http://nbviewer.ipython.org/urls/raw.github.com/jiffyclub/ipythonblocks/master/demos/Firework.ipynb
89 | .. _Using ipythonblocks.org: http://nbviewer.ipython.org/urls/raw.github.com/jiffyclub/ipythonblocks/master/demos/ipythonblocks_org_demo.ipynb
90 | .. _ipythonblocks.py: https://github.com/jiffyclub/ipythonblocks/blob/master/ipythonblocks/ipythonblocks.py
91 | .. _Python: http://python.org/
92 | .. _pytest: http://pytest.org/
93 | .. _requests: http://docs.python-requests.org/en/latest/
94 | .. _PIL: http://www.pythonware.com/products/pil/
95 | .. _Pillow: https://python-pillow.org/
96 | .. _responses: https://github.com/dropbox/responses
97 | .. _mock: http://www.voidspace.org.uk/python/mock/
98 | .. _ipythonblocks.org: http://www.ipythonblocks.org
99 |
--------------------------------------------------------------------------------
/ipythonblocks/test/test_imagegrid.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .. import ipythonblocks
4 |
5 |
6 | @pytest.fixture
7 | def upper_left():
8 | return ipythonblocks.ImageGrid(2, 3, (7, 8, 9), 20, True, 'upper-left')
9 |
10 |
11 | @pytest.fixture
12 | def lower_left():
13 | return ipythonblocks.ImageGrid(2, 3, (7, 8, 9), 20, True, 'lower-left')
14 |
15 |
16 | def test_init_bad_origin():
17 | """
18 | Test for an error with a bad origin keyword.
19 |
20 | """
21 | with pytest.raises(ValueError):
22 | ipythonblocks.ImageGrid(5, 6, origin='nowhere')
23 |
24 |
25 | def test_basic_api(upper_left, lower_left):
26 | """
27 | Test basic interfaces different from BlockGrid.
28 |
29 | """
30 | ul = upper_left
31 | ll = lower_left
32 |
33 | assert ul.origin == 'upper-left'
34 | assert ll.origin == 'lower-left'
35 |
36 | with pytest.raises(AttributeError):
37 | ul.block_size = 50
38 |
39 |
40 | def test_getitem_bad_index_ul(upper_left):
41 | ul = upper_left
42 |
43 | with pytest.raises(IndexError):
44 | ul[1]
45 |
46 | with pytest.raises(IndexError):
47 | ul[1:]
48 |
49 | with pytest.raises(IndexError):
50 | ul[0, 3]
51 |
52 | with pytest.raises(IndexError):
53 | ul[2, 0]
54 |
55 |
56 | def test_getitem_bad_index_ll(lower_left):
57 | ll = lower_left
58 |
59 | with pytest.raises(IndexError):
60 | ll[1]
61 |
62 | with pytest.raises(IndexError):
63 | ll[1:]
64 |
65 | with pytest.raises(IndexError):
66 | ll[0, 3]
67 |
68 | with pytest.raises(IndexError):
69 | ll[2, 0]
70 |
71 |
72 | def test_setitem_bad_index(upper_left):
73 | ul = upper_left
74 |
75 | with pytest.raises(IndexError):
76 | ul[1] = (4, 5, 6)
77 |
78 | with pytest.raises(IndexError):
79 | ul[1:] = (4, 5, 6)
80 |
81 |
82 | def test_getitem_upper_left_single(upper_left):
83 | ul = upper_left
84 |
85 | for row in range(ul.height):
86 | for col in range(ul.width):
87 | assert ul[col, row] is ul._grid[row][col]
88 |
89 |
90 | def test_getitem_upper_left_slice(upper_left):
91 | ul = upper_left
92 |
93 | ng = ul[:1, :2]
94 |
95 | assert ng.width == 1
96 | assert ng.height == 2
97 | assert ng._grid == [[ul._grid[0][0]], [ul._grid[1][0]]]
98 |
99 |
100 | def test_getitem_lower_left_single(lower_left):
101 | ll = lower_left
102 |
103 | for row in range(ll.height):
104 | for col in range(ll.width):
105 | trow = ll.height - row - 1
106 | assert ll[col, row] is ll._grid[trow][col]
107 |
108 |
109 | def test_getitem_lower_left_single_neg(lower_left):
110 | ll = lower_left
111 |
112 | ll[1, 2] = (1, 2, 3)
113 |
114 | pix = ll[-1, -1]
115 |
116 | assert pix.red == 1
117 | assert pix.green == 2
118 | assert pix.blue == 3
119 |
120 |
121 | def test_getitem_lower_left_slice(lower_left):
122 | ll = lower_left
123 |
124 | ng = ll[:1, :2]
125 |
126 | assert ng.width == 1
127 | assert ng.height == 2
128 | assert ng._grid == [[ll._grid[-2][0]], [ll._grid[-1][0]]]
129 |
130 |
131 | def test_setitem_lower_left_single(lower_left):
132 | ll = lower_left
133 |
134 | ll[0, 1].set_colors(201, 202, 203)
135 |
136 | assert ll._grid[-2][0].red == 201
137 | assert ll._grid[-2][0].green == 202
138 | assert ll._grid[-2][0].blue == 203
139 |
140 |
141 | def test_setitem_lower_left_slice(lower_left):
142 | ll = lower_left
143 |
144 | ll[:, ::2] = (201, 202, 203)
145 |
146 | for pix in ll._grid[0]:
147 | assert pix.red == 201
148 | assert pix.green == 202
149 | assert pix.blue == 203
150 |
151 | for pix in ll._grid[2]:
152 | assert pix.red == 201
153 | assert pix.green == 202
154 | assert pix.blue == 203
155 |
156 |
157 | def test_slice_assignment(lower_left):
158 | ll = lower_left
159 |
160 | ll[1, 1] = (42, 42, 42)
161 | ll[0, 0] = ll[1, 1]
162 | assert ll[0, 0].rgb == (42, 42, 42)
163 |
164 |
165 | def test_setitem_with_grid_ul(upper_left):
166 | og = upper_left.copy()
167 | og[:, :] = (4, 5, 6)
168 |
169 | upper_left[:1, :2] = og[-1:, -2:]
170 |
171 | for b in upper_left:
172 | if b.col < 1 and b.row < 2:
173 | assert b.rgb == (4, 5, 6)
174 | else:
175 | assert b.rgb == (7, 8, 9)
176 |
177 |
178 | def test_setitem_with_grid_ll(lower_left):
179 | og = lower_left.copy()
180 | og[:, :] = (4, 5, 6)
181 |
182 | lower_left[:1, :2] = og[-1:, -2:]
183 |
184 | for b in lower_left:
185 | if b.col < 1 and b.row < 2:
186 | assert b.rgb == (4, 5, 6)
187 | else:
188 | assert b.rgb == (7, 8, 9)
189 |
--------------------------------------------------------------------------------
/ipythonblocks/test/test_block.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pytest
3 |
4 | from .. import ipythonblocks
5 |
6 |
7 | @pytest.fixture
8 | def basic_block():
9 | return ipythonblocks.Block(5, 6, 7, size=20)
10 |
11 |
12 | def test_basic_api(basic_block):
13 | """
14 | Check that inputs are going to the right attributes and that assignment
15 | works when it should and not when it shouldn't.
16 |
17 | """
18 | bb = basic_block
19 |
20 | assert bb.rgb == (5, 6, 7)
21 |
22 | assert bb.red == 5
23 | bb.red = 1
24 | assert bb.red == 1
25 |
26 | assert bb.green == 6
27 | bb.green = 2
28 | assert bb.green == 2
29 |
30 | assert bb.blue == 7
31 | bb.blue = 3
32 | assert bb.blue == 3
33 |
34 | assert bb.rgb == (1, 2, 3)
35 |
36 | assert bb.size == 20
37 | bb.size = 10
38 | assert bb.size == 10
39 |
40 | assert bb.row is None
41 | with pytest.raises(AttributeError):
42 | bb.row = 5
43 |
44 | assert bb.col is None
45 | with pytest.raises(AttributeError):
46 | bb.col = 5
47 |
48 |
49 | def test_attribute_limits(basic_block):
50 | """
51 | Color and size attributes have some builtin limits, test that they
52 | are respected.
53 |
54 | """
55 | bb = basic_block
56 |
57 | bb.red = -50
58 | assert bb.red == 0
59 |
60 | bb.green = 1000
61 | assert bb.green == 255
62 |
63 | bb.size = -200
64 | assert bb.size == ipythonblocks._SMALLEST_BLOCK
65 |
66 |
67 | def test_check_value(basic_block):
68 | """
69 | Test the Block._check_value method that enforces color limits,
70 | converts to int, and checks values are numbers.
71 |
72 | """
73 | bb = basic_block
74 |
75 | bb.red = 4.56
76 | assert isinstance(bb.red, int)
77 | assert bb.red == 5
78 |
79 | bb.blue = 200.1
80 | assert isinstance(bb.blue, int)
81 | assert bb.blue == 200
82 |
83 | with pytest.raises(ipythonblocks.InvalidColorSpec):
84 | bb.green = 'green'
85 |
86 |
87 | def test_set_colors(basic_block):
88 | """
89 | Test the Block.set_colors method.
90 |
91 | """
92 | bb = basic_block
93 |
94 | bb.set_colors(200, 201, 202)
95 |
96 | assert bb.red == 200
97 | assert bb.green == 201
98 | assert bb.blue == 202
99 |
100 |
101 | def test_rgb_attr(basic_block):
102 | """
103 | Test out the .rgb attribute.
104 |
105 | """
106 | bb = basic_block
107 |
108 | assert bb.rgb == (5, 6, 7)
109 |
110 | bb.rgb = (1, 2, 3)
111 | assert bb.rgb == (1, 2, 3)
112 | assert bb._red == 1
113 | assert bb._green == 2
114 | assert bb._blue == 3
115 |
116 | with pytest.raises(ValueError):
117 | bb.rgb = (1, 2)
118 |
119 | with pytest.raises(ValueError):
120 | bb.rgb = (4, 5, 6, 7, 8)
121 |
122 |
123 | def test_td(basic_block):
124 | """
125 | Test the Block._td proerty that returns an HTML table cell.
126 |
127 | """
128 | bb = basic_block
129 |
130 | bb._row = 1
131 | bb._col = 2
132 |
133 | title = ipythonblocks._TITLE.format(bb._row, bb._col,
134 | bb.red, bb.green, bb.blue)
135 | rgb = ipythonblocks._RGB.format(bb.red, bb.green, bb.blue)
136 | td = ipythonblocks._TD.format(title, bb.size, rgb)
137 |
138 | assert bb._td == td
139 |
140 |
141 | def test_repr_html(basic_block, monkeypatch):
142 | """
143 | Test the Block._repr_html_ method that returns a single cell HTML table.
144 |
145 | """
146 | from .test_blockgrid import uuid, fake_uuid
147 |
148 | bb = basic_block
149 |
150 | monkeypatch.setattr(uuid, 'uuid4', fake_uuid)
151 |
152 | table = ipythonblocks._TABLE.format(fake_uuid(), 0,
153 | ipythonblocks._TR.format(bb._td))
154 |
155 | assert bb._repr_html_() == table
156 |
157 |
158 | def test_str1(basic_block):
159 | """
160 | Test the Block.__str__ method used with print.
161 |
162 | """
163 | bb = basic_block
164 |
165 | s = os.linesep.join(['Block', 'Color: (5, 6, 7)'])
166 |
167 | assert bb.__str__() == s
168 |
169 |
170 | def test_str2(basic_block):
171 | """
172 | Test the Block.__str__ method used with print.
173 |
174 | """
175 | bb = basic_block
176 | bb._row = 8
177 | bb._col = 9
178 |
179 | s = os.linesep.join(['Block [8, 9]', 'Color: (5, 6, 7)'])
180 |
181 | assert bb.__str__() == s
182 |
183 | def test_repr(basic_block):
184 | assert repr(basic_block) == "Block(5, 6, 7, size=20)"
185 |
186 | def test_eq():
187 | b1 = ipythonblocks.Block(0, 0, 0)
188 | b2 = ipythonblocks.Block(0, 0, 0)
189 | b3 = ipythonblocks.Block(1, 1, 1)
190 | b4 = ipythonblocks.Block(0, 0, 0, size=30)
191 |
192 | assert b1 == b1
193 | assert b1 == b2
194 | assert b1 != b3
195 | assert b1 != b4
196 | assert b1 != 42
197 |
198 | def test_hash(basic_block):
199 | with pytest.raises(TypeError):
200 | set([basic_block])
201 |
202 | def test_update():
203 | b1 = ipythonblocks.Block(0, 0, 0)
204 | b2 = ipythonblocks.Block(1, 1, 1, size=30)
205 |
206 | b1._update((42, 42, 42))
207 | assert b1.rgb == (42, 42, 42)
208 |
209 | b1._update(b2)
210 | assert b1.rgb == b2.rgb
211 | assert b1.size == b2.size
212 |
213 | with pytest.raises(ValueError):
214 | b1._update((1, 2, 3, 4))
215 |
--------------------------------------------------------------------------------
/demos/ipythonblocks_animation.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": {
3 | "name": "ipythonblocks_animation"
4 | },
5 | "nbformat": 3,
6 | "nbformat_minor": 0,
7 | "worksheets": [
8 | {
9 | "cells": [
10 | {
11 | "cell_type": "heading",
12 | "level": 1,
13 | "metadata": {},
14 | "source": [
15 | "Animation with ipythonblocks"
16 | ]
17 | },
18 | {
19 | "cell_type": "markdown",
20 | "metadata": {},
21 | "source": [
22 | "For more on `ipythonblocks` see the home page at [https://github.com/jiffyclub/ipythonblocks](https://github.com/jiffyclub/ipythonblocks)."
23 | ]
24 | },
25 | {
26 | "cell_type": "code",
27 | "collapsed": false,
28 | "input": [
29 | "from ipythonblocks import BlockGrid"
30 | ],
31 | "language": "python",
32 | "metadata": {},
33 | "outputs": [],
34 | "prompt_number": 1
35 | },
36 | {
37 | "cell_type": "code",
38 | "collapsed": false,
39 | "input": [
40 | "import time\n",
41 | "from IPython.display import clear_output"
42 | ],
43 | "language": "python",
44 | "metadata": {},
45 | "outputs": [],
46 | "prompt_number": 2
47 | },
48 | {
49 | "cell_type": "markdown",
50 | "metadata": {},
51 | "source": [
52 | "It's possible to do animation of sorts using IPython's `clear_output` function."
53 | ]
54 | },
55 | {
56 | "cell_type": "code",
57 | "collapsed": false,
58 | "input": [
59 | "grid = BlockGrid(3, 3)\n",
60 | "\n",
61 | "previous_block = None\n",
62 | "\n",
63 | "for block in grid:\n",
64 | " clear_output()\n",
65 | " block.green = 255\n",
66 | " \n",
67 | " if previous_block:\n",
68 | " previous_block.green = 0\n",
69 | " \n",
70 | " grid.show()\n",
71 | " \n",
72 | " previous_block = block\n",
73 | " \n",
74 | " time.sleep(0.2)"
75 | ],
76 | "language": "python",
77 | "metadata": {},
78 | "outputs": [
79 | {
80 | "html": [
81 | ""
82 | ],
83 | "output_type": "display_data",
84 | "text": [
85 | ""
86 | ]
87 | }
88 | ],
89 | "prompt_number": 3
90 | },
91 | {
92 | "cell_type": "markdown",
93 | "metadata": {},
94 | "source": [
95 | "And this is so much fun that it's been added to `ipythonblocks`: one way is to use the `BlockGrid.animate()` method. The `.animate()` method takes\n",
96 | "an optional `stop_time` keyword to control the amount of time between loop steps."
97 | ]
98 | },
99 | {
100 | "cell_type": "code",
101 | "collapsed": false,
102 | "input": [
103 | "grid = BlockGrid(3, 3)\n",
104 | "\n",
105 | "previous_block = None\n",
106 | "\n",
107 | "for block in grid.animate():\n",
108 | " block.green = 255\n",
109 | " \n",
110 | " if previous_block:\n",
111 | " previous_block.green = 0\n",
112 | " \n",
113 | " previous_block = block"
114 | ],
115 | "language": "python",
116 | "metadata": {},
117 | "outputs": [
118 | {
119 | "html": [
120 | ""
121 | ],
122 | "output_type": "display_data",
123 | "text": [
124 | ""
125 | ]
126 | }
127 | ],
128 | "prompt_number": 4
129 | },
130 | {
131 | "cell_type": "markdown",
132 | "metadata": {},
133 | "source": [
134 | "And another way, if not iterating over the grid, is to use the `BlockGrid.flash()` method. `.flash()` takes an optional `display_time` keyword that\n",
135 | "controls for how long each frame is displayed. The default is 0.2 seconds."
136 | ]
137 | },
138 | {
139 | "cell_type": "code",
140 | "collapsed": false,
141 | "input": [
142 | "grid = BlockGrid(3, 3)\n",
143 | "\n",
144 | "previous_block = None\n",
145 | "\n",
146 | "indices = [[(0, 0), (0, 2), (2, 0), (2, 2)], [(0, 1), (1, 0), (1, 2), (2, 1)]] * 10\n",
147 | "\n",
148 | "for ind in indices:\n",
149 | " for i in ind:\n",
150 | " grid[i[0], i[1]].green = 255\n",
151 | " \n",
152 | " grid.flash(display_time=0.1)\n",
153 | " \n",
154 | " for i in ind:\n",
155 | " grid[i[0], i[1]].green = 0\n",
156 | "\n",
157 | "grid.show()"
158 | ],
159 | "language": "python",
160 | "metadata": {},
161 | "outputs": [
162 | {
163 | "html": [
164 | ""
165 | ],
166 | "output_type": "display_data",
167 | "text": [
168 | ""
169 | ]
170 | }
171 | ],
172 | "prompt_number": 5
173 | },
174 | {
175 | "cell_type": "code",
176 | "collapsed": false,
177 | "input": [],
178 | "language": "python",
179 | "metadata": {},
180 | "outputs": []
181 | }
182 | ],
183 | "metadata": {}
184 | }
185 | ]
186 | }
--------------------------------------------------------------------------------
/ipythonblocks/test/test_ipborg.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests for ipythonblocks communication with ipythonblocks.org.
3 |
4 | """
5 |
6 | import json
7 | import string
8 | import sys
9 |
10 | import mock
11 | import pytest
12 | import responses
13 |
14 | from .. import ipythonblocks as ipb
15 |
16 | A10 = [a for a in string.ascii_lowercase[:10]]
17 |
18 |
19 | def setup_module(module):
20 | """
21 | mock out the get_ipython() function for the tests.
22 |
23 | """
24 | def get_ipython():
25 | class ip(object):
26 | user_ns = {'In': A10}
27 | return ip()
28 | ipb.get_ipython = get_ipython
29 |
30 |
31 | def teardown_module(module):
32 | del ipb.get_ipython
33 |
34 |
35 | @pytest.fixture
36 | def data_2x2():
37 | return [[(1, 2, 3, 4), (5, 6, 7, 8)],
38 | [(9, 10, 11, 12), (13, 14, 15, 16)]]
39 |
40 |
41 | def block_grid(data_2x2):
42 | bg = ipb.BlockGrid(2, 2)
43 | bg[0, 0].rgb = data_2x2[0][0][:3]
44 | bg[0, 0].size = data_2x2[0][0][3]
45 | bg[0, 1].rgb = data_2x2[0][1][:3]
46 | bg[0, 1].size = data_2x2[0][1][3]
47 | bg[1, 0].rgb = data_2x2[1][0][:3]
48 | bg[1, 0].size = data_2x2[1][0][3]
49 | bg[1, 1].rgb = data_2x2[1][1][:3]
50 | bg[1, 1].size = data_2x2[1][1][3]
51 | return bg
52 |
53 |
54 | @pytest.fixture(name='block_grid')
55 | def block_grid_fixture(data_2x2):
56 | return block_grid(data_2x2)
57 |
58 |
59 | def image_grid_ll(data_2x2):
60 | ig = ipb.ImageGrid(2, 2, origin='lower-left')
61 | ig[0, 0].rgb = data_2x2[1][0][:3]
62 | ig[0, 0].size = data_2x2[1][0][3]
63 | ig[0, 1].rgb = data_2x2[0][0][:3]
64 | ig[0, 1].size = data_2x2[0][0][3]
65 | ig[1, 0].rgb = data_2x2[1][1][:3]
66 | ig[1, 0].size = data_2x2[1][1][3]
67 | ig[1, 1].rgb = data_2x2[0][1][:3]
68 | ig[1, 1].size = data_2x2[0][1][3]
69 | return ig
70 |
71 |
72 | @pytest.fixture(name='image_grid_ll')
73 | def image_grid_ll_fixture(data_2x2):
74 | return image_grid_ll(data_2x2)
75 |
76 |
77 | def image_grid_ul(data_2x2):
78 | ig = ipb.ImageGrid(2, 2, origin='upper-left')
79 | ig[0, 0].rgb = data_2x2[0][0][:3]
80 | ig[0, 0].size = data_2x2[0][0][3]
81 | ig[0, 1].rgb = data_2x2[1][0][:3]
82 | ig[0, 1].size = data_2x2[1][0][3]
83 | ig[1, 0].rgb = data_2x2[0][1][:3]
84 | ig[1, 0].size = data_2x2[0][1][3]
85 | ig[1, 1].rgb = data_2x2[1][1][:3]
86 | ig[1, 1].size = data_2x2[1][1][3]
87 | return ig
88 |
89 |
90 | @pytest.fixture(name='image_grid_ul')
91 | def image_grid_ul_fixture(data_2x2):
92 | return image_grid_ul(data_2x2)
93 |
94 |
95 | class Test_parse_cells_spec(object):
96 | def test_single_int(self):
97 | assert ipb._parse_cells_spec(5, 100) == [5]
98 |
99 | def test_single_int_str(self):
100 | assert ipb._parse_cells_spec('5', 100) == [5]
101 |
102 | def test_multi_int_str(self):
103 | assert ipb._parse_cells_spec('2,9,4', 100) == [2, 4, 9]
104 |
105 | def test_slice(self):
106 | assert ipb._parse_cells_spec(slice(2, 5), 100) == [2, 3, 4]
107 |
108 | def test_slice_str(self):
109 | assert ipb._parse_cells_spec('2:5', 100) == [2, 3, 4]
110 |
111 | def test_slice_and_int(self):
112 | assert ipb._parse_cells_spec('4,9:12', 100) == [4, 9, 10, 11]
113 | assert ipb._parse_cells_spec('9:12,4', 100) == [4, 9, 10, 11]
114 | assert ipb._parse_cells_spec('4,9:12,16', 100) == [4, 9, 10, 11, 16]
115 | assert ipb._parse_cells_spec('10,9:12', 100) == [9, 10, 11]
116 |
117 |
118 | class Test_get_code_cells(object):
119 | def test_single_int(self):
120 | assert ipb._get_code_cells(5) == [A10[5]]
121 |
122 | def test_single_int_str(self):
123 | assert ipb._get_code_cells('5') == [A10[5]]
124 |
125 | def test_multi_int_str(self):
126 | assert ipb._get_code_cells('2,9,4') == [A10[x] for x in [2, 4, 9]]
127 |
128 | def test_slice(self):
129 | assert ipb._get_code_cells(slice(2, 5)) == [A10[x] for x in [2, 3, 4]]
130 |
131 | def test_slice_str(self):
132 | assert ipb._get_code_cells('2:5') == [A10[x] for x in [2, 3, 4]]
133 |
134 | def test_slice_and_int(self):
135 | assert ipb._get_code_cells('1,3:6') == [A10[x] for x in [1, 3, 4, 5]]
136 | assert ipb._get_code_cells('3:6,1') == [A10[x] for x in [1, 3, 4, 5]]
137 | assert ipb._get_code_cells('1,3:6,8') == [A10[x] for x in [1, 3, 4, 5, 8]]
138 | assert ipb._get_code_cells('4,3:6') == [A10[x] for x in [3, 4, 5]]
139 |
140 |
141 | @pytest.mark.parametrize(
142 | 'fixture',
143 | [block_grid, image_grid_ll, image_grid_ul]
144 | )
145 | def test_to_simple_grid(fixture, data_2x2):
146 | grid = fixture(data_2x2)
147 | assert grid._to_simple_grid() == data_2x2
148 |
149 |
150 | @pytest.mark.parametrize(
151 | 'test_grid, ref_grid',
152 | [(ipb.BlockGrid(2, 2), block_grid),
153 | (ipb.ImageGrid(2, 2, origin='upper-left'), image_grid_ul),
154 | (ipb.ImageGrid(2, 2, origin='lower-left'), image_grid_ll)]
155 | )
156 | def test_load_simple_grid(test_grid, ref_grid, data_2x2):
157 | ref_grid = ref_grid(data_2x2)
158 | test_grid._load_simple_grid(data_2x2)
159 | assert test_grid == ref_grid
160 |
161 |
162 | @responses.activate
163 | @mock.patch.object(ipb, '__version__', 'ipb_version')
164 | @mock.patch.object(ipb, '_POST_URL', 'http://www.ipythonblocks.org/post_url')
165 | def test_BlockGrid_post_to_web(data_2x2, block_grid):
166 | expected = {
167 | 'python_version': tuple(sys.version_info),
168 | 'ipb_version': ipb.__version__,
169 | 'ipb_class': 'BlockGrid',
170 | 'code_cells': None,
171 | 'secret': False,
172 | 'grid_data': {
173 | 'lines_on': block_grid.lines_on,
174 | 'width': block_grid.width,
175 | 'height': block_grid.height,
176 | 'blocks': data_2x2
177 | }
178 | }
179 | expected = json.dumps(expected)
180 |
181 | responses.add(responses.POST, ipb._POST_URL,
182 | body=json.dumps({'url': 'url'}).encode('utf-8'),
183 | status=200, content_type='application/json')
184 |
185 | url = block_grid.post_to_web()
186 |
187 | assert url == 'url'
188 | assert len(responses.calls) == 1
189 |
190 | req = responses.calls[0].request
191 | assert req.url == ipb._POST_URL
192 | assert req.body == expected
193 |
194 |
195 | @responses.activate
196 | @mock.patch.object(ipb, '__version__', 'ipb_version')
197 | @mock.patch.object(ipb, '_POST_URL', 'http://www.ipythonblocks.org/post_url')
198 | def test_ImageGrid_ul_post_to_web(data_2x2, image_grid_ul):
199 | expected = {
200 | 'python_version': tuple(sys.version_info),
201 | 'ipb_version': ipb.__version__,
202 | 'ipb_class': 'ImageGrid',
203 | 'code_cells': None,
204 | 'secret': False,
205 | 'grid_data': {
206 | 'lines_on': image_grid_ul.lines_on,
207 | 'width': image_grid_ul.width,
208 | 'height': image_grid_ul.height,
209 | 'blocks': data_2x2
210 | }
211 | }
212 | expected = json.dumps(expected)
213 |
214 | responses.add(responses.POST, ipb._POST_URL,
215 | body=json.dumps({'url': 'url'}).encode('utf-8'),
216 | status=200, content_type='application/json')
217 |
218 | url = image_grid_ul.post_to_web()
219 |
220 | assert url == 'url'
221 | assert len(responses.calls) == 1
222 |
223 | req = responses.calls[0].request
224 | assert req.url == ipb._POST_URL
225 | assert req.body == expected
226 |
227 |
228 | @responses.activate
229 | @mock.patch.object(ipb, '_GET_URL_PUBLIC', 'http://www.ipythonblocks.org/get_url/{0}')
230 | def test_BlockGrid_from_web(data_2x2):
231 | grid_id = 'abc'
232 | get_url = ipb._GET_URL_PUBLIC.format(grid_id)
233 | resp = {
234 | 'lines_on': True,
235 | 'width': 2,
236 | 'height': 2,
237 | 'blocks': data_2x2
238 | }
239 |
240 | responses.add(responses.GET, get_url,
241 | body=json.dumps(resp).encode('utf-8'), status=200,
242 | content_type='application/json')
243 |
244 | grid = ipb.BlockGrid.from_web(grid_id)
245 |
246 | assert grid.height == resp['height']
247 | assert grid.width == resp['width']
248 | assert grid.lines_on == resp['lines_on']
249 | assert grid._to_simple_grid() == data_2x2
250 |
251 | assert len(responses.calls) == 1
252 | assert responses.calls[0].request.url == get_url
253 |
254 |
255 | @responses.activate
256 | @mock.patch.object(ipb, '_GET_URL_SECRET', 'http://www.ipythonblocks.org/get_url/{0}')
257 | def test_ImageGrid_ul_from_web(data_2x2):
258 | grid_id = 'abc'
259 | get_url = ipb._GET_URL_SECRET.format(grid_id)
260 | resp = {
261 | 'lines_on': True,
262 | 'width': 2,
263 | 'height': 2,
264 | 'blocks': data_2x2
265 | }
266 |
267 | responses.add(responses.GET, get_url,
268 | body=json.dumps(resp).encode('utf-8'), status=200,
269 | content_type='application/json')
270 |
271 | origin = 'upper-left'
272 | grid = ipb.ImageGrid.from_web(grid_id, secret=True, origin=origin)
273 |
274 | assert grid.height == resp['height']
275 | assert grid.width == resp['width']
276 | assert grid.lines_on == resp['lines_on']
277 | assert grid._to_simple_grid() == data_2x2
278 | assert grid.origin == origin
279 |
280 | assert len(responses.calls) == 1
281 | assert responses.calls[0].request.url == get_url
282 |
--------------------------------------------------------------------------------
/ipythonblocks/test/test_blockgrid.py:
--------------------------------------------------------------------------------
1 | import os
2 | import uuid
3 | import pytest
4 |
5 | from .. import ipythonblocks
6 |
7 |
8 | def fake_uuid():
9 | return 'abc'
10 |
11 |
12 | @pytest.fixture
13 | def basic_grid():
14 | return ipythonblocks.BlockGrid(5, 6, (1, 2, 3), 20, True)
15 |
16 |
17 | def test_basic_api(basic_grid):
18 | """
19 | Check that inputs are going to the right attributes and that assignment
20 | works when it should and not when it shouldn't.
21 |
22 | """
23 | bg = basic_grid
24 |
25 | assert bg.width == 5
26 | with pytest.raises(AttributeError):
27 | bg.width = 20
28 |
29 | assert bg.height == 6
30 | with pytest.raises(AttributeError):
31 | bg.height = 20
32 |
33 | assert bg.shape == (5, 6)
34 | assert bg.block_size == 20
35 | assert bg.lines_on is True
36 |
37 |
38 | def test_grid_init(basic_grid):
39 | """
40 | Test that the grid is properly initialized.
41 |
42 | """
43 | bg = basic_grid
44 |
45 | for r in range(bg.height):
46 | for c in range(bg.width):
47 | assert bg[r, c].size == 20
48 | assert bg[r, c].red == 1
49 | assert bg[r, c].green == 2
50 | assert bg[r, c].blue == 3
51 | assert bg[r, c].row == r
52 | assert bg[r, c].col == c
53 |
54 |
55 | def test_change_block_size(basic_grid):
56 | """
57 | Test that all blocks are properly resized when changing the
58 | BlockGrid.block_size attribute.
59 |
60 | """
61 | bg = basic_grid
62 |
63 | bg.block_size = 10
64 | assert bg.block_size == 10
65 |
66 | for block in bg:
67 | assert block.size == 10
68 |
69 |
70 | def test_change_lines_on(basic_grid):
71 | """
72 | Test changing the BlockGrid.lines_on attribute.
73 |
74 | """
75 | bg = basic_grid
76 |
77 | assert bg.lines_on is True
78 |
79 | bg.lines_on = False
80 | assert bg.lines_on is False
81 |
82 | with pytest.raises(ValueError):
83 | bg.lines_on = 5
84 |
85 | with pytest.raises(ValueError):
86 | bg.lines_on = 'asdf'
87 |
88 |
89 | def test_view(basic_grid):
90 | """
91 | Check that getting a new BlockGrid object via slicing returns a view
92 | and not a copy.
93 |
94 | """
95 | bg = basic_grid
96 | ng = bg[:2, :2]
97 |
98 | ng[1, 1].set_colors(200, 201, 202)
99 |
100 | for block in (ng[1, 1], bg[1, 1]):
101 | assert block.red == 200
102 | assert block.green == 201
103 | assert block.blue == 202
104 |
105 |
106 | def test_view_coords(basic_grid):
107 | """
108 | Make sure that when we get a view that it has its own appropriate
109 | coordinates.
110 |
111 | """
112 | ng = basic_grid[-2:, -2:]
113 |
114 | coords = ((0, 0), (0, 1), (1, 0), (1, 1))
115 |
116 | for b, c in zip(ng, coords):
117 | assert b.row == c[0]
118 | assert b.col == c[1]
119 |
120 |
121 | def test_copy(basic_grid):
122 | """
123 | Check that getting a new BlockGrid via BlockGrid.copy returns a totally
124 | independent copy and not a view.
125 |
126 | """
127 | bg = basic_grid
128 | ng = bg[:2, :2].copy()
129 |
130 | ng[1, 1].set_colors(200, 201, 202)
131 |
132 | assert ng[1, 1].red == 200
133 | assert ng[1, 1].green == 201
134 | assert ng[1, 1].blue == 202
135 | assert bg[1, 1].red == 1
136 | assert bg[1, 1].green == 2
137 | assert bg[1, 1].blue == 3
138 |
139 |
140 | def test_str(basic_grid):
141 | """
142 | Test the BlockGrid.__str__ method used with print.
143 |
144 | """
145 | bg = basic_grid
146 |
147 | s = os.linesep.join(['BlockGrid', 'Shape: (5, 6)'])
148 |
149 | assert bg.__str__() == s
150 |
151 |
152 | def test_repr_html(monkeypatch):
153 | """
154 | HTML repr should be the same for a 1, 1 BlockGrid as for a single Block.
155 | (As long as the BlockGrid border is off.)
156 |
157 | """
158 | bg = ipythonblocks.BlockGrid(1, 1, lines_on=False)
159 |
160 | monkeypatch.setattr(uuid, 'uuid4', fake_uuid)
161 |
162 | assert bg._repr_html_() == bg[0, 0]._repr_html_()
163 |
164 |
165 | def test_iter():
166 | """
167 | Test that we do complete, row first iteration.
168 |
169 | """
170 | bg = ipythonblocks.BlockGrid(2, 2)
171 |
172 | coords = ((0, 0), (0, 1), (1, 0), (1, 1))
173 |
174 | for b, c in zip(bg, coords):
175 | assert b.row == c[0]
176 | assert b.col == c[1]
177 |
178 |
179 | def test_bad_index(basic_grid):
180 | """
181 | Test for the correct errors with bad indices.
182 |
183 | """
184 | bg = basic_grid
185 |
186 | with pytest.raises(IndexError):
187 | bg[1, 2, 3, 4]
188 |
189 | with pytest.raises(IndexError):
190 | bg[{4: 5}]
191 |
192 | with pytest.raises(TypeError):
193 | bg[1, ]
194 |
195 | with pytest.raises(IndexError):
196 | bg[0, 5]
197 |
198 | with pytest.raises(IndexError):
199 | bg[6, 0]
200 |
201 |
202 | def test_bad_colors(basic_grid):
203 | """
204 | Make sure this gets the right error when trying to assign something
205 | other than three integers.
206 |
207 | """
208 | with pytest.raises(ValueError):
209 | basic_grid[0, 0] = (1, 2, 3, 4)
210 |
211 |
212 | def test_getitem(basic_grid):
213 | """
214 | Exercise a bunch of different indexing.
215 |
216 | """
217 | bg = basic_grid
218 |
219 | # single block
220 | block = bg[1, 2]
221 |
222 | assert isinstance(block, ipythonblocks.Block)
223 | assert block.row == 1
224 | assert block.col == 2
225 |
226 | # single row
227 | ng = bg[2]
228 |
229 | assert isinstance(ng, ipythonblocks.BlockGrid)
230 | assert ng.shape == (bg.width, 1)
231 |
232 | # two rows
233 | ng = bg[1:3]
234 |
235 | assert isinstance(ng, ipythonblocks.BlockGrid)
236 | assert ng.shape == (bg.width, 2)
237 |
238 | # one row via a slice
239 | ng = bg[2, :]
240 |
241 | assert isinstance(ng, ipythonblocks.BlockGrid)
242 | assert ng.shape == (bg.width, 1)
243 |
244 | # one column
245 | ng = bg[:, 2]
246 |
247 | assert isinstance(ng, ipythonblocks.BlockGrid)
248 | assert ng.shape == (1, bg.height)
249 |
250 | # 2 x 2 subgrid
251 | ng = bg[:2, :2]
252 |
253 | assert isinstance(ng, ipythonblocks.BlockGrid)
254 | assert ng.shape == (2, 2)
255 |
256 | # strided slicing
257 | ng = bg[::3, ::3]
258 |
259 | assert isinstance(ng, ipythonblocks.BlockGrid)
260 | assert ng.shape == (2, 2)
261 |
262 | # one column / one row with a -1 index
263 | # testing fix for #7
264 | ng = bg[-1, :]
265 |
266 | assert isinstance(ng, ipythonblocks.BlockGrid)
267 | assert ng.shape == (bg.width, 1)
268 |
269 | ng = bg[1:4, -1]
270 |
271 | assert isinstance(ng, ipythonblocks.BlockGrid)
272 | assert ng.shape == (1, 3)
273 |
274 |
275 | def test_setitem(basic_grid):
276 | """
277 | Test assigning colors to blocks.
278 |
279 | """
280 | bg = basic_grid
281 | colors = (21, 22, 23)
282 |
283 | # single block
284 | bg[0, 0] = colors
285 | assert bg[0, 0].rgb == colors
286 |
287 | # single row
288 | bg[2] = colors
289 | for block in bg[2]:
290 | assert block.rgb == colors
291 |
292 | # two rows
293 | bg[3:5] = colors
294 | for block in bg[3:5]:
295 | assert block.rgb == colors
296 |
297 | # one row via a slice
298 | bg[1, :] = colors
299 | for block in bg[1, :]:
300 | assert block.rgb == colors
301 |
302 | # one column
303 | bg[:, 5] = colors
304 | for block in bg[:, 5]:
305 | assert block.rgb == colors
306 |
307 | # 2 x 2 subgrid
308 | bg[:2, :2] = colors
309 | for block in bg[:2, :2]:
310 | assert block.rgb == colors
311 |
312 | # strided slicing
313 | bg[::3, ::3] = colors
314 | for block in bg[::3, ::3]:
315 | assert block.rgb == colors
316 |
317 |
318 | def test_setitem_to_block(basic_grid):
319 | """
320 | Test assigning a Block to a BlockGrid.
321 | """
322 | bg = basic_grid
323 | bg[0, 0] = (0, 0, 0)
324 | bg[1, 1] = bg[0, 0]
325 | assert bg[0, 0] == bg[1, 1]
326 | assert bg[1, 1].rgb == (0, 0, 0)
327 |
328 |
329 | def test_setitem_with_grid(basic_grid):
330 | og = basic_grid.copy()
331 | og[:] = (4, 5, 6)
332 |
333 | basic_grid[:1, :2] = og[-1:, -2:]
334 |
335 | for b in basic_grid:
336 | if b.row < 1 and b.col < 2:
337 | assert b.rgb == (4, 5, 6)
338 | else:
339 | assert b.rgb == (1, 2, 3)
340 |
341 |
342 | def test_setitem_raises(basic_grid):
343 | og = basic_grid.copy()
344 |
345 | with pytest.raises(ipythonblocks.ShapeMismatch):
346 | basic_grid[:, :] = og[:2, :2]
347 |
348 | with pytest.raises(TypeError):
349 | basic_grid[0, 0] = og[:2, :2]
350 |
351 |
352 | def test_to_text(capsys):
353 | """
354 | Test using the BlockGrid.to_text method.
355 |
356 | """
357 | bg = ipythonblocks.BlockGrid(2, 1, block_size=20)
358 |
359 | bg[0, 0].rgb = (1, 2, 3)
360 | bg[0, 1].rgb = (4, 5, 6)
361 |
362 | ref = ['# width height',
363 | '2 1',
364 | '# block size',
365 | '20',
366 | '# initial color',
367 | '0 0 0',
368 | '# row column red green blue',
369 | '0 0 1 2 3',
370 | '0 1 4 5 6']
371 | ref = os.linesep.join(ref) + os.linesep
372 |
373 | bg.to_text()
374 | out, err = capsys.readouterr()
375 |
376 | assert out == ref
377 |
--------------------------------------------------------------------------------
/ez_setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Bootstrap setuptools installation
3 |
4 | To use setuptools in your package's setup.py, include this
5 | file in the same directory and add this to the top of your setup.py::
6 |
7 | from ez_setup import use_setuptools
8 | use_setuptools()
9 |
10 | To require a specific version of setuptools, set a download
11 | mirror, or use an alternate download directory, simply supply
12 | the appropriate options to ``use_setuptools()``.
13 |
14 | This file can also be run as a script to install or upgrade setuptools.
15 | """
16 | import os
17 | import shutil
18 | import sys
19 | import tempfile
20 | import tarfile
21 | import optparse
22 | import subprocess
23 | import platform
24 | import textwrap
25 |
26 | from distutils import log
27 |
28 | try:
29 | from site import USER_SITE
30 | except ImportError:
31 | USER_SITE = None
32 |
33 | DEFAULT_VERSION = "2.2"
34 | DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/"
35 |
36 | def _python_cmd(*args):
37 | """
38 | Return True if the command succeeded.
39 | """
40 | args = (sys.executable,) + args
41 | return subprocess.call(args) == 0
42 |
43 | def _install(tarball, install_args=()):
44 | # extracting the tarball
45 | tmpdir = tempfile.mkdtemp()
46 | log.warn('Extracting in %s', tmpdir)
47 | old_wd = os.getcwd()
48 | try:
49 | os.chdir(tmpdir)
50 | tar = tarfile.open(tarball)
51 | _extractall(tar)
52 | tar.close()
53 |
54 | # going in the directory
55 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
56 | os.chdir(subdir)
57 | log.warn('Now working in %s', subdir)
58 |
59 | # installing
60 | log.warn('Installing Setuptools')
61 | if not _python_cmd('setup.py', 'install', *install_args):
62 | log.warn('Something went wrong during the installation.')
63 | log.warn('See the error message above.')
64 | # exitcode will be 2
65 | return 2
66 | finally:
67 | os.chdir(old_wd)
68 | shutil.rmtree(tmpdir)
69 |
70 |
71 | def _build_egg(egg, tarball, to_dir):
72 | # extracting the tarball
73 | tmpdir = tempfile.mkdtemp()
74 | log.warn('Extracting in %s', tmpdir)
75 | old_wd = os.getcwd()
76 | try:
77 | os.chdir(tmpdir)
78 | tar = tarfile.open(tarball)
79 | _extractall(tar)
80 | tar.close()
81 |
82 | # going in the directory
83 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
84 | os.chdir(subdir)
85 | log.warn('Now working in %s', subdir)
86 |
87 | # building an egg
88 | log.warn('Building a Setuptools egg in %s', to_dir)
89 | _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
90 |
91 | finally:
92 | os.chdir(old_wd)
93 | shutil.rmtree(tmpdir)
94 | # returning the result
95 | log.warn(egg)
96 | if not os.path.exists(egg):
97 | raise IOError('Could not build the egg.')
98 |
99 |
100 | def _do_download(version, download_base, to_dir, download_delay):
101 | egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg'
102 | % (version, sys.version_info[0], sys.version_info[1]))
103 | if not os.path.exists(egg):
104 | tarball = download_setuptools(version, download_base,
105 | to_dir, download_delay)
106 | _build_egg(egg, tarball, to_dir)
107 | sys.path.insert(0, egg)
108 |
109 | # Remove previously-imported pkg_resources if present (see
110 | # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details).
111 | if 'pkg_resources' in sys.modules:
112 | del sys.modules['pkg_resources']
113 |
114 | import setuptools
115 | setuptools.bootstrap_install_from = egg
116 |
117 |
118 | def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
119 | to_dir=os.curdir, download_delay=15):
120 | to_dir = os.path.abspath(to_dir)
121 | rep_modules = 'pkg_resources', 'setuptools'
122 | imported = set(sys.modules).intersection(rep_modules)
123 | try:
124 | import pkg_resources
125 | except ImportError:
126 | return _do_download(version, download_base, to_dir, download_delay)
127 | try:
128 | pkg_resources.require("setuptools>=" + version)
129 | return
130 | except pkg_resources.DistributionNotFound:
131 | return _do_download(version, download_base, to_dir, download_delay)
132 | except pkg_resources.VersionConflict as VC_err:
133 | if imported:
134 | msg = textwrap.dedent("""
135 | The required version of setuptools (>={version}) is not available,
136 | and can't be installed while this script is running. Please
137 | install a more recent version first, using
138 | 'easy_install -U setuptools'.
139 |
140 | (Currently using {VC_err.args[0]!r})
141 | """).format(VC_err=VC_err, version=version)
142 | sys.stderr.write(msg)
143 | sys.exit(2)
144 |
145 | # otherwise, reload ok
146 | del pkg_resources, sys.modules['pkg_resources']
147 | return _do_download(version, download_base, to_dir, download_delay)
148 |
149 | def _clean_check(cmd, target):
150 | """
151 | Run the command to download target. If the command fails, clean up before
152 | re-raising the error.
153 | """
154 | try:
155 | subprocess.check_call(cmd)
156 | except subprocess.CalledProcessError:
157 | if os.access(target, os.F_OK):
158 | os.unlink(target)
159 | raise
160 |
161 | def download_file_powershell(url, target):
162 | """
163 | Download the file at url to target using Powershell (which will validate
164 | trust). Raise an exception if the command cannot complete.
165 | """
166 | target = os.path.abspath(target)
167 | cmd = [
168 | 'powershell',
169 | '-Command',
170 | "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars(),
171 | ]
172 | _clean_check(cmd, target)
173 |
174 | def has_powershell():
175 | if platform.system() != 'Windows':
176 | return False
177 | cmd = ['powershell', '-Command', 'echo test']
178 | devnull = open(os.path.devnull, 'wb')
179 | try:
180 | try:
181 | subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
182 | except:
183 | return False
184 | finally:
185 | devnull.close()
186 | return True
187 |
188 | download_file_powershell.viable = has_powershell
189 |
190 | def download_file_curl(url, target):
191 | cmd = ['curl', url, '--silent', '--output', target]
192 | _clean_check(cmd, target)
193 |
194 | def has_curl():
195 | cmd = ['curl', '--version']
196 | devnull = open(os.path.devnull, 'wb')
197 | try:
198 | try:
199 | subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
200 | except:
201 | return False
202 | finally:
203 | devnull.close()
204 | return True
205 |
206 | download_file_curl.viable = has_curl
207 |
208 | def download_file_wget(url, target):
209 | cmd = ['wget', url, '--quiet', '--output-document', target]
210 | _clean_check(cmd, target)
211 |
212 | def has_wget():
213 | cmd = ['wget', '--version']
214 | devnull = open(os.path.devnull, 'wb')
215 | try:
216 | try:
217 | subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
218 | except:
219 | return False
220 | finally:
221 | devnull.close()
222 | return True
223 |
224 | download_file_wget.viable = has_wget
225 |
226 | def download_file_insecure(url, target):
227 | """
228 | Use Python to download the file, even though it cannot authenticate the
229 | connection.
230 | """
231 | try:
232 | from urllib.request import urlopen
233 | except ImportError:
234 | from urllib2 import urlopen
235 | src = dst = None
236 | try:
237 | src = urlopen(url)
238 | # Read/write all in one block, so we don't create a corrupt file
239 | # if the download is interrupted.
240 | data = src.read()
241 | dst = open(target, "wb")
242 | dst.write(data)
243 | finally:
244 | if src:
245 | src.close()
246 | if dst:
247 | dst.close()
248 |
249 | download_file_insecure.viable = lambda: True
250 |
251 | def get_best_downloader():
252 | downloaders = [
253 | download_file_powershell,
254 | download_file_curl,
255 | download_file_wget,
256 | download_file_insecure,
257 | ]
258 |
259 | for dl in downloaders:
260 | if dl.viable():
261 | return dl
262 |
263 | def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
264 | to_dir=os.curdir, delay=15,
265 | downloader_factory=get_best_downloader):
266 | """Download setuptools from a specified location and return its filename
267 |
268 | `version` should be a valid setuptools version number that is available
269 | as an egg for download under the `download_base` URL (which should end
270 | with a '/'). `to_dir` is the directory where the egg will be downloaded.
271 | `delay` is the number of seconds to pause before an actual download
272 | attempt.
273 |
274 | ``downloader_factory`` should be a function taking no arguments and
275 | returning a function for downloading a URL to a target.
276 | """
277 | # making sure we use the absolute path
278 | to_dir = os.path.abspath(to_dir)
279 | tgz_name = "setuptools-%s.tar.gz" % version
280 | url = download_base + tgz_name
281 | saveto = os.path.join(to_dir, tgz_name)
282 | if not os.path.exists(saveto): # Avoid repeated downloads
283 | log.warn("Downloading %s", url)
284 | downloader = downloader_factory()
285 | downloader(url, saveto)
286 | return os.path.realpath(saveto)
287 |
288 |
289 | def _extractall(self, path=".", members=None):
290 | """Extract all members from the archive to the current working
291 | directory and set owner, modification time and permissions on
292 | directories afterwards. `path' specifies a different directory
293 | to extract to. `members' is optional and must be a subset of the
294 | list returned by getmembers().
295 | """
296 | import copy
297 | import operator
298 | from tarfile import ExtractError
299 | directories = []
300 |
301 | if members is None:
302 | members = self
303 |
304 | for tarinfo in members:
305 | if tarinfo.isdir():
306 | # Extract directories with a safe mode.
307 | directories.append(tarinfo)
308 | tarinfo = copy.copy(tarinfo)
309 | tarinfo.mode = 448 # decimal for oct 0700
310 | self.extract(tarinfo, path)
311 |
312 | # Reverse sort directories.
313 | directories.sort(key=operator.attrgetter('name'), reverse=True)
314 |
315 | # Set correct owner, mtime and filemode on directories.
316 | for tarinfo in directories:
317 | dirpath = os.path.join(path, tarinfo.name)
318 | try:
319 | self.chown(tarinfo, dirpath)
320 | self.utime(tarinfo, dirpath)
321 | self.chmod(tarinfo, dirpath)
322 | except ExtractError as e:
323 | if self.errorlevel > 1:
324 | raise
325 | else:
326 | self._dbg(1, "tarfile: %s" % e)
327 |
328 |
329 | def _build_install_args(options):
330 | """
331 | Build the arguments to 'python setup.py install' on the setuptools package
332 | """
333 | return ['--user'] if options.user_install else []
334 |
335 | def _parse_args():
336 | """
337 | Parse the command line for options
338 | """
339 | parser = optparse.OptionParser()
340 | parser.add_option(
341 | '--user', dest='user_install', action='store_true', default=False,
342 | help='install in user site package (requires Python 2.6 or later)')
343 | parser.add_option(
344 | '--download-base', dest='download_base', metavar="URL",
345 | default=DEFAULT_URL,
346 | help='alternative URL from where to download the setuptools package')
347 | parser.add_option(
348 | '--insecure', dest='downloader_factory', action='store_const',
349 | const=lambda: download_file_insecure, default=get_best_downloader,
350 | help='Use internal, non-validating downloader'
351 | )
352 | options, args = parser.parse_args()
353 | # positional arguments are ignored
354 | return options
355 |
356 | def main(version=DEFAULT_VERSION):
357 | """Install or upgrade setuptools and EasyInstall"""
358 | options = _parse_args()
359 | tarball = download_setuptools(download_base=options.download_base,
360 | downloader_factory=options.downloader_factory)
361 | return _install(tarball, _build_install_args(options))
362 |
363 | if __name__ == '__main__':
364 | sys.exit(main())
365 |
--------------------------------------------------------------------------------
/ipythonblocks/ipythonblocks.py:
--------------------------------------------------------------------------------
1 | """
2 | ipythonblocks provides a BlockGrid class that displays a colored grid in the
3 | IPython Notebook. The colors can be manipulated, making it useful for
4 | practicing control flow stuctures and quickly seeing the results.
5 |
6 | """
7 |
8 | # This file is copyright 2013 by Matt Davis and covered by the license at
9 | # https://github.com/jiffyclub/ipythonblocks/blob/master/LICENSE.txt
10 |
11 | import copy
12 | import collections
13 | import json
14 | import numbers
15 | import os
16 | import sys
17 | import time
18 | import uuid
19 |
20 | from collections import namedtuple
21 | from operator import iadd
22 | from functools import reduce
23 |
24 | from IPython.display import HTML, IFrame, display, clear_output
25 | from IPython.display import Image as ipyImage
26 |
27 | # This statement is required for compatibility issues with Python 3.10.
28 | # Since that version, Iterable and Sequence must be imported from
29 | # collections.abc, instead of collections
30 | try:
31 | from collections.abc import Iterable
32 | from collections.abc import Sequence
33 | except ImportError:
34 | from collections import Iterable
35 | from collections import Sequence
36 |
37 | __all__ = (
38 | 'Block',
39 | 'BlockGrid',
40 | 'Pixel',
41 | 'ImageGrid',
42 | 'InvalidColorSpec',
43 | 'ShapeMismatch',
44 | 'show_color',
45 | 'show_color_triple',
46 | 'embed_colorpicker',
47 | 'clear',
48 | 'colors',
49 | 'fui_colors',
50 | '__version__',
51 | )
52 | __version__ = '1.10.dev'
53 |
54 | _TABLE = (''
60 | '')
61 | _TR = '{0}
'
62 | _TD = (' | ')
64 | _RGB = 'rgb({0}, {1}, {2})'
65 | _TITLE = 'Index: [{0}, {1}]
Color: ({2}, {3}, {4})'
66 |
67 | _SINGLE_ITEM = 'single item'
68 | _SINGLE_ROW = 'single row'
69 | _ROW_SLICE = 'row slice'
70 | _DOUBLE_SLICE = 'double slice'
71 |
72 | _SMALLEST_BLOCK = 1
73 |
74 | _POST_URL = 'http://www.ipythonblocks.org/post'
75 | _GET_URL_PUBLIC = 'http://www.ipythonblocks.org/get/{0}'
76 | _GET_URL_SECRET = 'http://www.ipythonblocks.org/get/secret/{0}'
77 |
78 |
79 | class InvalidColorSpec(Exception):
80 | """
81 | Error for a color value that is not a number.
82 |
83 | """
84 | pass
85 |
86 |
87 | class ShapeMismatch(Exception):
88 | """
89 | Error for when a grid assigned to another doesn't have the same shape.
90 |
91 | """
92 | pass
93 |
94 |
95 | def clear():
96 | """
97 | Clear the output of the current cell.
98 |
99 | This is a thin wrapper around IPython.display.clear_output.
100 |
101 | """
102 | clear_output()
103 |
104 |
105 | def show_color(red, green, blue):
106 | """
107 | Show a given color in the IPython Notebook.
108 |
109 | Parameters
110 | ----------
111 | red, green, blue : int
112 | Integers on the range [0 - 255].
113 |
114 | """
115 | div = ('')
117 | display(HTML(div.format(_RGB.format(red, green, blue))))
118 |
119 |
120 | def show_color_triple(rgb_triple):
121 | """
122 | Show a given color in the IPython Notebook.
123 |
124 | Parameters
125 | ----------
126 | rgb_triple : iterable
127 | A Python iterable containing three integers in the range [0 - 255]
128 | representing red, green, and blue colors.
129 |
130 | """
131 | return show_color(*rgb_triple)
132 |
133 |
134 | def embed_colorpicker():
135 | """
136 | Embed the web page www.colorpicker.com inside the IPython Notebook.
137 |
138 | """
139 | display(
140 | IFrame(src='http://www.colorpicker.com/', height='550px', width='100%')
141 | )
142 |
143 |
144 | def _color_property(name):
145 | real_name = "_" + name
146 |
147 | @property
148 | def prop(self):
149 | return getattr(self, real_name)
150 |
151 | @prop.setter
152 | def prop(self, value):
153 | value = Block._check_value(value)
154 | setattr(self, real_name, value)
155 |
156 | return prop
157 |
158 |
159 | def _flatten(thing, ignore_types=(str,)):
160 | """
161 | Yield a single item or str/unicode or recursively yield from iterables.
162 |
163 | Adapted from Beazley's Python Cookbook.
164 |
165 | """
166 | if isinstance(thing, Iterable) and \
167 | not isinstance(thing, ignore_types):
168 | for i in thing:
169 | for x in _flatten(i):
170 | yield x
171 | else:
172 | yield thing
173 |
174 |
175 | def _parse_str_cell_spec(cells, length):
176 | """
177 | Parse a single string cell specification representing either a single
178 | integer or a slice.
179 |
180 | Parameters
181 | ----------
182 | cells : str
183 | E.g. '5' for an int or '5:9' for a slice.
184 | length : int
185 | The number of items in the user's In history list. Used for
186 | normalizing slices.
187 |
188 | Returns
189 | -------
190 | cell_nos : list of int
191 |
192 | """
193 | if ':' not in cells:
194 | return _parse_cells_spec(int(cells), length)
195 |
196 | else:
197 | return _parse_cells_spec(slice(*[int(x) if x else None
198 | for x in cells.split(':')]),
199 | length)
200 |
201 |
202 | def _parse_cells_spec(cells, length):
203 | """
204 | Used by _get_code_cells to parse a cell specification string into an
205 | ordered list of cell numbers.
206 |
207 | Parameters
208 | ----------
209 | cells : str, int, or slice
210 | Specification of which cells to retrieve. Can be a single number,
211 | a slice, or a combination of either separated by commas.
212 | length : int
213 | The number of items in the user's In history list. Used for
214 | normalizing slices.
215 |
216 | Returns
217 | -------
218 | cell_nos : list of int
219 | Ordered list of cell numbers derived from spec.
220 |
221 | """
222 | if isinstance(cells, int):
223 | return [cells]
224 |
225 | elif isinstance(cells, slice):
226 | return list(range(*cells.indices(length)))
227 |
228 | else:
229 | # string parsing
230 | return sorted(set(_flatten(_parse_str_cell_spec(s, length)
231 | for s in cells.split(','))))
232 |
233 |
234 | def _get_code_cells(cells):
235 | """
236 | Get the inputs of the specified cells from the notebook.
237 |
238 | Parameters
239 | ----------
240 | cells : str, int, or slice
241 | Specification of which cells to retrieve. Can be a single number,
242 | a slice, or a combination of either separated by commas.
243 |
244 | Returns
245 | -------
246 | code : list of str
247 | Contents of cells as strings in chronological order.
248 |
249 | """
250 | In = get_ipython().user_ns['In']
251 | cells = _parse_cells_spec(cells, len(In))
252 | return [In[x] for x in cells]
253 |
254 |
255 | class Block(object):
256 | """
257 | A colored square.
258 |
259 | Parameters
260 | ----------
261 | red, green, blue : int
262 | Integers on the range [0 - 255].
263 | size : int, optional
264 | Length of the sides of this block in pixels. One is the lower limit.
265 |
266 | Attributes
267 | ----------
268 | red, green, blue : int
269 | The color values for this `Block`. The color of the `Block` can be
270 | updated by assigning new values to these attributes.
271 | rgb : tuple of int
272 | Tuple of (red, green, blue) values. Can be used to set all the colors
273 | at once.
274 | row, col : int
275 | The zero-based grid position of this `Block`.
276 | size : int
277 | Length of the sides of this block in pixels. The block size can be
278 | changed by modifying this attribute. Note that one is the lower limit.
279 |
280 | """
281 |
282 | red = _color_property('red')
283 | green = _color_property('green')
284 | blue = _color_property('blue')
285 |
286 | def __init__(self, red, green, blue, size=20):
287 | self.red = red
288 | self.green = green
289 | self.blue = blue
290 | self.size = size
291 |
292 | self._row = None
293 | self._col = None
294 |
295 | @staticmethod
296 | def _check_value(value):
297 | """
298 | Check that a value is a number and constrain it to [0 - 255].
299 |
300 | """
301 | if not isinstance(value, numbers.Number):
302 | s = 'value must be a number. got {0}.'.format(value)
303 | raise InvalidColorSpec(s)
304 |
305 | return int(round(min(255, max(0, value))))
306 |
307 | @property
308 | def rgb(self):
309 | return (self._red, self._green, self._blue)
310 |
311 | @rgb.setter
312 | def rgb(self, colors):
313 | if len(colors) != 3:
314 | s = 'Setting colors requires three values: (red, green, blue).'
315 | raise ValueError(s)
316 |
317 | self.red, self.green, self.blue = colors
318 |
319 | @property
320 | def row(self):
321 | return self._row
322 |
323 | @property
324 | def col(self):
325 | return self._col
326 |
327 | @property
328 | def size(self):
329 | return self._size
330 |
331 | @size.setter
332 | def size(self, size):
333 | self._size = max(_SMALLEST_BLOCK, size)
334 |
335 | def set_colors(self, red, green, blue):
336 | """
337 | Updated block colors.
338 |
339 | Parameters
340 | ----------
341 | red, green, blue : int
342 | Integers on the range [0 - 255].
343 |
344 | """
345 | self.red = red
346 | self.green = green
347 | self.blue = blue
348 |
349 | def _update(self, other):
350 | if isinstance(other, Block):
351 | self.rgb = other.rgb
352 | self.size = other.size
353 | elif isinstance(other, Sequence) and len(other) == 3:
354 | self.rgb = other
355 | else:
356 | errmsg = (
357 | 'Value must be a Block or a sequence of 3 integers. '
358 | 'Got {0!r}.'
359 | )
360 | raise ValueError(errmsg.format(other))
361 |
362 | @property
363 | def _td(self):
364 | """
365 | The HTML for a table cell with the background color of this Block.
366 |
367 | """
368 | title = _TITLE.format(self._row, self._col,
369 | self._red, self._green, self._blue)
370 | rgb = _RGB.format(self._red, self._green, self._blue)
371 | return _TD.format(title, self._size, rgb)
372 |
373 | def _repr_html_(self):
374 | return _TABLE.format(uuid.uuid4(), 0, _TR.format(self._td))
375 |
376 | def show(self):
377 | display(HTML(self._repr_html_()))
378 |
379 | __hash__ = None
380 |
381 | def __eq__(self, other):
382 | if not isinstance(other, Block):
383 | return False
384 | return self.rgb == other.rgb and self.size == other.size
385 |
386 | def __str__(self):
387 | s = ['{0}'.format(self.__class__.__name__),
388 | 'Color: ({0}, {1}, {2})'.format(self._red,
389 | self._green,
390 | self._blue)]
391 |
392 | # add position information if we have it
393 | if self._row is not None:
394 | s[0] += ' [{0}, {1}]'.format(self._row, self._col)
395 |
396 | return os.linesep.join(s)
397 |
398 | def __repr__(self):
399 | type_name = type(self).__name__
400 | return '{0}({1}, {2}, {3}, size={4})'.format(type_name,
401 | self.red,
402 | self.green,
403 | self.blue,
404 | self.size)
405 |
406 |
407 | class BlockGrid(object):
408 | """
409 | A grid of blocks whose colors can be individually controlled.
410 |
411 | Parameters
412 | ----------
413 | width : int
414 | Number of blocks wide to make the grid.
415 | height : int
416 | Number of blocks high to make the grid.
417 | fill : tuple of int, optional
418 | An optional initial color for the grid, defaults to black.
419 | Specified as a tuple of (red, green, blue). E.g.: (10, 234, 198)
420 | block_size : int, optional
421 | Length of the sides of grid blocks in pixels. One is the lower limit.
422 | lines_on : bool, optional
423 | Whether or not to display lines between blocks.
424 |
425 | Attributes
426 | ----------
427 | width : int
428 | Number of blocks along the width of the grid.
429 | height : int
430 | Number of blocks along the height of the grid.
431 | shape : tuple of int
432 | A tuple of (width, height).
433 | block_size : int
434 | Length of the sides of grid blocks in pixels. The block size can be
435 | changed by modifying this attribute. Note that one is the lower limit.
436 | lines_on : bool
437 | Whether lines are shown between blocks when the grid is displayed.
438 | This attribute can used to toggle the whether the lines appear.
439 |
440 | """
441 |
442 | def __init__(self, width, height, fill=(0, 0, 0),
443 | block_size=20, lines_on=True):
444 | self._width = width
445 | self._height = height
446 | self._block_size = block_size
447 | self.lines_on = lines_on
448 | self._initialize_grid(fill)
449 |
450 | def _initialize_grid(self, fill):
451 | grid = [[Block(*fill, size=self._block_size)
452 | for col in range(self.width)]
453 | for row in range(self.height)]
454 |
455 | self._grid = grid
456 |
457 | @property
458 | def width(self):
459 | return self._width
460 |
461 | @property
462 | def height(self):
463 | return self._height
464 |
465 | @property
466 | def shape(self):
467 | return (self._width, self._height)
468 |
469 | @property
470 | def block_size(self):
471 | return self._block_size
472 |
473 | @block_size.setter
474 | def block_size(self, size):
475 | self._block_size = size
476 |
477 | for block in self:
478 | block.size = size
479 |
480 | @property
481 | def lines_on(self):
482 | return self._lines_on
483 |
484 | @lines_on.setter
485 | def lines_on(self, value):
486 | if value not in (0, 1):
487 | s = 'lines_on may only be True or False.'
488 | raise ValueError(s)
489 |
490 | self._lines_on = value
491 |
492 | def __eq__(self, other):
493 | if not isinstance(other, BlockGrid):
494 | return False
495 | else:
496 | # compare the underlying grids
497 | return self._grid == other._grid
498 |
499 | def _view_from_grid(self, grid):
500 | """
501 | Make a new grid from a list of lists of Block objects.
502 |
503 | """
504 | new_width = len(grid[0])
505 | new_height = len(grid)
506 |
507 | new_BG = self.__class__(new_width, new_height,
508 | block_size=self._block_size,
509 | lines_on=self._lines_on)
510 | new_BG._grid = grid
511 |
512 | return new_BG
513 |
514 | @staticmethod
515 | def _categorize_index(index):
516 | """
517 | Used by __getitem__ and __setitem__ to determine whether the user
518 | is asking for a single item, single row, or some kind of slice.
519 |
520 | """
521 | if isinstance(index, int):
522 | return _SINGLE_ROW
523 |
524 | elif isinstance(index, slice):
525 | return _ROW_SLICE
526 |
527 | elif isinstance(index, tuple):
528 | if len(index) > 2:
529 | s = 'Invalid index, too many dimensions.'
530 | raise IndexError(s)
531 |
532 | elif len(index) == 1:
533 | s = 'Single indices must be integers, not tuple.'
534 | raise TypeError(s)
535 |
536 | if isinstance(index[0], slice):
537 | if isinstance(index[1], (int, slice)):
538 | return _DOUBLE_SLICE
539 |
540 | if isinstance(index[1], slice):
541 | if isinstance(index[0], (int, slice)):
542 | return _DOUBLE_SLICE
543 |
544 | elif isinstance(index[0], int) and isinstance(index[0], int):
545 | return _SINGLE_ITEM
546 |
547 | raise IndexError('Invalid index.')
548 |
549 | def __getitem__(self, index):
550 | ind_cat = self._categorize_index(index)
551 |
552 | if ind_cat == _SINGLE_ROW:
553 | return self._view_from_grid([self._grid[index]])
554 |
555 | elif ind_cat == _SINGLE_ITEM:
556 | block = self._grid[index[0]][index[1]]
557 | block._row, block._col = index
558 | return block
559 |
560 | elif ind_cat == _ROW_SLICE:
561 | return self._view_from_grid(self._grid[index])
562 |
563 | elif ind_cat == _DOUBLE_SLICE:
564 | new_grid = self._get_double_slice(index)
565 | return self._view_from_grid(new_grid)
566 |
567 | def __setitem__(self, index, value):
568 | thing = self[index]
569 |
570 | if isinstance(value, BlockGrid):
571 | if isinstance(thing, BlockGrid):
572 | if thing.shape != value.shape:
573 | raise ShapeMismatch('Both sides of grid assignment must '
574 | 'have the same shape.')
575 |
576 | for a, b in zip(thing, value):
577 | a._update(b)
578 |
579 | else:
580 | raise TypeError('Cannot assign grid to single block.')
581 |
582 | elif isinstance(value, (Iterable, Block)):
583 | for b in _flatten(thing):
584 | b._update(value)
585 |
586 | def _get_double_slice(self, index):
587 | sl_height, sl_width = index
588 |
589 | if isinstance(sl_width, int):
590 | if sl_width == -1:
591 | sl_width = slice(sl_width, None)
592 | else:
593 | sl_width = slice(sl_width, sl_width + 1)
594 |
595 | if isinstance(sl_height, int):
596 | if sl_height == -1:
597 | sl_height = slice(sl_height, None)
598 | else:
599 | sl_height = slice(sl_height, sl_height + 1)
600 |
601 | rows = self._grid[sl_height]
602 | grid = [r[sl_width] for r in rows]
603 |
604 | return grid
605 |
606 | def __iter__(self):
607 | for r in range(self.height):
608 | for c in range(self.width):
609 | yield self[r, c]
610 |
611 | def animate(self, stop_time=0.2):
612 | """
613 | Call this method in a loop definition to have your changes to the grid
614 | animated in the IPython Notebook.
615 |
616 | Parameters
617 | ----------
618 | stop_time : float
619 | Amount of time to pause between loop steps.
620 |
621 | """
622 | for block in self:
623 | self.show()
624 | time.sleep(stop_time)
625 | yield block
626 | clear_output(wait=True)
627 | self.show()
628 |
629 | def _repr_html_(self):
630 | rows = range(self._height)
631 | cols = range(self._width)
632 |
633 | html = reduce(iadd,
634 | (_TR.format(reduce(iadd,
635 | (self[r, c]._td
636 | for c in cols)))
637 | for r in rows))
638 |
639 | return _TABLE.format(uuid.uuid4(), int(self._lines_on), html)
640 |
641 | def __str__(self):
642 | s = ['{0}'.format(self.__class__.__name__),
643 | 'Shape: {0}'.format(self.shape)]
644 |
645 | return os.linesep.join(s)
646 |
647 | def copy(self):
648 | """
649 | Returns an independent copy of this BlockGrid.
650 |
651 | """
652 | return copy.deepcopy(self)
653 |
654 | def show(self):
655 | """
656 | Display colored grid as an HTML table.
657 |
658 | """
659 | display(HTML(self._repr_html_()))
660 |
661 | def flash(self, display_time=0.2):
662 | """
663 | Display the grid for a time.
664 |
665 | Useful for making an animation or iteratively displaying changes.
666 |
667 | Note that this will leave the grid in place until something replaces
668 | it in the same cell. You can use the ``clear`` function to
669 | manually clear output.
670 |
671 | Parameters
672 | ----------
673 | display_time : float
674 | Amount of time, in seconds, to display the grid.
675 |
676 | """
677 | self.show()
678 | time.sleep(display_time)
679 | clear_output(wait=True)
680 |
681 | def _calc_image_size(self):
682 | """
683 | Calculate the size, in pixels, of the grid as an image.
684 |
685 | Returns
686 | -------
687 | px_width : int
688 | px_height : int
689 |
690 | """
691 | px_width = self._block_size * self._width
692 | px_height = self._block_size * self._height
693 |
694 | if self._lines_on:
695 | px_width += self._width + 1
696 | px_height += self._height + 1
697 |
698 | return px_width, px_height
699 |
700 | def _write_image(self, fp, format='png'):
701 | """
702 | Write an image of the current grid to a file-object.
703 |
704 | Parameters
705 | ----------
706 | fp : file-like
707 | A file-like object such as an open file pointer or
708 | a StringIO/BytesIO instance.
709 | format : str, optional
710 | An image format that will be understood by PIL,
711 | e.g. 'png', 'jpg', 'gif', etc.
712 |
713 | """
714 | try:
715 | # PIL
716 | import Image
717 | import ImageDraw
718 | except ImportError:
719 | # pillow
720 | from PIL import Image, ImageDraw
721 |
722 | im = Image.new(
723 | mode='RGB', size=self._calc_image_size(), color=(255, 255, 255))
724 | draw = ImageDraw.Draw(im)
725 |
726 | _bs = self._block_size
727 |
728 | for r in range(self._height):
729 | for c in range(self._width):
730 | px_r = r * _bs
731 | px_c = c * _bs
732 | if self._lines_on:
733 | px_r += r + 1
734 | px_c += c + 1
735 |
736 | rect = ((px_c, px_r), (px_c + _bs - 1, px_r + _bs - 1))
737 | draw.rectangle(rect, fill=self._grid[r][c].rgb)
738 |
739 | im.save(fp, format=format)
740 |
741 | def show_image(self):
742 | """
743 | Embed grid in the notebook as a PNG image.
744 |
745 | """
746 | if sys.version_info[0] == 2:
747 | from StringIO import StringIO as BytesIO
748 | elif sys.version_info[0] == 3:
749 | from io import BytesIO
750 |
751 | im = BytesIO()
752 | self._write_image(im)
753 | display(ipyImage(data=im.getvalue(), format='png'))
754 |
755 | def save_image(self, filename):
756 | """
757 | Save an image representation of the grid to a file.
758 | Image format will be inferred from file extension.
759 |
760 | Parameters
761 | ----------
762 | filename : str
763 | Name of file to save to.
764 |
765 | """
766 | with open(filename, 'wb') as f:
767 | self._write_image(f, format=filename.split('.')[-1])
768 |
769 | def to_text(self, filename=None):
770 | """
771 | Write a text file containing the size and block color information
772 | for this grid.
773 |
774 | If no file name is given the text is sent to stdout.
775 |
776 | Parameters
777 | ----------
778 | filename : str, optional
779 | File into which data will be written. Will be overwritten if
780 | it already exists.
781 |
782 | """
783 | if filename:
784 | f = open(filename, 'w')
785 | else:
786 | f = sys.stdout
787 |
788 | s = ['# width height', '{0} {1}'.format(self.width, self.height),
789 | '# block size', '{0}'.format(self.block_size),
790 | '# initial color', '0 0 0',
791 | '# row column red green blue']
792 | f.write(os.linesep.join(s) + os.linesep)
793 |
794 | for block in self:
795 | things = [str(x) for x in (block.row, block.col) + block.rgb]
796 | f.write(' '.join(things) + os.linesep)
797 |
798 | if filename:
799 | f.close()
800 |
801 | def _to_simple_grid(self):
802 | """
803 | Make a simple representation of the table: nested lists of
804 | of the rows containing tuples of (red, green, blue, size)
805 | for each of the blocks.
806 |
807 | Returns
808 | -------
809 | grid : list of lists
810 | No matter the class this method is called on the returned
811 | grid will be Python-style: row oriented with the top-left
812 | block in the [0][0] position.
813 |
814 | """
815 | return [[(x.red, x.green, x.blue, x.size) for x in row]
816 | for row in self._grid]
817 |
818 | def _construct_post_request(self, code_cells, secret):
819 | """
820 | Construct the request dictionary that will be posted
821 | to ipythonblocks.org.
822 |
823 | Parameters
824 | ----------
825 | code_cells : int, str, slice, or None
826 | Specify any code cells to be sent and displayed with the grid.
827 | You can specify a single cell, a Python, slice, or a combination
828 | as a string separated by commas.
829 |
830 | For example, '3,5,8:10' would send cells 3, 5, 8, and 9.
831 | secret : bool
832 | If True, this grid will not be shown randomly on ipythonblocks.org.
833 |
834 | Returns
835 | -------
836 | request : dict
837 |
838 | """
839 | if code_cells is not None:
840 | code_cells = _get_code_cells(code_cells)
841 |
842 | req = {
843 | 'python_version': tuple(sys.version_info),
844 | 'ipb_version': __version__,
845 | 'ipb_class': self.__class__.__name__,
846 | 'code_cells': code_cells,
847 | 'secret': secret,
848 | 'grid_data': {
849 | 'lines_on': self.lines_on,
850 | 'width': self.width,
851 | 'height': self.height,
852 | 'blocks': self._to_simple_grid()
853 | }
854 | }
855 |
856 | return req
857 |
858 | def post_to_web(self, code_cells=None, secret=False):
859 | """
860 | Post this grid to ipythonblocks.org and return a URL to
861 | view the grid on the web.
862 |
863 | Parameters
864 | ----------
865 | code_cells : int, str, or slice, optional
866 | Specify any code cells to be sent and displayed with the grid.
867 | You can specify a single cell, a Python, slice, or a combination
868 | as a string separated by commas.
869 |
870 | For example, '3,5,8:10' would send cells 3, 5, 8, and 9.
871 | secret : bool, optional
872 | If True, this grid will not be shown randomly on ipythonblocks.org.
873 |
874 | Returns
875 | -------
876 | url : str
877 | URL to view your grid on ipythonblocks.org.
878 |
879 | """
880 | import requests
881 |
882 | req = self._construct_post_request(code_cells, secret)
883 | response = requests.post(_POST_URL, data=json.dumps(req))
884 | response.raise_for_status()
885 |
886 | return response.json()['url']
887 |
888 | def _load_simple_grid(self, block_data):
889 | """
890 | Modify the grid to reflect the data in `block_data`, which
891 | should be a nested list of tuples as produced by `_to_simple_grid`.
892 |
893 | Parameters
894 | ----------
895 | block_data : list of lists
896 | Nested list of tuples as produced by `_to_simple_grid`.
897 |
898 | """
899 | if len(block_data) != self.height or \
900 | len(block_data[0]) != self.width:
901 | raise ShapeMismatch('block_data must have same shape as grid.')
902 |
903 | for row in range(self.height):
904 | for col in range(self.width):
905 | self._grid[row][col].rgb = block_data[row][col][:3]
906 | self._grid[row][col].size = block_data[row][col][3]
907 |
908 | @classmethod
909 | def from_web(cls, grid_id, secret=False):
910 | """
911 | Make a new BlockGrid from a grid on ipythonblocks.org.
912 |
913 | Parameters
914 | ----------
915 | grid_id : str
916 | ID of a grid on ipythonblocks.org. This will be the part of the
917 | URL after 'ipythonblocks.org/'.
918 | secret : bool, optional
919 | Whether or not the grid on ipythonblocks.org is secret.
920 |
921 | Returns
922 | -------
923 | grid : BlockGrid
924 |
925 | """
926 | import requests
927 |
928 | get_url = _GET_URL_PUBLIC if not secret else _GET_URL_SECRET
929 | resp = requests.get(get_url.format(grid_id))
930 | resp.raise_for_status()
931 | grid_spec = resp.json()
932 |
933 | grid = cls(grid_spec['width'], grid_spec['height'],
934 | lines_on=grid_spec['lines_on'])
935 | grid._load_simple_grid(grid_spec['blocks'])
936 |
937 | return grid
938 |
939 |
940 | class Pixel(Block):
941 | @property
942 | def x(self):
943 | """
944 | Horizontal coordinate of Pixel.
945 |
946 | """
947 | return self._col
948 |
949 | @property
950 | def y(self):
951 | """
952 | Vertical coordinate of Pixel.
953 |
954 | """
955 | return self._row
956 |
957 | @property
958 | def _td(self):
959 | """
960 | The HTML for a table cell with the background color of this Pixel.
961 |
962 | """
963 | title = _TITLE.format(self._col, self._row,
964 | self._red, self._green, self._blue)
965 | rgb = _RGB.format(self._red, self._green, self._blue)
966 | return _TD.format(title, self._size, rgb)
967 |
968 | def __str__(self):
969 | s = ['{0}'.format(self.__class__.__name__),
970 | 'Color: ({0}, {1}, {2})'.format(self._red,
971 | self._green,
972 | self._blue)]
973 |
974 | # add position information if we have it
975 | if self._row is not None:
976 | s[0] += ' [{0}, {1}]'.format(self._col, self._row)
977 |
978 | return os.linesep.join(s)
979 |
980 |
981 | class ImageGrid(BlockGrid):
982 | """
983 | A grid of blocks whose colors can be individually controlled.
984 |
985 | Parameters
986 | ----------
987 | width : int
988 | Number of blocks wide to make the grid.
989 | height : int
990 | Number of blocks high to make the grid.
991 | fill : tuple of int, optional
992 | An optional initial color for the grid, defaults to black.
993 | Specified as a tuple of (red, green, blue). E.g.: (10, 234, 198)
994 | block_size : int, optional
995 | Length of the sides of grid blocks in pixels. One is the lower limit.
996 | lines_on : bool, optional
997 | Whether or not to display lines between blocks.
998 | origin : {'lower-left', 'upper-left'}, optional
999 | Set the location of the grid origin.
1000 |
1001 | Attributes
1002 | ----------
1003 | width : int
1004 | Number of blocks along the width of the grid.
1005 | height : int
1006 | Number of blocks along the height of the grid.
1007 | shape : tuple of int
1008 | A tuple of (width, height).
1009 | block_size : int
1010 | Length of the sides of grid blocks in pixels.
1011 | lines_on : bool
1012 | Whether lines are shown between blocks when the grid is displayed.
1013 | This attribute can used to toggle the whether the lines appear.
1014 | origin : str
1015 | The location of the grid origin.
1016 |
1017 | """
1018 |
1019 | def __init__(self, width, height, fill=(0, 0, 0),
1020 | block_size=20, lines_on=True, origin='lower-left'):
1021 | super(ImageGrid, self).__init__(width, height, fill,
1022 | block_size, lines_on)
1023 |
1024 | if origin not in ('lower-left', 'upper-left'):
1025 | s = "origin keyword must be one of {'lower-left', 'upper-left'}."
1026 | raise ValueError(s)
1027 |
1028 | self._origin = origin
1029 |
1030 | def _initialize_grid(self, fill):
1031 | grid = [[Pixel(*fill, size=self._block_size)
1032 | for col in range(self.width)]
1033 | for row in range(self.height)]
1034 |
1035 | self._grid = grid
1036 |
1037 | @property
1038 | def block_size(self):
1039 | return self._block_size
1040 |
1041 | @property
1042 | def origin(self):
1043 | return self._origin
1044 |
1045 | def _transform_index(self, index):
1046 | """
1047 | Transform a single-item index from Python style coordinates to
1048 | image style coordinates in which the first item refers to column and
1049 | the second item refers to row. Also takes into account the
1050 | location of the origin.
1051 |
1052 | """
1053 | # in ImageGrid index is guaranteed to be a tuple.
1054 |
1055 | # first thing, switch the coordinates since ImageGrid is column
1056 | # major and ._grid is row major.
1057 | new_ind = [index[1], index[0]]
1058 |
1059 | # now take into account that the ImageGrid origin may be lower-left,
1060 | # while the ._grid origin is upper-left.
1061 | if self._origin == 'lower-left':
1062 | if new_ind[0] >= 0:
1063 | new_ind[0] = self._height - new_ind[0] - 1
1064 | else:
1065 | new_ind[0] = abs(new_ind[0]) - 1
1066 |
1067 | return tuple(new_ind)
1068 |
1069 | def __getitem__(self, index):
1070 | ind_cat = self._categorize_index(index)
1071 |
1072 | # ImageGrid will only support single item indexing and 2D slices
1073 | if ind_cat not in (_DOUBLE_SLICE, _SINGLE_ITEM):
1074 | s = 'ImageGrid only supports 2D indexing.'
1075 | raise IndexError(s)
1076 |
1077 | if ind_cat == _SINGLE_ITEM:
1078 | # should be able to index ._grid with new_ind regardless of any
1079 | # following coordinate transforms. let's just make sure.
1080 | self._grid[index[1]][index[0]]
1081 |
1082 | real_index = self._transform_index(index)
1083 | pixel = self._grid[real_index[0]][real_index[1]]
1084 | pixel._col, pixel._row = index
1085 | return pixel
1086 |
1087 | elif ind_cat == _DOUBLE_SLICE:
1088 | new_grid = self._get_double_slice(index)
1089 | return self._view_from_grid(new_grid)
1090 |
1091 | def _get_double_slice(self, index):
1092 | cslice, rslice = index
1093 |
1094 | if isinstance(rslice, int):
1095 | if rslice == -1:
1096 | rslice = slice(rslice, None)
1097 | else:
1098 | rslice = slice(rslice, rslice + 1)
1099 |
1100 | if isinstance(cslice, int):
1101 | if cslice == -1:
1102 | cslice = slice(cslice, None)
1103 | else:
1104 | cslice = slice(cslice, cslice + 1)
1105 |
1106 | rows = range(self._height)[rslice]
1107 | if self._origin == 'lower-left':
1108 | rows = rows[::-1]
1109 |
1110 | cols = range(self._width)[cslice]
1111 |
1112 | new_grid = [[self[c, r] for c in cols] for r in rows]
1113 |
1114 | return new_grid
1115 |
1116 | def __iter__(self):
1117 | for col in range(self.width):
1118 | for row in range(self.height):
1119 | yield self[col, row]
1120 |
1121 | def _repr_html_(self):
1122 | rows = range(self._height)
1123 | cols = range(self._width)
1124 |
1125 | if self._origin == 'lower-left':
1126 | rows = rows[::-1]
1127 |
1128 | html = reduce(iadd,
1129 | (_TR.format(reduce(iadd,
1130 | (self[c, r]._td
1131 | for c in cols)))
1132 | for r in rows))
1133 |
1134 | return _TABLE.format(uuid.uuid4(), int(self._lines_on), html)
1135 |
1136 | @classmethod
1137 | def from_web(cls, grid_id, secret=False, origin='lower-left'):
1138 | """
1139 | Make a new ImageGrid from a grid on ipythonblocks.org.
1140 |
1141 | Parameters
1142 | ----------
1143 | grid_id : str
1144 | ID of a grid on ipythonblocks.org. This will be the part of the
1145 | URL after 'ipythonblocks.org/'.
1146 | secret : bool, optional
1147 | Whether or not the grid on ipythonblocks.org is secret.
1148 | origin : {'lower-left', 'upper-left'}, optional
1149 | Set the location of the grid origin.
1150 |
1151 | Returns
1152 | -------
1153 | grid : ImageGrid
1154 |
1155 | """
1156 | import requests
1157 |
1158 | get_url = _GET_URL_PUBLIC if not secret else _GET_URL_SECRET
1159 | resp = requests.get(get_url.format(grid_id))
1160 | resp.raise_for_status()
1161 | grid_spec = resp.json()
1162 |
1163 | grid = cls(grid_spec['width'], grid_spec['height'],
1164 | lines_on=grid_spec['lines_on'], origin=origin)
1165 | grid._load_simple_grid(grid_spec['blocks'])
1166 |
1167 | return grid
1168 |
1169 |
1170 | # Convenience wrapper for color tuples with attribute access for the
1171 | # component colors
1172 | Color = namedtuple('Color', ['red', 'green', 'blue'])
1173 |
1174 | # This doesn't work on Python 2
1175 | # Color.__doc__ += """\
1176 | # Wrapper for a color tuple that provides .red, .green, and .blue
1177 | # attributes for access to the individual components.
1178 | # """
1179 |
1180 |
1181 | # As a convenience, provide some colors as a custom hybrid
1182 | # dictionary and object with the color names as attributes
1183 | class _ColorBunch(dict):
1184 | """
1185 | Customized dictionary that exposes its keys as attributes.
1186 |
1187 | """
1188 | def __init__(self, colors):
1189 | colors = {name: Color(*rgb) for name, rgb in colors.items()}
1190 | super(_ColorBunch, self).__init__(colors)
1191 | self.__dict__.update(colors)
1192 |
1193 |
1194 | # HTML colors
1195 | colors = _ColorBunch({
1196 | 'AliceBlue': (240, 248, 255),
1197 | 'AntiqueWhite': (250, 235, 215),
1198 | 'Aqua': (0, 255, 255),
1199 | 'Aquamarine': (127, 255, 212),
1200 | 'Azure': (240, 255, 255),
1201 | 'Beige': (245, 245, 220),
1202 | 'Bisque': (255, 228, 196),
1203 | 'Black': (0, 0, 0),
1204 | 'BlanchedAlmond': (255, 235, 205),
1205 | 'Blue': (0, 0, 255),
1206 | 'BlueViolet': (138, 43, 226),
1207 | 'Brown': (165, 42, 42),
1208 | 'BurlyWood': (222, 184, 135),
1209 | 'CadetBlue': (95, 158, 160),
1210 | 'Chartreuse': (127, 255, 0),
1211 | 'Chocolate': (210, 105, 30),
1212 | 'Coral': (255, 127, 80),
1213 | 'CornflowerBlue': (100, 149, 237),
1214 | 'Cornsilk': (255, 248, 220),
1215 | 'Crimson': (220, 20, 60),
1216 | 'Cyan': (0, 255, 255),
1217 | 'DarkBlue': (0, 0, 139),
1218 | 'DarkCyan': (0, 139, 139),
1219 | 'DarkGoldenrod': (184, 134, 11),
1220 | 'DarkGray': (169, 169, 169),
1221 | 'DarkGreen': (0, 100, 0),
1222 | 'DarkKhaki': (189, 183, 107),
1223 | 'DarkMagenta': (139, 0, 139),
1224 | 'DarkOliveGreen': (85, 107, 47),
1225 | 'DarkOrange': (255, 140, 0),
1226 | 'DarkOrchid': (153, 50, 204),
1227 | 'DarkRed': (139, 0, 0),
1228 | 'DarkSalmon': (233, 150, 122),
1229 | 'DarkSeaGreen': (143, 188, 143),
1230 | 'DarkSlateBlue': (72, 61, 139),
1231 | 'DarkSlateGray': (47, 79, 79),
1232 | 'DarkTurquoise': (0, 206, 209),
1233 | 'DarkViolet': (148, 0, 211),
1234 | 'DeepPink': (255, 20, 147),
1235 | 'DeepSkyBlue': (0, 191, 255),
1236 | 'DimGray': (105, 105, 105),
1237 | 'DodgerBlue': (30, 144, 255),
1238 | 'FireBrick': (178, 34, 34),
1239 | 'FloralWhite': (255, 250, 240),
1240 | 'ForestGreen': (34, 139, 34),
1241 | 'Fuchsia': (255, 0, 255),
1242 | 'Gainsboro': (220, 220, 220),
1243 | 'GhostWhite': (248, 248, 255),
1244 | 'Gold': (255, 215, 0),
1245 | 'Goldenrod': (218, 165, 32),
1246 | 'Gray': (128, 128, 128),
1247 | 'Green': (0, 128, 0),
1248 | 'GreenYellow': (173, 255, 47),
1249 | 'Honeydew': (240, 255, 240),
1250 | 'HotPink': (255, 105, 180),
1251 | 'IndianRed': (205, 92, 92),
1252 | 'Indigo': (75, 0, 130),
1253 | 'Ivory': (255, 255, 240),
1254 | 'Khaki': (240, 230, 140),
1255 | 'Lavender': (230, 230, 250),
1256 | 'LavenderBlush': (255, 240, 245),
1257 | 'LawnGreen': (124, 252, 0),
1258 | 'LemonChiffon': (255, 250, 205),
1259 | 'LightBlue': (173, 216, 230),
1260 | 'LightCoral': (240, 128, 128),
1261 | 'LightCyan': (224, 255, 255),
1262 | 'LightGoldenrodYellow': (250, 250, 210),
1263 | 'LightGray': (211, 211, 211),
1264 | 'LightGreen': (144, 238, 144),
1265 | 'LightPink': (255, 182, 193),
1266 | 'LightSalmon': (255, 160, 122),
1267 | 'LightSeaGreen': (32, 178, 170),
1268 | 'LightSkyBlue': (135, 206, 250),
1269 | 'LightSlateGray': (119, 136, 153),
1270 | 'LightSteelBlue': (176, 196, 222),
1271 | 'LightYellow': (255, 255, 224),
1272 | 'Lime': (0, 255, 0),
1273 | 'LimeGreen': (50, 205, 50),
1274 | 'Linen': (250, 240, 230),
1275 | 'Magenta': (255, 0, 255),
1276 | 'Maroon': (128, 0, 0),
1277 | 'MediumAquamarine': (102, 205, 170),
1278 | 'MediumBlue': (0, 0, 205),
1279 | 'MediumOrchid': (186, 85, 211),
1280 | 'MediumPurple': (147, 112, 219),
1281 | 'MediumSeaGreen': (60, 179, 113),
1282 | 'MediumSlateBlue': (123, 104, 238),
1283 | 'MediumSpringGreen': (0, 250, 154),
1284 | 'MediumTurquoise': (72, 209, 204),
1285 | 'MediumVioletRed': (199, 21, 133),
1286 | 'MidnightBlue': (25, 25, 112),
1287 | 'MintCream': (245, 255, 250),
1288 | 'MistyRose': (255, 228, 225),
1289 | 'Moccasin': (255, 228, 181),
1290 | 'NavajoWhite': (255, 222, 173),
1291 | 'Navy': (0, 0, 128),
1292 | 'OldLace': (253, 245, 230),
1293 | 'Olive': (128, 128, 0),
1294 | 'OliveDrab': (107, 142, 35),
1295 | 'Orange': (255, 165, 0),
1296 | 'OrangeRed': (255, 69, 0),
1297 | 'Orchid': (218, 112, 214),
1298 | 'PaleGoldenrod': (238, 232, 170),
1299 | 'PaleGreen': (152, 251, 152),
1300 | 'PaleTurquoise': (175, 238, 238),
1301 | 'PaleVioletRed': (219, 112, 147),
1302 | 'PapayaWhip': (255, 239, 213),
1303 | 'PeachPuff': (255, 218, 185),
1304 | 'Peru': (205, 133, 63),
1305 | 'Pink': (255, 192, 203),
1306 | 'Plum': (221, 160, 221),
1307 | 'PowderBlue': (176, 224, 230),
1308 | 'Purple': (128, 0, 128),
1309 | 'Red': (255, 0, 0),
1310 | 'RosyBrown': (188, 143, 143),
1311 | 'RoyalBlue': (65, 105, 225),
1312 | 'SaddleBrown': (139, 69, 19),
1313 | 'Salmon': (250, 128, 114),
1314 | 'SandyBrown': (244, 164, 96),
1315 | 'SeaGreen': (46, 139, 87),
1316 | 'Seashell': (255, 245, 238),
1317 | 'Sienna': (160, 82, 45),
1318 | 'Silver': (192, 192, 192),
1319 | 'SkyBlue': (135, 206, 235),
1320 | 'SlateBlue': (106, 90, 205),
1321 | 'SlateGray': (112, 128, 144),
1322 | 'Snow': (255, 250, 250),
1323 | 'SpringGreen': (0, 255, 127),
1324 | 'SteelBlue': (70, 130, 180),
1325 | 'Tan': (210, 180, 140),
1326 | 'Teal': (0, 128, 128),
1327 | 'Thistle': (216, 191, 216),
1328 | 'Tomato': (255, 99, 71),
1329 | 'Turquoise': (64, 224, 208),
1330 | 'Violet': (238, 130, 238),
1331 | 'Wheat': (245, 222, 179),
1332 | 'White': (255, 255, 255),
1333 | 'WhiteSmoke': (245, 245, 245),
1334 | 'Yellow': (255, 255, 0),
1335 | 'YellowGreen': (154, 205, 50)
1336 | })
1337 |
1338 |
1339 | # Flat UI colors: http://flatuicolors.com/
1340 | fui_colors = _ColorBunch({
1341 | 'Alizarin': (231, 76, 60),
1342 | 'Pomegranate': (192, 57, 43),
1343 | 'Carrot': (230, 126, 34),
1344 | 'Pumpkin': (211, 84, 0),
1345 | 'SunFlower': (241, 196, 15),
1346 | 'Orange': (243, 156, 18),
1347 | 'Emerald': (46, 204, 113),
1348 | 'Nephritis': (39, 174, 96),
1349 | 'Turquoise': (26, 188, 156),
1350 | 'GreenSea': (22, 160, 133),
1351 | 'PeterRiver': (52, 152, 219),
1352 | 'BelizeHole': (41, 128, 185),
1353 | 'Amethyst': (155, 89, 182),
1354 | 'Wisteria': (142, 68, 173),
1355 | 'WetAsphalt': (52, 73, 94),
1356 | 'MidnightBlue': (44, 62, 80),
1357 | 'Concrete': (149, 165, 166),
1358 | 'Asbestos': (127, 140, 141),
1359 | 'Clouds': (236, 240, 241),
1360 | 'Silver': (189, 195, 199)
1361 | })
1362 |
--------------------------------------------------------------------------------
/demos/ipythonblocks_imagegrid.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": {
3 | "name": "ipythonblocks_imagegrid"
4 | },
5 | "nbformat": 3,
6 | "nbformat_minor": 0,
7 | "worksheets": [
8 | {
9 | "cells": [
10 | {
11 | "cell_type": "heading",
12 | "level": 1,
13 | "metadata": {},
14 | "source": [
15 | "ipythonblocks.ImageGrid"
16 | ]
17 | },
18 | {
19 | "cell_type": "markdown",
20 | "metadata": {},
21 | "source": [
22 | "To learn more about `ipythonblocks` visit the homepage at [https://github.com/jiffyclub/ipythonblocks](https://github.com/jiffyclub/ipythonblocks).\n",
23 | "\n",
24 | "`ImageGrid` is a class that imitates image manipulation libraries like [PIL](http://www.pythonware.com/products/pil/).\n",
25 | "Key differences from `BlockGrid` are:\n",
26 | "\n",
27 | "* Only 2D indexing is supported\n",
28 | "* Indices are [column, row]\n",
29 | "* Grid units are `Pixel` objects, which have `.x` (column) and `.y` (row) attributes\n",
30 | "* Grid origin defaults to the lower-left corner, but can be set to the upper-left"
31 | ]
32 | },
33 | {
34 | "cell_type": "code",
35 | "collapsed": false,
36 | "input": [
37 | "from ipythonblocks import ImageGrid"
38 | ],
39 | "language": "python",
40 | "metadata": {},
41 | "outputs": [],
42 | "prompt_number": 1
43 | },
44 | {
45 | "cell_type": "code",
46 | "collapsed": false,
47 | "input": [
48 | "grid = ImageGrid(10, 10, fill=(124, 124, 124), block_size=15)\n",
49 | "grid"
50 | ],
51 | "language": "python",
52 | "metadata": {},
53 | "outputs": [
54 | {
55 | "html": [
56 | ""
57 | ],
58 | "output_type": "pyout",
59 | "prompt_number": 2,
60 | "text": [
61 | ""
62 | ]
63 | }
64 | ],
65 | "prompt_number": 2
66 | },
67 | {
68 | "cell_type": "code",
69 | "collapsed": false,
70 | "input": [
71 | "for pixel in grid:\n",
72 | " if pixel.x < 5:\n",
73 | " pixel.green = 255\n",
74 | " elif pixel.x > 5 and pixel.y >= 5:\n",
75 | " pixel.blue = 255\n",
76 | "grid"
77 | ],
78 | "language": "python",
79 | "metadata": {},
80 | "outputs": [
81 | {
82 | "html": [
83 | ""
84 | ],
85 | "output_type": "pyout",
86 | "prompt_number": 3,
87 | "text": [
88 | ""
89 | ]
90 | }
91 | ],
92 | "prompt_number": 3
93 | },
94 | {
95 | "cell_type": "code",
96 | "collapsed": false,
97 | "input": [
98 | "grid[:3, :3] = (255, 124, 124)\n",
99 | "grid"
100 | ],
101 | "language": "python",
102 | "metadata": {},
103 | "outputs": [
104 | {
105 | "html": [
106 | ""
107 | ],
108 | "output_type": "pyout",
109 | "prompt_number": 4,
110 | "text": [
111 | ""
112 | ]
113 | }
114 | ],
115 | "prompt_number": 4
116 | },
117 | {
118 | "cell_type": "code",
119 | "collapsed": false,
120 | "input": [
121 | "grid[::4, ::4] = (0, 0, 0)\n",
122 | "grid"
123 | ],
124 | "language": "python",
125 | "metadata": {},
126 | "outputs": [
127 | {
128 | "html": [
129 | ""
130 | ],
131 | "output_type": "pyout",
132 | "prompt_number": 5,
133 | "text": [
134 | ""
135 | ]
136 | }
137 | ],
138 | "prompt_number": 5
139 | },
140 | {
141 | "cell_type": "code",
142 | "collapsed": false,
143 | "input": [
144 | "grid[6, :] = (255, 0, 255)\n",
145 | "grid"
146 | ],
147 | "language": "python",
148 | "metadata": {},
149 | "outputs": [
150 | {
151 | "html": [
152 | ""
153 | ],
154 | "output_type": "pyout",
155 | "prompt_number": 6,
156 | "text": [
157 | ""
158 | ]
159 | }
160 | ],
161 | "prompt_number": 6
162 | },
163 | {
164 | "cell_type": "code",
165 | "collapsed": false,
166 | "input": [
167 | "grid[:, 6] = (255, 255, 0)\n",
168 | "grid"
169 | ],
170 | "language": "python",
171 | "metadata": {},
172 | "outputs": [
173 | {
174 | "html": [
175 | ""
176 | ],
177 | "output_type": "pyout",
178 | "prompt_number": 7,
179 | "text": [
180 | ""
181 | ]
182 | }
183 | ],
184 | "prompt_number": 7
185 | }
186 | ],
187 | "metadata": {}
188 | }
189 | ]
190 | }
--------------------------------------------------------------------------------