Skip to content

Commit

Permalink
Potential path forward for a pyglet live viewer.
Browse files Browse the repository at this point in the history
  • Loading branch information
ichristen committed Sep 22, 2022
1 parent 857a147 commit 381af3e
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 29 deletions.
1 change: 1 addition & 0 deletions docs/source/slmsuite-examples
Submodule slmsuite-examples added at 59fe6f
Binary file added docs/source/static/qp-slm-notext-128x128.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/static/qp-slm-notext-256x256.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 71 additions & 0 deletions slmsuite/hardware/cameras/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ def __init__(
Flips returned image up down.
Used to determine :attr:`transform`.
"""
self.name = "Unnamed Camera"

if rot in ("90", 1, "270", 3):
self.shape = (width, height)
self.default_shape = (width, height)
Expand Down Expand Up @@ -461,8 +463,77 @@ def view_continuous(cameras, cmap=None, facecolor=None, dpi=300):
fig.canvas.flush_events()


try:
import pyglet
import pyglet.gl as gl
import threading
import os
import ctypes
except ImportError:
# pyglet not installed. Fail gracefully.
pyglet = None

from slmsuite.misc.windowing import Window
from matplotlib import colors
import matplotlib.pyplot as plt

class CameraMonitor(Window):

def __init__(self, cam, cmap="jet"):
"""
"""
self.cam = cam
self.shape = cam.shape
self.name = cam.name

self.thread = threading.Thread(target=self.main)

self.set_cmap(cmap=cmap)

self._create_window()

# window = self.window

@self.window.event
def on_resize(width, height):
print('The window was resized to %dx%d' % (width, height))

@self.window.event
def on_key_press(symbol, modifiers):
print('A key was pressed')
print(symbol, modifiers)
if symbol == pyglet.window.key.ESCAPE:
return pyglet.event.EVENT_HANDLED

self.write(np.random.random(self.shape))

pyglet.clock.tick()

time.sleep(.1)

self.thread.start()

def main(self):
print("RUNNING!")
pyglet.app.run()

def set_cmap(self, cmap="jet"):
if isinstance(cmap, str):
cmap = plt.get_cmap(cmap, self.cam.bitresolution)

self.cmap = cmap

def write(self, data):
"""Writes to screen. See :class:`.SLM`."""

rgba = self.cmap(data, bytes=True)

print(rgba.shape)

# Write to buffer (self.buffer is the same as self.cbuffer).
np.copyto(self.buffer[:,:,0], rgba[:, :, 0])
np.copyto(self.buffer[:,:,1], rgba[:, :, 1])
np.copyto(self.buffer[:,:,2], rgba[:, :, 2])

self._flip()
59 changes: 30 additions & 29 deletions slmsuite/hardware/slms/screenmirrored.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ class ScreenMirrored(SLM):
~~~~~~~
This class currently supports SLMs with 8-bit precision or less.
In the future, this class will support 16-bit SLMs using RG color.
Important
~~~~~~~~~
Many SLM manufacturers provide an SDK for interfacing with their devices.
Using a python wrapper for these SDKs is recommended, instead of this class,
as there often is functionality additional to a mirrored screen
Using a python wrapper for these SDKs is recommended, instead of
or in supplement to this class,
as there often is functionality additional to a mirrored screen
(e.g. USB for changing settings) along with device-specific optimizations.
Note
Expand All @@ -53,12 +54,12 @@ class ScreenMirrored(SLM):
:mod:`slmpy` [12]_ uses :mod:`wx`.
:mod:`slmsuite` uses :mod:`pyglet` as the default display package.
:mod:`pyglet` is generally more capable than the mentioned SDL wrappers while immediately supporting
:mod:`pyglet` is generally more capable than the mentioned SDL wrappers while immediately supporting
features such as detecting connected displays which low-level packages like :mod:`OpenGL` and
:mod:`moderngl` do not have. :mod:`pyglet` allows us to interact more directly with the display
:mod:`moderngl` do not have. :mod:`pyglet` allows us to interact more directly with the display
hardware without the additional overhead that is found in GUI libraries.
Most importantly, :mod:`pyglet` is well documented.
However, it might be worthwhile in the future to look back into SDL options, as SDL surfaces
are closer to the pixels than OpenGL textures, so greater speed might be achievable (even without
loading data to the GPU as a texture). Another potential improvement could come from writing
Expand Down Expand Up @@ -89,7 +90,7 @@ class ScreenMirrored(SLM):
Used to load data to the texture.
texture : pyglet.gl.GLuint
Identifier for the texture loaded into ``OpenGL`` memory.
References
----------
Expand Down Expand Up @@ -120,9 +121,9 @@ def __init__(self, display_number, bitdepth=8, verbose=True, **kwargs):
Caution
~~~~~~~
An SLM designed at 1064 nm can be used for an application at 780 nm by passing
``wav_um=.780`` and ``wav_design_um=1.064``,
thus causing the SLM to use only a fraction (780/1064)
An SLM designed at 1064 nm can be used for an application at 780 nm by passing
``wav_um=.780`` and ``wav_design_um=1.064``,
thus causing the SLM to use only a fraction (780/1064)
of the full dynamic range. Be sure these values are correct.
Note that there are some performance losses from using this modality (see :meth:`.write()`).
Expand Down Expand Up @@ -166,34 +167,34 @@ def __init__(self, display_number, bitdepth=8, verbose=True, **kwargs):

if verbose and screen_info[display_number][2]:
print("warning: this is the main display... ")

if verbose:
print("success")
print("Creating window... ", end="")

screen = screens[display_number]

super().__init__(screen.width, screen.height, bitdepth=bitdepth, **kwargs)

# Setup the window. If failure, closes elegantly upon except().
try:
# Make the window and do basic setup.
self.window = pyglet.window.Window( screen=screen,
self.window = pyglet.window.Window( screen=screen,
fullscreen=True, vsync=True)
self.window.set_caption(self.name)
self.window.set_mouse_visible(False)

try:
# Icons. Currently hardcoded. Feel free to implement custom icons.
path, filename = os.path.split(os.path.realpath(__file__))
path, _ = os.path.split(os.path.realpath(__file__))
path = os.path.join(path, '..', '..', '..',
'docs', 'source', 'static', 'qp-slm-notext-')
img16x16 = pyglet.image.load(path + '16x16.png')
img32x32 = pyglet.image.load(path + '32x32.png')
self.window.set_icon(img16x16, img32x32)
except Exception as e:
print(e)

# Set the viewpoint.
proj = pyglet.window.Projection2D()
proj.set(self.shape[1], self.shape[0], self.shape[1], self.shape[0])
Expand Down Expand Up @@ -225,14 +226,14 @@ def __init__(self, display_number, bitdepth=8, verbose=True, **kwargs):
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST)

# Malloc the OpenGL memory
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8,
texture_shape[1], texture_shape[0],
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8,
texture_shape[1], texture_shape[0],
0, gl.GL_BGRA, gl.GL_UNSIGNED_BYTE,
texcbuffer)

# Make sure we can write to a subset of the memory (as we will do in the future)
gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, 0,
self.shape[1], self.shape[0],
gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, 0,
self.shape[1], self.shape[0],
gl.GL_BGRA, gl.GL_UNSIGNED_BYTE,
self.cbuffer)

Expand All @@ -251,7 +252,7 @@ def __init__(self, display_number, bitdepth=8, verbose=True, **kwargs):

if verbose:
print("success")

# Warn the user if wav_um > wav_design_um
if self.wav_norm > 1:
print(
Expand All @@ -277,7 +278,7 @@ def _write_hw(self, data):
ya = 0
xb = self.tex_shape_ratio[1]
yb = self.tex_shape_ratio[0]

array = (gl.GLfloat * 32)(
xa, ya, 0., 1., # tex coord,
x1, y1, 0., 1., # real coord, ...
Expand All @@ -291,11 +292,11 @@ def _write_hw(self, data):
# Update the texture.
gl.glEnable(gl.GL_TEXTURE_2D)
gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture.value)
gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, 0,
self.shape[1], self.shape[0],
gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, 0,
self.shape[1], self.shape[0],
gl.GL_RGBA, gl.GL_UNSIGNED_BYTE,
self.cbuffer)

# Blit the texture.
gl.glPushClientAttrib(gl.GL_CLIENT_VERTEX_ARRAY_BIT)
gl.glInterleavedArrays(gl.GL_T4F_V4F, 0, array)
Expand Down Expand Up @@ -342,13 +343,13 @@ def parse_window(window):
x, y = window.get_location()
return ("x={}, y={}, width={}, height={}"
.format(x, y, window.width, window.height))

default_str = parse_screen(default)

window_strs = []
for window in windows:
window_strs.append(parse_window(window))

if verbose:
print('Display Positions:')
print('#, Position')
Expand All @@ -367,11 +368,11 @@ def parse_window(window):
if screen_str in window_strs:
window_bool = True
screen_str += ' (has ScreenMirrored)'

if verbose:
print('{}, {}'.format(x, screen_str))

screen_list.append((x, parse_screen_int(screen),
screen_list.append((x, parse_screen_int(screen),
main_bool, window_bool))

return screen_list

0 comments on commit 381af3e

Please sign in to comment.