jupyter_compare_view 0.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jupyter_compare_view-0.2.5/LICENSE +21 -0
- jupyter_compare_view-0.2.5/PKG-INFO +204 -0
- jupyter_compare_view-0.2.5/README.md +182 -0
- jupyter_compare_view-0.2.5/jupyter_compare_view/__init__.py +14 -0
- jupyter_compare_view-0.2.5/jupyter_compare_view/compare.py +131 -0
- jupyter_compare_view-0.2.5/jupyter_compare_view/sw_cellmagic.py +85 -0
- jupyter_compare_view-0.2.5/jupyter_compare_view/template.html +20 -0
- jupyter_compare_view-0.2.5/pyproject.toml +28 -0
- jupyter_compare_view-0.2.5/vendor/compare_view/browser_compare_view.js +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Octoframes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jupyter_compare_view
|
|
3
|
+
Version: 0.2.5
|
|
4
|
+
Summary: Blend Between Multiple Images in JupyterLab.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Octoframes
|
|
8
|
+
Requires-Python: >=3.8,<3.13
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Dist: Jinja2 (>=2.11.3)
|
|
17
|
+
Requires-Dist: Pillow (>=7.1.2)
|
|
18
|
+
Requires-Dist: ipykernel (>=5.0.0)
|
|
19
|
+
Requires-Dist: ipython (>=6.0.0)
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Jupyter compare_view
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
[](https://octoframes.github.io/jupyter_compare_view)
|
|
27
|
+
[](https://mybinder.org/v2/gh/Octoframes/jupyter_compare_view/HEAD?labpath=example_notebook.ipynb)
|
|
28
|
+
[](https://colab.research.google.com/github/Octoframes/jupyter_compare_view/blob/main/example_notebook.ipynb)
|
|
29
|
+
[](https://badge.fury.io/py/jupyter_compare_view)
|
|
30
|
+
[](https://github.com/Octoframes/jupyter_compare_view/blob/main/LICENSE)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Blend between multiple images using a cell magic in JupyterLab using [compare_view](https://octoframes.github.io/compare_view).
|
|
34
|
+
*This project was called jupyter-splitview before.*
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
```py
|
|
39
|
+
pip install jupyter_compare_view
|
|
40
|
+
```
|
|
41
|
+
## Example
|
|
42
|
+
```py
|
|
43
|
+
import jupyter_compare_view
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```py
|
|
47
|
+
%%compare
|
|
48
|
+
from skimage import data
|
|
49
|
+
from skimage.color import rgb2gray
|
|
50
|
+
import matplotlib.pyplot as plt
|
|
51
|
+
|
|
52
|
+
img = data.chelsea()
|
|
53
|
+
grayscale_img = rgb2gray(img)
|
|
54
|
+
|
|
55
|
+
plt.imshow(img)
|
|
56
|
+
plt.axis("off")
|
|
57
|
+
plt.show()
|
|
58
|
+
|
|
59
|
+
plt.imshow(grayscale_img, cmap="gray")
|
|
60
|
+
plt.axis("off")
|
|
61
|
+
plt.show()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
<img src="https://user-images.githubusercontent.com/44469195/179499138-65160434-11e1-4358-8e25-5b26ba9ebf4a.png" style="width: 400px;"/>
|
|
65
|
+
|
|
66
|
+
Another example:
|
|
67
|
+
```py
|
|
68
|
+
from jupyter_compare_view import compare
|
|
69
|
+
|
|
70
|
+
compare(img, grayscale_img, cmap="gray", start_mode="horizontal", start_slider_pos=0.73)
|
|
71
|
+
```
|
|
72
|
+
<img src="https://user-images.githubusercontent.com/44469195/179499350-94244408-cabf-4945-affc-fd0444d53555.png" style="width: 400px;"/>
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
The split view widget is still responsive after closing and reopening the notebook without running the cell again.
|
|
76
|
+
|
|
77
|
+
## Notebook arguments
|
|
78
|
+
(Might still change in future)
|
|
79
|
+
* `--config '{"start_mode": "horizontal"}'` will init the compare-view in horizontal slider mode.
|
|
80
|
+
* `--config '{"circle_size": 30}'` the circle size is now 30 pixel in circle mode.
|
|
81
|
+
* `--config '{"show_slider": false}'` will hide the slider bar.
|
|
82
|
+
* `--config '{"start_slider_pos": 0.73}'` will set the slider start position to 73%.
|
|
83
|
+
|
|
84
|
+
* *Removed in 0.1.1: `--position 73%` will no longer the slider start position to 73%.*
|
|
85
|
+
* `--config '{"start_mode": "horizontal","start_slider_pos": 0.73}'` will both set the start mode to horizontal and set the slider position
|
|
86
|
+
* `--height 220` will set the height to 220 pixel.
|
|
87
|
+
* When `--height`is not provided, the default height of the widget is 300 pixel.
|
|
88
|
+
* `--height auto` will set the height by the value of the first image's resolution in vertical direction.
|
|
89
|
+
* The widget's width will always be adjusted automatically.
|
|
90
|
+
|
|
91
|
+
## Notebook formatting
|
|
92
|
+
Formatting with black can be done this way:
|
|
93
|
+
1. `pip install 'black[jupyter]'`
|
|
94
|
+
2. `black --python-cell-magics compare compare_view_magic.ipynb`
|
|
95
|
+
|
|
96
|
+
## Interactive Export
|
|
97
|
+
jupyter_compare_view fully supports offline interactive HTML exports.
|
|
98
|
+
The web library [compare_view](https://octoframes.github.io/compare_view) is inlined into the exported HTML document.
|
|
99
|
+
Therefore, the export is viewable without an internet connection.
|
|
100
|
+
|
|
101
|
+

|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## Developer Installation
|
|
105
|
+
|
|
106
|
+
1. `git clone --recurse https://github.com/Octoframes/jupyter_compare_view`
|
|
107
|
+
(Note: In case that the repo was already cloned e.g. with the GitHub Desktop client, the GitHub submodule has to be loaded via `git submodule update --init --recursive`)
|
|
108
|
+
2. `poetry install`
|
|
109
|
+
|
|
110
|
+
*Note*: The IPython extension `autoreload` reloads modules before every cell execution. Very useful when debugging the `%%capture` cell magic!
|
|
111
|
+
Just add these lines into the first jupyter cell.
|
|
112
|
+
```py
|
|
113
|
+
%load_ext autoreload
|
|
114
|
+
%autoreload 2
|
|
115
|
+
import jupyter_compare_view
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Changelog
|
|
119
|
+
|
|
120
|
+
# 0.2.4
|
|
121
|
+
|
|
122
|
+
CHange to importlib [#48](https://github.com/Octoframes/jupyter_compare_view/pull/48)
|
|
123
|
+
|
|
124
|
+
## 0.2.3
|
|
125
|
+
|
|
126
|
+
Remove setuptools dependency
|
|
127
|
+
## 0.2.2
|
|
128
|
+
|
|
129
|
+
* Remove python3.7 support [#46](https://github.com/Octoframes/jupyter_compare_view/pull/46)
|
|
130
|
+
* fix jupyterlite example
|
|
131
|
+
## 0.2.1
|
|
132
|
+
|
|
133
|
+
* Support python 3.11
|
|
134
|
+
|
|
135
|
+
## 0.2.0
|
|
136
|
+
|
|
137
|
+
* Implemented `capture` to display the compare view frame without calling the cell magic. This is not an ipywidget as mentioned [here](https://github.com/Octoframes/jupyter_compare_view/pull/41#pullrequestreview-1205327074).
|
|
138
|
+
* Update version requirements [#42](https://github.com/Octoframes/jupyter_compare_view/pull/42/files)
|
|
139
|
+
## 0.1.5
|
|
140
|
+
|
|
141
|
+
* BugFix: Remove black import that was added by accident.
|
|
142
|
+
|
|
143
|
+
## 0.1.4
|
|
144
|
+
|
|
145
|
+
* `%%compare` is now `%%splity`. `%%splity` is deprecated.
|
|
146
|
+
* Update examples
|
|
147
|
+
|
|
148
|
+
## 0.1.3
|
|
149
|
+
|
|
150
|
+
* octoframes github actions setup
|
|
151
|
+
|
|
152
|
+
## 0.1.2
|
|
153
|
+
|
|
154
|
+
* Move the repo from kolibril13/jupyter-spitview to octoframes/jupyter_compare_view
|
|
155
|
+
* Rename all references
|
|
156
|
+
## 0.1.1
|
|
157
|
+
|
|
158
|
+
* Drop the [github.com/NUKnightLab/juxtapose](https://github.com/NUKnightLab/juxtapose) backend and replace it with [github.com/Octoframes/compare_view](https://github.com/Octoframes/compare_view).
|
|
159
|
+
* Implement horizontal slider
|
|
160
|
+
* Implement Round Mask
|
|
161
|
+
## 0.1.0
|
|
162
|
+
|
|
163
|
+
* Update dependencies
|
|
164
|
+
* Update JupyterLite version
|
|
165
|
+
* Fix: in JupyterLite, a figure has to be explicitly called by plt.show()
|
|
166
|
+
* Better installation workflow
|
|
167
|
+
|
|
168
|
+
## 0.0.8
|
|
169
|
+
|
|
170
|
+
* Fixing problem with cell id and notebook reloading
|
|
171
|
+
* Experimentally lowering the dependencies to
|
|
172
|
+
`ipython = ">=6.0.0"` and `ipykernel = ">=5.0.0"` so that jupyterlite will work hopefully.
|
|
173
|
+
|
|
174
|
+
## 0.0.7
|
|
175
|
+
|
|
176
|
+
* Rewrite of the import of JavaScript and CSS to make it more robust when closing and opening the notebook
|
|
177
|
+
* First attempt to add a JupyterLite example.
|
|
178
|
+
## 0.0.6
|
|
179
|
+
|
|
180
|
+
Fix poetry workflow
|
|
181
|
+
|
|
182
|
+
## 0.0.5
|
|
183
|
+
|
|
184
|
+
* Ship the javascript directly with the package, so no internet connection is required
|
|
185
|
+
* use jinja2 to save HTML in separate file
|
|
186
|
+
* load stylesheet and javascript only once in the beginning, and not in every cell that contains the splitview widget.
|
|
187
|
+
|
|
188
|
+
## 0.0.4
|
|
189
|
+
|
|
190
|
+
* New `--height` parameter
|
|
191
|
+
|
|
192
|
+
## 0.0.3
|
|
193
|
+
|
|
194
|
+
* default slider position
|
|
195
|
+
* updated minimal example
|
|
196
|
+
* internal code restructuring and formatting
|
|
197
|
+
* Handle import in non jupyter context
|
|
198
|
+
|
|
199
|
+
## 0.0.2
|
|
200
|
+
* save images in base64 strings and don't load images to disk (increases package security).
|
|
201
|
+
## 0.0.1
|
|
202
|
+
|
|
203
|
+
* First release
|
|
204
|
+
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Jupyter compare_view
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
[](https://octoframes.github.io/jupyter_compare_view)
|
|
6
|
+
[](https://mybinder.org/v2/gh/Octoframes/jupyter_compare_view/HEAD?labpath=example_notebook.ipynb)
|
|
7
|
+
[](https://colab.research.google.com/github/Octoframes/jupyter_compare_view/blob/main/example_notebook.ipynb)
|
|
8
|
+
[](https://badge.fury.io/py/jupyter_compare_view)
|
|
9
|
+
[](https://github.com/Octoframes/jupyter_compare_view/blob/main/LICENSE)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Blend between multiple images using a cell magic in JupyterLab using [compare_view](https://octoframes.github.io/compare_view).
|
|
13
|
+
*This project was called jupyter-splitview before.*
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
```py
|
|
18
|
+
pip install jupyter_compare_view
|
|
19
|
+
```
|
|
20
|
+
## Example
|
|
21
|
+
```py
|
|
22
|
+
import jupyter_compare_view
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
%%compare
|
|
27
|
+
from skimage import data
|
|
28
|
+
from skimage.color import rgb2gray
|
|
29
|
+
import matplotlib.pyplot as plt
|
|
30
|
+
|
|
31
|
+
img = data.chelsea()
|
|
32
|
+
grayscale_img = rgb2gray(img)
|
|
33
|
+
|
|
34
|
+
plt.imshow(img)
|
|
35
|
+
plt.axis("off")
|
|
36
|
+
plt.show()
|
|
37
|
+
|
|
38
|
+
plt.imshow(grayscale_img, cmap="gray")
|
|
39
|
+
plt.axis("off")
|
|
40
|
+
plt.show()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
<img src="https://user-images.githubusercontent.com/44469195/179499138-65160434-11e1-4358-8e25-5b26ba9ebf4a.png" style="width: 400px;"/>
|
|
44
|
+
|
|
45
|
+
Another example:
|
|
46
|
+
```py
|
|
47
|
+
from jupyter_compare_view import compare
|
|
48
|
+
|
|
49
|
+
compare(img, grayscale_img, cmap="gray", start_mode="horizontal", start_slider_pos=0.73)
|
|
50
|
+
```
|
|
51
|
+
<img src="https://user-images.githubusercontent.com/44469195/179499350-94244408-cabf-4945-affc-fd0444d53555.png" style="width: 400px;"/>
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
The split view widget is still responsive after closing and reopening the notebook without running the cell again.
|
|
55
|
+
|
|
56
|
+
## Notebook arguments
|
|
57
|
+
(Might still change in future)
|
|
58
|
+
* `--config '{"start_mode": "horizontal"}'` will init the compare-view in horizontal slider mode.
|
|
59
|
+
* `--config '{"circle_size": 30}'` the circle size is now 30 pixel in circle mode.
|
|
60
|
+
* `--config '{"show_slider": false}'` will hide the slider bar.
|
|
61
|
+
* `--config '{"start_slider_pos": 0.73}'` will set the slider start position to 73%.
|
|
62
|
+
|
|
63
|
+
* *Removed in 0.1.1: `--position 73%` will no longer the slider start position to 73%.*
|
|
64
|
+
* `--config '{"start_mode": "horizontal","start_slider_pos": 0.73}'` will both set the start mode to horizontal and set the slider position
|
|
65
|
+
* `--height 220` will set the height to 220 pixel.
|
|
66
|
+
* When `--height`is not provided, the default height of the widget is 300 pixel.
|
|
67
|
+
* `--height auto` will set the height by the value of the first image's resolution in vertical direction.
|
|
68
|
+
* The widget's width will always be adjusted automatically.
|
|
69
|
+
|
|
70
|
+
## Notebook formatting
|
|
71
|
+
Formatting with black can be done this way:
|
|
72
|
+
1. `pip install 'black[jupyter]'`
|
|
73
|
+
2. `black --python-cell-magics compare compare_view_magic.ipynb`
|
|
74
|
+
|
|
75
|
+
## Interactive Export
|
|
76
|
+
jupyter_compare_view fully supports offline interactive HTML exports.
|
|
77
|
+
The web library [compare_view](https://octoframes.github.io/compare_view) is inlined into the exported HTML document.
|
|
78
|
+
Therefore, the export is viewable without an internet connection.
|
|
79
|
+
|
|
80
|
+

|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
## Developer Installation
|
|
84
|
+
|
|
85
|
+
1. `git clone --recurse https://github.com/Octoframes/jupyter_compare_view`
|
|
86
|
+
(Note: In case that the repo was already cloned e.g. with the GitHub Desktop client, the GitHub submodule has to be loaded via `git submodule update --init --recursive`)
|
|
87
|
+
2. `poetry install`
|
|
88
|
+
|
|
89
|
+
*Note*: The IPython extension `autoreload` reloads modules before every cell execution. Very useful when debugging the `%%capture` cell magic!
|
|
90
|
+
Just add these lines into the first jupyter cell.
|
|
91
|
+
```py
|
|
92
|
+
%load_ext autoreload
|
|
93
|
+
%autoreload 2
|
|
94
|
+
import jupyter_compare_view
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Changelog
|
|
98
|
+
|
|
99
|
+
# 0.2.4
|
|
100
|
+
|
|
101
|
+
CHange to importlib [#48](https://github.com/Octoframes/jupyter_compare_view/pull/48)
|
|
102
|
+
|
|
103
|
+
## 0.2.3
|
|
104
|
+
|
|
105
|
+
Remove setuptools dependency
|
|
106
|
+
## 0.2.2
|
|
107
|
+
|
|
108
|
+
* Remove python3.7 support [#46](https://github.com/Octoframes/jupyter_compare_view/pull/46)
|
|
109
|
+
* fix jupyterlite example
|
|
110
|
+
## 0.2.1
|
|
111
|
+
|
|
112
|
+
* Support python 3.11
|
|
113
|
+
|
|
114
|
+
## 0.2.0
|
|
115
|
+
|
|
116
|
+
* Implemented `capture` to display the compare view frame without calling the cell magic. This is not an ipywidget as mentioned [here](https://github.com/Octoframes/jupyter_compare_view/pull/41#pullrequestreview-1205327074).
|
|
117
|
+
* Update version requirements [#42](https://github.com/Octoframes/jupyter_compare_view/pull/42/files)
|
|
118
|
+
## 0.1.5
|
|
119
|
+
|
|
120
|
+
* BugFix: Remove black import that was added by accident.
|
|
121
|
+
|
|
122
|
+
## 0.1.4
|
|
123
|
+
|
|
124
|
+
* `%%compare` is now `%%splity`. `%%splity` is deprecated.
|
|
125
|
+
* Update examples
|
|
126
|
+
|
|
127
|
+
## 0.1.3
|
|
128
|
+
|
|
129
|
+
* octoframes github actions setup
|
|
130
|
+
|
|
131
|
+
## 0.1.2
|
|
132
|
+
|
|
133
|
+
* Move the repo from kolibril13/jupyter-spitview to octoframes/jupyter_compare_view
|
|
134
|
+
* Rename all references
|
|
135
|
+
## 0.1.1
|
|
136
|
+
|
|
137
|
+
* Drop the [github.com/NUKnightLab/juxtapose](https://github.com/NUKnightLab/juxtapose) backend and replace it with [github.com/Octoframes/compare_view](https://github.com/Octoframes/compare_view).
|
|
138
|
+
* Implement horizontal slider
|
|
139
|
+
* Implement Round Mask
|
|
140
|
+
## 0.1.0
|
|
141
|
+
|
|
142
|
+
* Update dependencies
|
|
143
|
+
* Update JupyterLite version
|
|
144
|
+
* Fix: in JupyterLite, a figure has to be explicitly called by plt.show()
|
|
145
|
+
* Better installation workflow
|
|
146
|
+
|
|
147
|
+
## 0.0.8
|
|
148
|
+
|
|
149
|
+
* Fixing problem with cell id and notebook reloading
|
|
150
|
+
* Experimentally lowering the dependencies to
|
|
151
|
+
`ipython = ">=6.0.0"` and `ipykernel = ">=5.0.0"` so that jupyterlite will work hopefully.
|
|
152
|
+
|
|
153
|
+
## 0.0.7
|
|
154
|
+
|
|
155
|
+
* Rewrite of the import of JavaScript and CSS to make it more robust when closing and opening the notebook
|
|
156
|
+
* First attempt to add a JupyterLite example.
|
|
157
|
+
## 0.0.6
|
|
158
|
+
|
|
159
|
+
Fix poetry workflow
|
|
160
|
+
|
|
161
|
+
## 0.0.5
|
|
162
|
+
|
|
163
|
+
* Ship the javascript directly with the package, so no internet connection is required
|
|
164
|
+
* use jinja2 to save HTML in separate file
|
|
165
|
+
* load stylesheet and javascript only once in the beginning, and not in every cell that contains the splitview widget.
|
|
166
|
+
|
|
167
|
+
## 0.0.4
|
|
168
|
+
|
|
169
|
+
* New `--height` parameter
|
|
170
|
+
|
|
171
|
+
## 0.0.3
|
|
172
|
+
|
|
173
|
+
* default slider position
|
|
174
|
+
* updated minimal example
|
|
175
|
+
* internal code restructuring and formatting
|
|
176
|
+
* Handle import in non jupyter context
|
|
177
|
+
|
|
178
|
+
## 0.0.2
|
|
179
|
+
* save images in base64 strings and don't load images to disk (increases package security).
|
|
180
|
+
## 0.0.1
|
|
181
|
+
|
|
182
|
+
* First release
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from IPython import get_ipython
|
|
2
|
+
import importlib.metadata
|
|
3
|
+
from .compare import compare, StartMode
|
|
4
|
+
from .sw_cellmagic import CompareViewMagic
|
|
5
|
+
|
|
6
|
+
__version__: str = importlib.metadata.version(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
ipy = get_ipython()
|
|
11
|
+
ipy.register_magics(CompareViewMagic)
|
|
12
|
+
print(f"Jupyter compare_view v{__version__}")
|
|
13
|
+
except AttributeError:
|
|
14
|
+
print("Can not load CompareViewMagic because this is not a notebook")
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import enum
|
|
3
|
+
import io
|
|
4
|
+
import json
|
|
5
|
+
import typing
|
|
6
|
+
import uuid
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from jinja2 import Template, StrictUndefined
|
|
9
|
+
import IPython
|
|
10
|
+
import PIL
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
ImageLike = typing.TypeVar('ImageLike')
|
|
14
|
+
ImageSource = typing.Union[str, bytes, ImageLike]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def img2bytes(img: ImageLike, format: str, cmap: str) -> bytes:
|
|
18
|
+
with io.BytesIO() as im_file:
|
|
19
|
+
if isinstance(img, PIL.Image.Image):
|
|
20
|
+
img.save(im_file, format=format)
|
|
21
|
+
else:
|
|
22
|
+
# anything other that can be displayed with plt.imshow
|
|
23
|
+
import matplotlib.pyplot as plt
|
|
24
|
+
|
|
25
|
+
plt.imsave(im_file, img, format=format, cmap=cmap)
|
|
26
|
+
return im_file.getvalue()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def img2url(img: ImageSource, format: str, cmap: str) -> str:
|
|
30
|
+
if isinstance(img, str):
|
|
31
|
+
return img.strip()
|
|
32
|
+
if isinstance(img, bytes):
|
|
33
|
+
data = img
|
|
34
|
+
else:
|
|
35
|
+
data = img2bytes(img, format=format, cmap=cmap)
|
|
36
|
+
return f"data:image/{format};base64,{str(base64.b64encode(data), 'utf8')}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def compile_template(in_file: str, **variables) -> str:
|
|
40
|
+
with open(in_file, "r", encoding="utf-8") as file:
|
|
41
|
+
template = Template(file.read(), undefined=StrictUndefined)
|
|
42
|
+
return template.render(**variables)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def prepare_html(image_urls: typing.List[str], height: str, add_controls: bool, config: dict) -> str:
|
|
46
|
+
uid=uuid.uuid1()
|
|
47
|
+
config['key'] = str(uid)
|
|
48
|
+
if add_controls:
|
|
49
|
+
config["controls_id"] = f"controls_{uid}"
|
|
50
|
+
root = Path(__file__).parent
|
|
51
|
+
js_path = root / "../vendor/compare_view/browser_compare_view.js"
|
|
52
|
+
js = js_path.read_text()
|
|
53
|
+
return compile_template(
|
|
54
|
+
root / "template.html",
|
|
55
|
+
uid=uid,
|
|
56
|
+
image_urls=image_urls,
|
|
57
|
+
height=height,
|
|
58
|
+
js=js,
|
|
59
|
+
add_controls=add_controls,
|
|
60
|
+
config=json.dumps(config),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@enum.unique
|
|
65
|
+
class StartMode(str, enum.Enum):
|
|
66
|
+
CIRCLE = "circle"
|
|
67
|
+
HORIZONTAL = "horizontal"
|
|
68
|
+
VERTICAL = "vertical"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def compare(
|
|
72
|
+
image1: ImageSource,
|
|
73
|
+
image2: ImageSource,
|
|
74
|
+
*other_images: ImageSource,
|
|
75
|
+
height: typing.Union[str, int] = 'auto',
|
|
76
|
+
add_controls: bool = True,
|
|
77
|
+
start_mode: typing.Union[StartMode, str] = StartMode.CIRCLE,
|
|
78
|
+
circumference_fraction: float = 0.005,
|
|
79
|
+
circle_size: typing.Optional[float] = None,
|
|
80
|
+
circle_fraction: float = 0.2,
|
|
81
|
+
show_circle: bool = True,
|
|
82
|
+
revolve_imgs_on_click: bool = True,
|
|
83
|
+
slider_fraction: float = 0.01,
|
|
84
|
+
slider_time: float = 400,
|
|
85
|
+
# rate_function: str = 'ease_in_out_cubic',
|
|
86
|
+
start_slider_pos: float = 0.5,
|
|
87
|
+
show_slider: bool = True,
|
|
88
|
+
display_format: str = 'jpeg',
|
|
89
|
+
cmap: typing.Optional[str] = None,
|
|
90
|
+
) -> IPython.display.HTML:
|
|
91
|
+
"""
|
|
92
|
+
Args:
|
|
93
|
+
height: height of the widget in pixels or "auto"
|
|
94
|
+
add_controls: pass False to not create controls
|
|
95
|
+
start_mode: either "circle", "horizontal" or "vertical"
|
|
96
|
+
circumference_fraction: size of circle outline as fraction of image width or height (whatever is bigger)
|
|
97
|
+
circle_size: the radius in pixel
|
|
98
|
+
circle_fraction: a fraction of the image width or height (whichever is bigger—called max_size in this document)
|
|
99
|
+
show_circle: draw line around circle
|
|
100
|
+
slider_time: time slider takes to reach clicked location
|
|
101
|
+
start_slider_pos: 0.0 -> left; 1.0 -> right
|
|
102
|
+
show_slider: draw line at slider
|
|
103
|
+
display_format: format used for displaying images
|
|
104
|
+
cmap: colormap for grayscale images
|
|
105
|
+
"""
|
|
106
|
+
images = [image1, image2, *other_images]
|
|
107
|
+
image_urls = [
|
|
108
|
+
img2url(img, format=display_format, cmap=cmap) for img in images
|
|
109
|
+
]
|
|
110
|
+
_locals = locals()
|
|
111
|
+
config = {k: _locals[k] for k in [
|
|
112
|
+
'start_mode',
|
|
113
|
+
'circumference_fraction',
|
|
114
|
+
'circle_fraction',
|
|
115
|
+
'show_circle',
|
|
116
|
+
'revolve_imgs_on_click',
|
|
117
|
+
'slider_fraction',
|
|
118
|
+
'slider_time',
|
|
119
|
+
# 'rate_function',
|
|
120
|
+
'start_slider_pos',
|
|
121
|
+
'show_slider',
|
|
122
|
+
]
|
|
123
|
+
+ ['circle_size'] * (circle_size is not None)
|
|
124
|
+
}
|
|
125
|
+
html = prepare_html(
|
|
126
|
+
image_urls=image_urls,
|
|
127
|
+
height=f'{height}px' if not isinstance(height, str) else height,
|
|
128
|
+
add_controls=add_controls,
|
|
129
|
+
config=config,
|
|
130
|
+
)
|
|
131
|
+
return IPython.display.HTML(html)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from IPython.core import magic_arguments
|
|
5
|
+
from IPython.core.magic import Magics, cell_magic, magics_class
|
|
6
|
+
from IPython.utils.capture import capture_output
|
|
7
|
+
from PIL import Image
|
|
8
|
+
|
|
9
|
+
from .compare import compare
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@magics_class
|
|
13
|
+
class CompareViewMagic(Magics):
|
|
14
|
+
@magic_arguments.magic_arguments()
|
|
15
|
+
@magic_arguments.argument( # TODO This is currently not used.
|
|
16
|
+
"--position",
|
|
17
|
+
"-p",
|
|
18
|
+
default="50%",
|
|
19
|
+
help="""The start position of the slider. Currently not implemented, use `--config '{"start_slider_pos": 0.73}'` instead""",
|
|
20
|
+
)
|
|
21
|
+
@magic_arguments.argument(
|
|
22
|
+
"--height",
|
|
23
|
+
"-h",
|
|
24
|
+
default="auto",
|
|
25
|
+
help=(
|
|
26
|
+
"The widget's height. The width will be adjusted automatically. \
|
|
27
|
+
If height is `auto`, the vertical resolution of the first image is used."
|
|
28
|
+
),
|
|
29
|
+
)
|
|
30
|
+
@magic_arguments.argument(
|
|
31
|
+
"--config",
|
|
32
|
+
"-c",
|
|
33
|
+
default="{}",
|
|
34
|
+
help=(
|
|
35
|
+
"The compare view config as described here: https://github.com/Octoframes/compare_view"
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
@cell_magic
|
|
39
|
+
def compare(self, line, cell): # TODO: make a %%splity deprecated version
|
|
40
|
+
"""Saves the png image and creates the compare_view canvas"""
|
|
41
|
+
|
|
42
|
+
with capture_output(stdout=False, stderr=False, display=True) as result:
|
|
43
|
+
self.shell.run_cell(cell)
|
|
44
|
+
|
|
45
|
+
# saves all jupyter output images into the out_images_base64 list
|
|
46
|
+
out_images_base64 = []
|
|
47
|
+
for output in result.outputs:
|
|
48
|
+
data = output.data
|
|
49
|
+
if "image/png" in data:
|
|
50
|
+
png_bytes_data = data["image/png"]
|
|
51
|
+
if isinstance(png_bytes_data, str):
|
|
52
|
+
png_bytes_data = f'data:image/png;base64,{png_bytes_data}'
|
|
53
|
+
out_images_base64.append(png_bytes_data)
|
|
54
|
+
if len(out_images_base64) < 2:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
"There need to be at least two images for Jupyter compare_view to work."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# get the parameters that configure the widget
|
|
60
|
+
args = magic_arguments.parse_argstring(CompareViewMagic.compare, line)
|
|
61
|
+
height = args.height
|
|
62
|
+
|
|
63
|
+
return compare(
|
|
64
|
+
*out_images_base64,
|
|
65
|
+
**{
|
|
66
|
+
**json.loads(args.config.strip("'").strip('"')),
|
|
67
|
+
"height": height if height == "auto" else int(height)
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@cell_magic
|
|
72
|
+
def splity(self, line, cell): # TODO: Delete this somewhere 10/2022.
|
|
73
|
+
import warnings
|
|
74
|
+
|
|
75
|
+
print(
|
|
76
|
+
"""
|
|
77
|
+
**************************************************************
|
|
78
|
+
Warning: %%splity is deprecated. Please use %%compare instead.
|
|
79
|
+
**************************************************************
|
|
80
|
+
"""
|
|
81
|
+
)
|
|
82
|
+
new_line = "%%compare"
|
|
83
|
+
new_line += line
|
|
84
|
+
complete_cell = new_line + "\n" + cell
|
|
85
|
+
self.shell.run_cell(complete_cell)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
{{ js }}
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div style="display: flex; flex-direction: row; width: 100%;">
|
|
6
|
+
<canvas id="canvas_{{ uid }}" style="height: {{ height }};"></canvas>
|
|
7
|
+
{% if add_controls %}
|
|
8
|
+
<div id="controls_{{ uid }}" style="width: auto; margin-right: 10px;"></div>
|
|
9
|
+
{% endif %}
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<script>
|
|
13
|
+
compare_view.load(
|
|
14
|
+
[{% for image_url in image_urls %}
|
|
15
|
+
"{{ image_url }}",
|
|
16
|
+
{% endfor %}],
|
|
17
|
+
"canvas_{{ uid }}",
|
|
18
|
+
{{config}}
|
|
19
|
+
);
|
|
20
|
+
</script>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "jupyter_compare_view"
|
|
3
|
+
version = "0.2.5"
|
|
4
|
+
description = "Blend Between Multiple Images in JupyterLab."
|
|
5
|
+
authors = ["Octoframes"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
include = ["vendor/compare_view/browser_compare_view.js"]
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = ">=3.8,<3.13"
|
|
12
|
+
ipython = ">=6.0.0"
|
|
13
|
+
ipykernel = ">=5.0.0"
|
|
14
|
+
Pillow = ">=7.1.2"
|
|
15
|
+
Jinja2 = ">=2.11.3"
|
|
16
|
+
|
|
17
|
+
[tool.poetry.dev-dependencies]
|
|
18
|
+
black = { extras = ["jupyter"], version = ">=22.3.0" }
|
|
19
|
+
matplotlib = ">=3.5.1"
|
|
20
|
+
scipy = ">=1.8.0"
|
|
21
|
+
scikit-image = ">=0.19.2"
|
|
22
|
+
pytest = ">=7.1.2"
|
|
23
|
+
isort = ">=5.10.1"
|
|
24
|
+
jupyterlab = ">=4.5.9"
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["poetry-core>=1.0.0"]
|
|
28
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var compare_view;(()=>{"use strict";var e,t,c={d:(e,t)=>{for(var n in t)c.o(t,n)&&!c.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},n={};function o(e){var t,c;let n=null===(t=e[0])||void 0===t?void 0:t.element.width,o=null===(c=e[0])||void 0===c?void 0:c.element.height;for(let t of e)t.element.width==n&&t.element.height==o||console.log("Warning: images don't have the same resolution"),n=Math.max(n,t.element.width),o=Math.max(o,t.element.height);return[n,o]}function a(e){return e.images.unshift(e.images.pop()),!0}function i(e,c,n){c.onchange=()=>{c.checked?(e.next_mode=n,b(e,t.change_mode)):r(e)}}function r(t){var c;if(null!=t.ctrl_data)switch((c=t.ctrl_data).circle_check.checked=!1,c.horizontal_check.checked=!1,c.vertical_check.checked=!1,t.current_mode){case e.circle:t.ctrl_data.circle_check.checked=!0;break;case e.horizontal:t.ctrl_data.horizontal_check.checked=!0;break;case e.vertical:t.ctrl_data.vertical_check.checked=!0;break;default:throw`unsupported mode: ${t.current_mode}`}}function s(e,t){let c=e.canvas.getBoundingClientRect(),n=e.width/c.width,o=e.height/c.height;e.mouse_pos=[(t.clientX-c.left)*n,(t.clientY-c.top)*o]}function l(e){return e.render_circle=!0,e.canvas.style.cursor="none",!1}function u(e){return x(e,t.update_circle),e.canvas.style.cursor="default",!0}function d(e,t,c){e.ctx.beginPath(),e.ctx.arc(e.mouse_pos[0],e.mouse_pos[1],e.circle_size,t,c),c-t!=2*Math.PI&&e.ctx.lineTo(e.mouse_pos[0],e.mouse_pos[1]),e.ctx.closePath()}function _(e,t,c,n){var o;d(e,c,n),e.ctx.save(),e.ctx.clip(),e.ctx.drawImage(null===(o=e.images[t])||void 0===o?void 0:o.element,0,0,e.width,e.height),e.ctx.restore(),e.show_circle&&(d(e,c,n),e.ctx.strokeStyle="black",e.ctx.lineWidth=e.circumference_thickness,e.ctx.stroke())}function h(c,n){return b(c,t.update_slider),c.start_timestamp=n,c.start_pos=c.slider_pos,c.current_mode==e.horizontal?c.target_pos=c.mouse_pos[0]/c.width:c.target_pos=c.mouse_pos[1]/c.height,!0}function m(t,c){return t.held_down&&(t.current_mode==e.horizontal?t.target_pos=t.mouse_pos[0]/t.width:t.target_pos=t.mouse_pos[1]/t.height,t.start_pos=t.target_pos,t.slider_pos=t.target_pos),!0}function v(e,t){let c=(t-e.start_timestamp)/e.slider_time;return c=Math.min(Math.max(c,0),1),c=e.rate_function(c),e.slider_pos=(1-c)*e.start_pos+c*e.target_pos,e.slider_pos==e.target_pos}function p(c){switch(c.current_mode){case e.undefined:break;case e.circle:!function(e){e.canvas.onmousemove=null,e.canvas.onmouseleave=null,e.canvas.ontouchstart=null,e.canvas.onmousedown=null,e.canvas.onfocus=null,e.canvas.onblur=null,e.canvas.style.cursor="default",document.body.style.userSelect="text",e.touching=!1,b(e,t.remove_circle)}(c);break;case e.horizontal:case e.vertical:!function(e){e.canvas.onmousedown=null,e.canvas.onmouseup=null,document.onmouseup=null,e.canvas.onmouseup=null,e.canvas.onmousemove=null,e.canvas.onmouseenter=null,e.canvas.onmouseleave=null,e.canvas.onfocus=null,e.canvas.onblur=null,e.held_down=!1,e.canvas.style.cursor="default",document.body.style.userSelect="text"}(c);break;default:throw`unsupported mode: ${c.current_mode}`}}function g(c){switch(c.current_mode=c.next_mode,c.next_mode){case e.circle:!function(e){e.canvas.onmousemove=c=>{s(e,c),b(e,t.update_circle)},e.canvas.onmouseleave=()=>{b(e,t.remove_circle)},e.revolve_imgs_on_click&&(e.canvas.ontouchstart=()=>{e.touching=!0},e.canvas.onmousedown=()=>{e.touching||b(e,t.revolve_imgs)}),e.canvas.matches(":hover")&&b(e,t.update_circle),e.canvas.onfocus=()=>{document.body.style.userSelect="none"},e.canvas.onblur=()=>{document.body.style.userSelect="text",e.touching=!1}}(c);break;case e.horizontal:case e.vertical:!function(c){c.canvas.onmousedown=()=>{c.held_down=!0,b(c,t.start_slider_move)},c.canvas.onmouseup=()=>{c.held_down=!1},document.onmouseup=()=>{c.held_down=!1},c.canvas.onmousemove=e=>{s(c,e),b(c,t.possible_instant_slide)},c.current_mode==e.horizontal?c.canvas.onmouseenter=()=>{c.canvas.style.cursor="ew-resize"}:c.canvas.onmouseenter=()=>{c.canvas.style.cursor="ns-resize"},c.canvas.onmouseleave=()=>{c.canvas.style.cursor="default"},c.canvas.onfocus=()=>{document.body.style.userSelect="none"},c.canvas.onblur=()=>{document.body.style.userSelect="text"}}(c);break;default:throw`unsupported mode: ${c.current_mode}`}r(c)}function f(e){return p(e),g(e),!0}function w(c,n){!function(e,c){let n=[];for(;e.task_stack.length;){let o,i=e.task_stack.pop();switch(i){case t.none:o=!0;break;case t.change_mode:o=f(e);break;case t.revolve_imgs:o=a(e);break;case t.update_circle:o=l(e);break;case t.remove_circle:o=u(e);break;case t.start_slider_move:o=h(e,c);break;case t.possible_instant_slide:o=m(e);break;case t.update_slider:o=v(e,c);break;default:throw`unknown task: ${i}`}o||n.push(i)}e.task_stack=n}(c,n),function(t){switch(t.current_mode){case e.circle:!function(e){var t;if(e.render_circle){e.ctx.clearRect(0,0,e.width,e.height),function(e){var t;e.ctx.beginPath(),e.ctx.arc(e.mouse_pos[0],e.mouse_pos[1],e.circle_size-1,0,2*Math.PI),e.ctx.lineTo(e.width,0),e.ctx.lineTo(0,0),e.ctx.lineTo(0,e.height),e.ctx.lineTo(e.width,e.height),e.ctx.lineTo(e.width,0),e.ctx.closePath(),e.ctx.save(),e.ctx.clip(),e.ctx.drawImage(null===(t=e.images[0])||void 0===t?void 0:t.element,0,0,e.width,e.height),e.ctx.restore()}(e);for(let t=1;t<e.images_len;++t)_(e,t,(t-1)*Math.PI*2/(e.images_len-1),t*Math.PI*2/(e.images_len-1))}else e.ctx.clearRect(0,0,e.width,e.height),e.ctx.drawImage(null===(t=e.images[0])||void 0===t?void 0:t.element,0,0,e.width,e.height);e.render_circle=!1}(t);break;case e.horizontal:case e.vertical:!function(t){var c;t.ctx.clearRect(0,0,t.width,t.height),t.ctx.drawImage(null===(c=t.images[0])||void 0===c?void 0:c.element,0,0,t.width,t.height),t.show_slider&&function(t){t.ctx.beginPath(),t.current_mode==e.horizontal?(t.ctx.moveTo(t.slider_pos*t.width,0),t.ctx.lineTo(t.slider_pos*t.width,t.height)):(t.ctx.moveTo(0,t.slider_pos*t.height),t.ctx.lineTo(t.width,t.slider_pos*t.height)),t.ctx.closePath(),t.ctx.strokeStyle="black",t.ctx.lineWidth=t.slider_thickness,t.ctx.stroke()}(t),function(t){var c;t.ctx.beginPath(),t.current_mode==e.horizontal?t.ctx.rect(t.slider_pos*t.width,0,t.width,t.height):t.ctx.rect(0,t.slider_pos*t.height,t.width,t.height),t.ctx.closePath(),t.ctx.save(),t.ctx.clip(),t.ctx.clearRect(0,0,t.width,t.height),t.ctx.drawImage(null===(c=t.images[1])||void 0===c?void 0:c.element,0,0,t.width,t.height),t.ctx.restore()}(t)}(t);break;default:throw`unsupported mode: ${t.current_mode}`}}(c),c.task_stack.length?k(c):c.next_update_queued=!1}function k(e){e.next_update_queued=!0,window.requestAnimationFrame((t=>{w(e,t)}))}function x(e,t){let c=e.task_stack.indexOf(t);-1!=c&&e.task_stack.splice(c,1)}function b(e,t){x(e,t),e.task_stack.push(t),function(e){e.next_update_queued||k(e)}(e)}c.r(n),c.d(n,{load:()=>I}),function(e){e[void 0]="undefined",e.horizontal="horizontal",e.vertical="vertical",e.circle="circle"}(e||(e={})),function(e){e[e.none=0]="none",e[e.revolve_imgs=1]="revolve_imgs",e[e.change_mode=2]="change_mode",e[e.update_circle=3]="update_circle",e[e.remove_circle=4]="remove_circle",e[e.start_slider_move=5]="start_slider_move",e[e.possible_instant_slide=6]="possible_instant_slide",e[e.update_slider=7]="update_slider"}(t||(t={}));const y={start_mode:e.circle,circumference_fraction:.005,circle_size:void 0,circle_fraction:.2,show_circle:!0,revolve_imgs_on_click:!0,slider_fraction:.01,slider_time:400,rate_function:function(e){return e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2},start_slider_pos:.5,show_slider:!0};function z(e,t){return null!=e[t]?e[t]:y[t]}function M(c,n,a,r){!function(e,t){if(e.length<2)throw`image_urls must contain at least two images, not ${e.length}`;let c=[],n=0,a=e.length;for(let i=0;i<a;++i){let r={url:e[i],element:document.createElement("img"),label:`${i}`};c.push(r),r.element.onload=()=>{++n,n==a&&t(c,o(c))},r.element.src=r.url}}(c,((c,o)=>{let s={images:c,images_len:c.length,canvas:n.canvas,ctx:n,width:0,height:0,ctrl_data:r,mouse_pos:[0,0],held_down:!1,next_mode:z(a,"start_mode"),current_mode:e.undefined,task_stack:[],next_update_queued:!1,circumference_thickness:0,render_circle:!1,circle_size:0,show_circle:z(a,"show_circle"),revolve_imgs_on_click:z(a,"revolve_imgs_on_click"),touching:!1,slider_thickness:0,slider_pos:z(a,"start_slider_pos"),slider_time:z(a,"slider_time"),rate_function:z(a,"rate_function"),show_slider:z(a,"show_slider"),start_timestamp:0,start_pos:0,target_pos:0};if(function(c){null!=c.ctrl_data&&(i(c,c.ctrl_data.circle_check,e.circle),i(c,c.ctrl_data.horizontal_check,e.horizontal),i(c,c.ctrl_data.vertical_check,e.vertical),c.ctrl_data.revolve_imgs_button.onclick=()=>{b(c,t.revolve_imgs)})}(s),function(e,t,c){e.canvas.width=t[0],e.canvas.height=t[1],e.width=e.canvas.width,e.height=e.canvas.height,function(e,t){let c=Math.max(e.canvas.width,e.canvas.height);e.circumference_thickness=c*z(t,"circumference_fraction"),e.circle_size=null!=t.circle_size?t.circle_size:c*z(t,"circle_fraction"),e.slider_thickness=c*z(t,"slider_fraction")}(e,c)}(s,o,a),"y"===s.canvas.dataset.in_use)throw`the canvas with the id '${s.canvas.id}' is already in use`;s.canvas.dataset.in_use="y",b(s,t.change_mode)}))}function P(e,t,c){let n=document.createElement("input");n.type="checkbox",n.id=e;let o=document.createElement("label");o.innerHTML=t,o.htmlFor=n.id;let a=document.createElement("br");return c.appendChild(n),c.appendChild(o),c.appendChild(a),n}function T(e,t){let c=document.createElement("button");c.innerHTML=e;let n=document.createElement("br");return t.appendChild(c),t.appendChild(n),c}function I(e,t,c={}){M(e,function(e){let t=document.getElementById(e),c=t.getContext("2d");return t.tabIndex=1,c}(t),c,null!=c.controls_id?function(e,t=function(){let e="";for(let t=0;t<12;t++)e+="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".charAt(Math.floor(36*Math.random()));return e}()){let c=document.getElementById(e);if(null==c)throw`controls_id '${e}' isn't valid`;return{controls_parent:c,circle_check:P(`${t}_circle_button`,"Circle",c),horizontal_check:P(`${t}_horizontal_button`,"Horizontal",c),vertical_check:P(`${t}_vertical_button`,"Vertical",c),revolve_imgs_button:T("Revolve Images",c)}}(c.controls_id,c.key):void 0)}compare_view=n})();
|