mpl-stereo 0.8.1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Scott Shambaugh
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,226 @@
1
+ Metadata-Version: 2.1
2
+ Name: mpl_stereo
3
+ Version: 0.8.1
4
+ Summary: See your matplotlib plots in 3D by making stereograms and anaglyphs
5
+ License: MIT
6
+ Author: Scott Shambaugh
7
+ Author-email: scottshambaugh@users.noreply.github.com
8
+ Requires-Python: >=3.9,<4.0
9
+ Classifier: Framework :: Matplotlib
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
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: matplotlib (>=3.8.2,<4.0.0)
17
+ Requires-Dist: numpy (>=1.26.2,<2.0.0)
18
+ Description-Content-Type: text/markdown
19
+
20
+ ![Release](https://img.shields.io/github/v/release/scottshambaugh/mpl_stereo?sort=semver)
21
+ ![Builds](https://github.com/scottshambaugh/mpl_stereo/actions/workflows/builds.yml/badge.svg)
22
+ ![Tests](https://github.com/scottshambaugh/mpl_stereo/actions/workflows/tests.yml/badge.svg)
23
+ [![codecov](https://codecov.io/gh/scottshambaugh/mpl_stereo/graph/badge.svg?token=V2ZSLFUK03)](https://codecov.io/gh/scottshambaugh/mpl_stereo)
24
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/mpl_stereo)
25
+
26
+ <p float="center" align="center">
27
+ <img width="320 " height="80" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/mpl_stereo_logo_with_text.png">
28
+ </p>
29
+
30
+ Matplotlib add-on to make [stereograms](https://en.wikipedia.org/wiki/Stereoscopy) and [anaglyphs](https://en.wikipedia.org/wiki/Anaglyph_3D).
31
+
32
+ Stereographic images can significantly enhance the interpretability of 3D data by leveraging human binocular vision. Instead of looking at a flat projection on a page, stereograms give us "3D glasses" for 2D data with just our eyes.
33
+
34
+ It takes some practice to be able to view the stereoscopic effect for the first time, but the effort is well worth it!
35
+
36
+ ## Basic Usage
37
+ **Python Installation**
38
+ ```
39
+ pip install mpl_stereo
40
+ ```
41
+ **Setup**
42
+ ```python
43
+ from mpl_stereo import AxesStereo2D, AxesStereo3D, AxesAnaglyph
44
+ from mpl_stereo.example_data import trefoil
45
+ x, y, z = trefoil() # trefoil knot
46
+ ```
47
+
48
+ ### 2D Stereogram plots
49
+ Currently, only a subset of matplotlib's 2D plots are officially supported. See the list in `axstereo.known_methods`.
50
+ ```python
51
+ axstereo = AxesStereo2D()
52
+ axstereo.plot(x, y, z, c='k', alpha=0.2)
53
+ axstereo.scatter(x, y, z, c=z, cmap='viridis', s=10)
54
+ ```
55
+ <p float="left" align="center">
56
+ <img width="500" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/trefoil_2d.png">
57
+ </p>
58
+ When you are viewing the stereogram properly, you should see the knot weave in and out of the page!
59
+
60
+ *Warning*: Please note that for 2D plots the stereoscopic effect requires shifting data, so the data will *not* necessarily line up with the x-axis labels! Right now this is controlled with the `eye_balance` parameter. Calling `AxesStereo2D(eye_balance=-1)` (the default) will ensure that the left x-axis data is not shifted, whereas `AxesStereo2D(eye_balance=1)` will lock down the right x-axis data. The tick labels for x-axes where the data is not aligned will have transparency applied. So in the plot above, the right side labels being lighter gray indicates that you should not trust that x-axis data to be positioned correctly, but the left subplot with its black labeling is accurate.
61
+
62
+ ### 3D Stereogram plots
63
+ The stereoscopic effect in 3D is made just by rotating the plot view, so all of matplotlib's 3D plot types are supported, and there are no concerns about data not lining up with the axis labels.
64
+ ```python
65
+ axstereo = AxesStereo3D()
66
+ axstereo.plot(x, y, z, c='k', alpha=0.2)
67
+ axstereo.scatter(x, y, z, c=z, cmap='viridis', s=10)
68
+ ```
69
+ <p float="left" align="center">
70
+ <img width="500" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/trefoil_3d.png">
71
+ </p>
72
+
73
+ ### Red-Cyan Anaglyphs
74
+ Some 2D plots can also be made into anaglyphs, which are stereograms that can be viewed with red-cyan 3D glasses. While this allows for seeing the stereoscopic effect without training your eyes, it also means that the data cannot be otherwise colored.
75
+
76
+ The same warning as for the 2D stereo plots about shifting data applies here as well. If `eye_balance` is -1 or +1 such that the data for one of the colors is not shifted, then that color will be applied to the x-axis tick labels to show that they are accurate.
77
+ ```python
78
+ axstereo = AxesAnaglyph()
79
+ axstereo.plot(x, y, z)
80
+ axstereo.scatter(x, y, z, s=10)
81
+ ```
82
+ <p float="left" align="center">
83
+ <img width="250" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/trefoil_anaglyph.png">
84
+ </p>
85
+
86
+ ### Existing Stereo Images
87
+ Existing stereo image data can easily be plotted up side-by-side, or combined into an anaglyph. Here we plot up two 1-D grayscale images of the sun taken in July 2023 by the NASA STEREO-A and SDO spacecraft. This example was adapted from [the SunPy documentation](https://docs.sunpy.org/en/stable/generated/gallery/showcase/stereoscopic_3d.html#sphx-glr-generated-gallery-showcase-stereoscopic-3d-py).
88
+ ```python
89
+ from mpl_stereo.example_data import sun_left_right
90
+ sun_left_data, sun_right_data = sun_left_right
91
+
92
+ axstereo = AxesStereo2D()
93
+ axstereo.ax_left.imshow(sun_left_data, cmap='gray') # try other colormaps!
94
+ axstereo.ax_right.imshow(sun_right_data, cmap='gray')
95
+
96
+ axstereo = AxesAnaglyph()
97
+ axstereo.imshow_stereo(sun_left_data, sun_right_data, cmap='gray')
98
+ ```
99
+ <p float="left" align="center">
100
+ <img width="450" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/sun_2d.png">
101
+ </p>
102
+ <p float="left" align="center">
103
+ <img width="250" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/sun_anaglyph.png">
104
+ </p>
105
+
106
+ Here's another example, showing how this translates to full color data. This example is a pair of photos of [St. Mary's Church](https://commons.wikimedia.org/wiki/File:St_Mary%27s_Church,_Colston_Bassett_3D-35486887876.jpg) in Colston Basset, Britain, taken by David Skinner and shared under a [CC-BY2.0 license](https://creativecommons.org/licenses/by/2.0/deed.en).
107
+
108
+ Color anaglyph algorithms can be chosen from the methods `'dubois'`, `'photoshop'`, and `'photoshop2'`, as described in the paper [Sanders, William R., and David F. McAllister. "Producing anaglyphs from synthetic images." *Stereoscopic displays and virtual reality systems X.* Vol. 5006. SPIE, 2003.](https://research.csc.ncsu.edu/stereographics/ei03.pdf)
109
+
110
+ ```python
111
+ from mpl_stereo.example_data import church_left_right
112
+ church_left_data, church_right_data = church_left_right
113
+
114
+ # or
115
+ import matplotlib as mpl
116
+ church_left_data = mpl.image.imread('church_left.jpg')
117
+ church_right_data = mpl.image.imread('church_right.jpg')
118
+
119
+ axstereo = AxesStereo2D()
120
+ axstereo.ax_left.imshow(church_left_data)
121
+ axstereo.ax_right.imshow(church_right_data)
122
+
123
+ axstereo = AxesAnaglyph()
124
+ axstereo.imshow_stereo(church_left_data, church_right_data)
125
+ ```
126
+ <p float="left" align="center">
127
+ <img width="550" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/church_2d.png">
128
+ </p>
129
+ <p float="left" align="center">
130
+ <img width="300" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/church_anaglyph.png">
131
+ </p>
132
+
133
+ ### Wiggle Stereograms
134
+ As a final way to show off the stereoscopic effect, we can make a [wiggle stereogram](https://en.wikipedia.org/wiki/Wiggle_stereoscopy) or "wigglegram". This isn't as useful for examining data, but allows seeing the effect without having to train your eyes or using 3D glasses. The sense of depth may be enhanced if you close one eye.
135
+
136
+ ```python
137
+ axstereo = AxesStereo2D() # wiggle also works with AxesStereo3D
138
+ axstereo.ax_left.imshow(sun_left_data, cmap='gray')
139
+ axstereo.ax_right.imshow(sun_right_data, cmap='gray')
140
+ axstereo.wiggle('sun_wiggle.gif') # saves to file
141
+ ```
142
+
143
+ <p float="left" align="center">
144
+ <img width="250" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/sun_wiggle.gif">
145
+ </p>
146
+
147
+ ## Advanced Usage
148
+
149
+ ### Working With Plots
150
+ The figure and subplot axes can be accessed with the following:
151
+ ```python
152
+ axstereo.fig
153
+ axstereo.axs # (ax_left, ax_right), for AxesStereo2D and AxesStereo3D
154
+ axstereo.ax # for AxesAnaglyph
155
+ ```
156
+
157
+ Calling any method on `axstereo` will pass that method call onto all the subplot axes. In the 2D cases, the plotting methods which take in `x` and `y` arguments are intercepted and the additional `z` data is processed to obtain appropriate horizontal offsets.
158
+
159
+ ```python
160
+ # For example instead of:
161
+ for ax in axstereo.axs:
162
+ ax.set_xlabel('X Label')
163
+
164
+ # You can do the equivalent:
165
+ axstereo.set_xlabel('X Label')
166
+
167
+ # When applicable, the return values from the two method calls are returned as a tuple
168
+ (line_left, line_right) = axstereo.plot(x, y, z)
169
+ ```
170
+
171
+ ### Parallel vs Cross-Eyed Viewing
172
+ By default, the stereograms are set up for the "parallel" / "divergent" / "wall-eyed" viewing method. For "cross-eyed" viewing, initialize with a negative `ipd` parameter. An ipd (Inter-Pupilary Distance) of 65 millimeters is the default, so call `AxesStereo2D(ipd=-65)` for the default cross-eyed viewing.
173
+
174
+ ### Depth and Focal Plane Location
175
+ The apparent depth of 2D stereograms can be adjusted with the `zscale` parameter. The default is 1/4th the x-axis range, i.e. `zscale=(max(ax.get_xlim()) - ax.get_xlim()) / 4`. Decrease this number to flatten the stereogram, or increase it to exaggerate the depth.
176
+
177
+ For 2D plots and anaglyphs, the focal plane can be set with the `zzero` parameter. This is the z value that will be "on the page" when viewing the stereogram.
178
+
179
+ ```python
180
+ axstereo = AxesAnaglyph(zzero=min(z)) # data will float above the page
181
+ axstereo = AxesAnaglyph(zzero=None) # page will be at the midpoint of the data range (default)
182
+ axstereo = AxesAnaglyph(zzero=max(z)) # data will sink below the page
183
+ ```
184
+ <p float="left" align="center">
185
+ <img width="750" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/trefoil_anaglyph_zzero.png">
186
+ </p>
187
+
188
+ ### Animations
189
+ See an example of how to use this with matplotlib animations in [docs/gen_graphics.py](docs/gen_graphics.py).
190
+ <p float="left" align="center">
191
+ <img width="500" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/trefoil_2d_animation.gif">
192
+ </p>
193
+ <p float="left" align="center">
194
+ <img width="500" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/trefoil_3d_animation.gif">
195
+ </p>
196
+
197
+
198
+ ## Viewing Stereograms
199
+
200
+ These are not [*auto*stereograms](https://en.wikipedia.org/wiki/Autostereogram), like the "Magic Eye" books that were popular in the 1990's. However, they use the same viewing technique. Below is a great video by Vox on how to view stereograms, click on the image and skip to [0:35](https://www.youtube.com/watch?v=v8O8Em_RPNg&t=35s) for the how-to portion and then [4:33](https://www.youtube.com/watch?v=v8O8Em_RPNg&t=4m33s) for more examples.
201
+
202
+ <p float="left" align="center">
203
+ <a href="https://www.youtube.com/watch?v=v8O8Em_RPNg">
204
+ <img width="500" height="320" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/vox_stereogram_title_card.png">
205
+ </a>
206
+ </p>
207
+
208
+ There is also a great how-to guide at the stereoscopy blog here: [Learning to Free-View: See Stereoscopic Images with the Naked Eye](https://stereoscopy.blog/2022/03/11/learning-to-free-view-see-stereoscopic-images-with-the-naked-eye/)
209
+
210
+ ## Derivation of Geometry
211
+ Two eyes with separation `IPD` are looking at a point a distance `z` offset from a focal plane at distance `d`, resulting in view angle `θ`. If this point were projected back to the focal plane, it would be offset by `δ` from where it visually appears on that plane. This offset `δ` is used to displace each point in the 2D stereograms and anaglyphs for each eye based on its `z` value to achieve the stereoscopic effect. The `eye_balance` parameter allocates the total relative displacement of `2δ` between the two eyes.
212
+
213
+ For 3D stereograms, the azimuth view angle for the two plots is simply shifted by `θ`, with no modification to the data.
214
+
215
+ ```
216
+ θ = arctan((d - z) / (IPD / 2))
217
+ D / d = (IPD / 2) / (d - z)
218
+ δ = D - IPD / 2
219
+ = IPD / 2 * (d / (d - z) - 1)
220
+ = IPD / 2 * z / (d - z)
221
+ ```
222
+
223
+ <p float="left" align="center">
224
+ <img width="375" height="450" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/geometry.png">
225
+ </p>
226
+
@@ -0,0 +1,206 @@
1
+ ![Release](https://img.shields.io/github/v/release/scottshambaugh/mpl_stereo?sort=semver)
2
+ ![Builds](https://github.com/scottshambaugh/mpl_stereo/actions/workflows/builds.yml/badge.svg)
3
+ ![Tests](https://github.com/scottshambaugh/mpl_stereo/actions/workflows/tests.yml/badge.svg)
4
+ [![codecov](https://codecov.io/gh/scottshambaugh/mpl_stereo/graph/badge.svg?token=V2ZSLFUK03)](https://codecov.io/gh/scottshambaugh/mpl_stereo)
5
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/mpl_stereo)
6
+
7
+ <p float="center" align="center">
8
+ <img width="320 " height="80" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/mpl_stereo_logo_with_text.png">
9
+ </p>
10
+
11
+ Matplotlib add-on to make [stereograms](https://en.wikipedia.org/wiki/Stereoscopy) and [anaglyphs](https://en.wikipedia.org/wiki/Anaglyph_3D).
12
+
13
+ Stereographic images can significantly enhance the interpretability of 3D data by leveraging human binocular vision. Instead of looking at a flat projection on a page, stereograms give us "3D glasses" for 2D data with just our eyes.
14
+
15
+ It takes some practice to be able to view the stereoscopic effect for the first time, but the effort is well worth it!
16
+
17
+ ## Basic Usage
18
+ **Python Installation**
19
+ ```
20
+ pip install mpl_stereo
21
+ ```
22
+ **Setup**
23
+ ```python
24
+ from mpl_stereo import AxesStereo2D, AxesStereo3D, AxesAnaglyph
25
+ from mpl_stereo.example_data import trefoil
26
+ x, y, z = trefoil() # trefoil knot
27
+ ```
28
+
29
+ ### 2D Stereogram plots
30
+ Currently, only a subset of matplotlib's 2D plots are officially supported. See the list in `axstereo.known_methods`.
31
+ ```python
32
+ axstereo = AxesStereo2D()
33
+ axstereo.plot(x, y, z, c='k', alpha=0.2)
34
+ axstereo.scatter(x, y, z, c=z, cmap='viridis', s=10)
35
+ ```
36
+ <p float="left" align="center">
37
+ <img width="500" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/trefoil_2d.png">
38
+ </p>
39
+ When you are viewing the stereogram properly, you should see the knot weave in and out of the page!
40
+
41
+ *Warning*: Please note that for 2D plots the stereoscopic effect requires shifting data, so the data will *not* necessarily line up with the x-axis labels! Right now this is controlled with the `eye_balance` parameter. Calling `AxesStereo2D(eye_balance=-1)` (the default) will ensure that the left x-axis data is not shifted, whereas `AxesStereo2D(eye_balance=1)` will lock down the right x-axis data. The tick labels for x-axes where the data is not aligned will have transparency applied. So in the plot above, the right side labels being lighter gray indicates that you should not trust that x-axis data to be positioned correctly, but the left subplot with its black labeling is accurate.
42
+
43
+ ### 3D Stereogram plots
44
+ The stereoscopic effect in 3D is made just by rotating the plot view, so all of matplotlib's 3D plot types are supported, and there are no concerns about data not lining up with the axis labels.
45
+ ```python
46
+ axstereo = AxesStereo3D()
47
+ axstereo.plot(x, y, z, c='k', alpha=0.2)
48
+ axstereo.scatter(x, y, z, c=z, cmap='viridis', s=10)
49
+ ```
50
+ <p float="left" align="center">
51
+ <img width="500" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/trefoil_3d.png">
52
+ </p>
53
+
54
+ ### Red-Cyan Anaglyphs
55
+ Some 2D plots can also be made into anaglyphs, which are stereograms that can be viewed with red-cyan 3D glasses. While this allows for seeing the stereoscopic effect without training your eyes, it also means that the data cannot be otherwise colored.
56
+
57
+ The same warning as for the 2D stereo plots about shifting data applies here as well. If `eye_balance` is -1 or +1 such that the data for one of the colors is not shifted, then that color will be applied to the x-axis tick labels to show that they are accurate.
58
+ ```python
59
+ axstereo = AxesAnaglyph()
60
+ axstereo.plot(x, y, z)
61
+ axstereo.scatter(x, y, z, s=10)
62
+ ```
63
+ <p float="left" align="center">
64
+ <img width="250" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/trefoil_anaglyph.png">
65
+ </p>
66
+
67
+ ### Existing Stereo Images
68
+ Existing stereo image data can easily be plotted up side-by-side, or combined into an anaglyph. Here we plot up two 1-D grayscale images of the sun taken in July 2023 by the NASA STEREO-A and SDO spacecraft. This example was adapted from [the SunPy documentation](https://docs.sunpy.org/en/stable/generated/gallery/showcase/stereoscopic_3d.html#sphx-glr-generated-gallery-showcase-stereoscopic-3d-py).
69
+ ```python
70
+ from mpl_stereo.example_data import sun_left_right
71
+ sun_left_data, sun_right_data = sun_left_right
72
+
73
+ axstereo = AxesStereo2D()
74
+ axstereo.ax_left.imshow(sun_left_data, cmap='gray') # try other colormaps!
75
+ axstereo.ax_right.imshow(sun_right_data, cmap='gray')
76
+
77
+ axstereo = AxesAnaglyph()
78
+ axstereo.imshow_stereo(sun_left_data, sun_right_data, cmap='gray')
79
+ ```
80
+ <p float="left" align="center">
81
+ <img width="450" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/sun_2d.png">
82
+ </p>
83
+ <p float="left" align="center">
84
+ <img width="250" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/sun_anaglyph.png">
85
+ </p>
86
+
87
+ Here's another example, showing how this translates to full color data. This example is a pair of photos of [St. Mary's Church](https://commons.wikimedia.org/wiki/File:St_Mary%27s_Church,_Colston_Bassett_3D-35486887876.jpg) in Colston Basset, Britain, taken by David Skinner and shared under a [CC-BY2.0 license](https://creativecommons.org/licenses/by/2.0/deed.en).
88
+
89
+ Color anaglyph algorithms can be chosen from the methods `'dubois'`, `'photoshop'`, and `'photoshop2'`, as described in the paper [Sanders, William R., and David F. McAllister. "Producing anaglyphs from synthetic images." *Stereoscopic displays and virtual reality systems X.* Vol. 5006. SPIE, 2003.](https://research.csc.ncsu.edu/stereographics/ei03.pdf)
90
+
91
+ ```python
92
+ from mpl_stereo.example_data import church_left_right
93
+ church_left_data, church_right_data = church_left_right
94
+
95
+ # or
96
+ import matplotlib as mpl
97
+ church_left_data = mpl.image.imread('church_left.jpg')
98
+ church_right_data = mpl.image.imread('church_right.jpg')
99
+
100
+ axstereo = AxesStereo2D()
101
+ axstereo.ax_left.imshow(church_left_data)
102
+ axstereo.ax_right.imshow(church_right_data)
103
+
104
+ axstereo = AxesAnaglyph()
105
+ axstereo.imshow_stereo(church_left_data, church_right_data)
106
+ ```
107
+ <p float="left" align="center">
108
+ <img width="550" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/church_2d.png">
109
+ </p>
110
+ <p float="left" align="center">
111
+ <img width="300" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/church_anaglyph.png">
112
+ </p>
113
+
114
+ ### Wiggle Stereograms
115
+ As a final way to show off the stereoscopic effect, we can make a [wiggle stereogram](https://en.wikipedia.org/wiki/Wiggle_stereoscopy) or "wigglegram". This isn't as useful for examining data, but allows seeing the effect without having to train your eyes or using 3D glasses. The sense of depth may be enhanced if you close one eye.
116
+
117
+ ```python
118
+ axstereo = AxesStereo2D() # wiggle also works with AxesStereo3D
119
+ axstereo.ax_left.imshow(sun_left_data, cmap='gray')
120
+ axstereo.ax_right.imshow(sun_right_data, cmap='gray')
121
+ axstereo.wiggle('sun_wiggle.gif') # saves to file
122
+ ```
123
+
124
+ <p float="left" align="center">
125
+ <img width="250" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/sun_wiggle.gif">
126
+ </p>
127
+
128
+ ## Advanced Usage
129
+
130
+ ### Working With Plots
131
+ The figure and subplot axes can be accessed with the following:
132
+ ```python
133
+ axstereo.fig
134
+ axstereo.axs # (ax_left, ax_right), for AxesStereo2D and AxesStereo3D
135
+ axstereo.ax # for AxesAnaglyph
136
+ ```
137
+
138
+ Calling any method on `axstereo` will pass that method call onto all the subplot axes. In the 2D cases, the plotting methods which take in `x` and `y` arguments are intercepted and the additional `z` data is processed to obtain appropriate horizontal offsets.
139
+
140
+ ```python
141
+ # For example instead of:
142
+ for ax in axstereo.axs:
143
+ ax.set_xlabel('X Label')
144
+
145
+ # You can do the equivalent:
146
+ axstereo.set_xlabel('X Label')
147
+
148
+ # When applicable, the return values from the two method calls are returned as a tuple
149
+ (line_left, line_right) = axstereo.plot(x, y, z)
150
+ ```
151
+
152
+ ### Parallel vs Cross-Eyed Viewing
153
+ By default, the stereograms are set up for the "parallel" / "divergent" / "wall-eyed" viewing method. For "cross-eyed" viewing, initialize with a negative `ipd` parameter. An ipd (Inter-Pupilary Distance) of 65 millimeters is the default, so call `AxesStereo2D(ipd=-65)` for the default cross-eyed viewing.
154
+
155
+ ### Depth and Focal Plane Location
156
+ The apparent depth of 2D stereograms can be adjusted with the `zscale` parameter. The default is 1/4th the x-axis range, i.e. `zscale=(max(ax.get_xlim()) - ax.get_xlim()) / 4`. Decrease this number to flatten the stereogram, or increase it to exaggerate the depth.
157
+
158
+ For 2D plots and anaglyphs, the focal plane can be set with the `zzero` parameter. This is the z value that will be "on the page" when viewing the stereogram.
159
+
160
+ ```python
161
+ axstereo = AxesAnaglyph(zzero=min(z)) # data will float above the page
162
+ axstereo = AxesAnaglyph(zzero=None) # page will be at the midpoint of the data range (default)
163
+ axstereo = AxesAnaglyph(zzero=max(z)) # data will sink below the page
164
+ ```
165
+ <p float="left" align="center">
166
+ <img width="750" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/trefoil_anaglyph_zzero.png">
167
+ </p>
168
+
169
+ ### Animations
170
+ See an example of how to use this with matplotlib animations in [docs/gen_graphics.py](docs/gen_graphics.py).
171
+ <p float="left" align="center">
172
+ <img width="500" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/trefoil_2d_animation.gif">
173
+ </p>
174
+ <p float="left" align="center">
175
+ <img width="500" height="250" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/trefoil_3d_animation.gif">
176
+ </p>
177
+
178
+
179
+ ## Viewing Stereograms
180
+
181
+ These are not [*auto*stereograms](https://en.wikipedia.org/wiki/Autostereogram), like the "Magic Eye" books that were popular in the 1990's. However, they use the same viewing technique. Below is a great video by Vox on how to view stereograms, click on the image and skip to [0:35](https://www.youtube.com/watch?v=v8O8Em_RPNg&t=35s) for the how-to portion and then [4:33](https://www.youtube.com/watch?v=v8O8Em_RPNg&t=4m33s) for more examples.
182
+
183
+ <p float="left" align="center">
184
+ <a href="https://www.youtube.com/watch?v=v8O8Em_RPNg">
185
+ <img width="500" height="320" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/vox_stereogram_title_card.png">
186
+ </a>
187
+ </p>
188
+
189
+ There is also a great how-to guide at the stereoscopy blog here: [Learning to Free-View: See Stereoscopic Images with the Naked Eye](https://stereoscopy.blog/2022/03/11/learning-to-free-view-see-stereoscopic-images-with-the-naked-eye/)
190
+
191
+ ## Derivation of Geometry
192
+ Two eyes with separation `IPD` are looking at a point a distance `z` offset from a focal plane at distance `d`, resulting in view angle `θ`. If this point were projected back to the focal plane, it would be offset by `δ` from where it visually appears on that plane. This offset `δ` is used to displace each point in the 2D stereograms and anaglyphs for each eye based on its `z` value to achieve the stereoscopic effect. The `eye_balance` parameter allocates the total relative displacement of `2δ` between the two eyes.
193
+
194
+ For 3D stereograms, the azimuth view angle for the two plots is simply shifted by `θ`, with no modification to the data.
195
+
196
+ ```
197
+ θ = arctan((d - z) / (IPD / 2))
198
+ D / d = (IPD / 2) / (d - z)
199
+ δ = D - IPD / 2
200
+ = IPD / 2 * (d / (d - z) - 1)
201
+ = IPD / 2 * z / (d - z)
202
+ ```
203
+
204
+ <p float="left" align="center">
205
+ <img width="375" height="450" src="https://raw.githubusercontent.com/scottshambaugh/mpl_stereo/main/docs/geometry.png">
206
+ </p>
@@ -0,0 +1,29 @@
1
+ [tool.poetry]
2
+ name = "mpl_stereo"
3
+ version = "0.8.1"
4
+ description = "See your matplotlib plots in 3D by making stereograms and anaglyphs"
5
+ authors = ["Scott Shambaugh <scottshambaugh@users.noreply.github.com>"]
6
+ license = "MIT"
7
+ readme = "README.md"
8
+ classifiers = [
9
+ "Framework :: Matplotlib",
10
+ # Classifier for Python 3.12 can be removed after Poetry 1.8.0 has been released.
11
+ "Programming Language :: Python :: 3.12",
12
+ ]
13
+
14
+ [tool.poetry.dependencies]
15
+ python = "^3.9"
16
+ matplotlib = "^3.8.2"
17
+ numpy = "^1.26.2"
18
+
19
+ [tool.poetry.group.dev.dependencies]
20
+ pytest = "^7.4.3"
21
+ pytest-cov = "^4.0.0"
22
+ flake8 = "^6"
23
+ pre-commit = "^3"
24
+ mypy = "^1.7"
25
+ pillow = ">10.0.0"
26
+
27
+ [build-system]
28
+ requires = ["poetry-core"]
29
+ build-backend = "poetry.core.masonry.api"