Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scale and center visuals. #7

Open
wants to merge 7 commits into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.prerendered/
40 changes: 26 additions & 14 deletions MPLAnimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(self, name=None, setup_cb=None):
if name == None:
self.tmpdir = tempfile.TemporaryDirectory()
self.dir = self.tmpdir.name
self.name = 'animator_'+self.dir
self.name = 'animator_' + self.dir
else:
self.dir = '.prerendered/' + name
if not os.path.exists(self.dir):
Expand All @@ -47,20 +47,21 @@ def __init__(self, name=None, setup_cb=None):
if setup_cb:
setup_cb()


def initUI(self):
"""Initialize the UI."""

# main widget with vbox-layout for displaying the figure at the top and the controls below
self.w = QtWidgets.QWidget()
self.layout = QtWidgets.QVBoxLayout()
self.w.setLayout(self.layout)
self.w.resizeEvent = self._onResize

# using a stacked layout for the figure
# allows for quick exchange between pre-rendered image-view vs matplotlib figure
self.stack = QtWidgets.QStackedLayout()
self.layout.addLayout(self.stack)
self.label = QtWidgets.QLabel()
self.label.setAlignment(Qt.AlignCenter)
self.stack.addWidget(self.label)
self.fig = plt.figure()
self.canvas = FigureCanvas(self.fig)
Expand All @@ -76,7 +77,6 @@ def initUI(self):
self.prerender_checkbox = QtWidgets.QCheckBox('Pre-rendered')
self.layout.addWidget(self.prerender_checkbox)


def setFrameCallback(self, frame_cb, max_frame):
"""Set frame-callback relevant attributes.

Expand All @@ -90,7 +90,6 @@ def setFrameCallback(self, frame_cb, max_frame):
self.max_frame = max_frame
self.slider.setMaximum(max_frame - 1)


def setClickCallback(self, click_cb):
"""Set click-callback relevant attributes.

Expand All @@ -100,7 +99,6 @@ def setClickCallback(self, click_cb):
"""
self.click_cb = click_cb


def prerender(self):
"""Prerender the animation."""
if len(os.listdir(self.dir)) == 0:
Expand All @@ -110,13 +108,12 @@ def prerender(self):
self.frame_cb(i)
plt.savefig('{}/{}.png'.format(self.dir, i))


def handleCanvasClick(self, event: matplotlib.backend_bases.MouseEvent):
"""Unpack canvas click event to click callback function."""
self.click_cb(**(event.__dict__))
if hasattr(self, 'click') and self.click is not None:
self.click_cb(**(event.__dict__))
self.visualize()


def visualize(self, i=None):
"""Update visualization for set frame.

Expand All @@ -133,30 +130,30 @@ def visualize(self, i=None):
self.prerender()
if self.stack.currentWidget() != self.label:
self.stack.setCurrentWidget(self.label)
pm = QtGui.QPixmap('{}/{}.png'.format(self.dir, i))
self.label.setPixmap(pm)
self._renderFrameFromFile(i)
else:
if self.stack.currentWidget() != self.canvas:
self.stack.setCurrentWidget(self.canvas)
self.frame_cb(i)
self.canvas.draw()


def clear(self):
"""Clear pre-rendered images."""

for file in os.listdir(self.dir):
os.remove(self.dir + '/' + file)


def run(self, clear=False, prerendered=True, initialFrame=0):
def run(self, clear=False, prerendered=False, initialFrame=0):
"""Start the animator.

The function will block and also start PyQt in the background.

Args:
clear (bool): Whether to clear potentially existing pre-rendered images.
prerendered (bool): Whether to use pre-rendered images. If there are already images saved, these are used.
prerendered (bool): Whether to use pre-rendered images.
If there are already images saved, these are used.
Turned off by default because resizing the window before pre-
rendering will give better quality results.
initialFrame (int): Frame number to start the animation with.

"""
Expand All @@ -173,3 +170,18 @@ def run(self, clear=False, prerendered=True, initialFrame=0):
self.visualize(initialFrame)
self.slider.setValue(initialFrame)
self.qApp.exec()

def _renderFrameFromFile(self, i=None):
"""Render graphic from file given a frame number."""
if i == None:
i = self.slider.value()
pm = QtGui.QPixmap('{}/{}.png'.format(self.dir, i))
pm = pm.scaled(self.label.width(), self.label.height(),
Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.label.setPixmap(pm)
self.label.setMinimumSize(1, 1) # Allow downsizing the window.

def _onResize(self, event): # pylint: disable=unused-argument
"""Re-render prerendered graphics on window resize."""
if self.prerender_checkbox.isChecked():
self._renderFrameFromFile()
18 changes: 11 additions & 7 deletions example.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,35 @@
import matplotlib.pyplot as plt
from MPLAnimator import Animator


def naive_estimator(x, data, h):
n = sum(1 for d in data if x-h/2 < d <= x+h/2)
n = sum(1 for d in data if x - h / 2 < d <= x + h / 2)
N = len(data)
return n/(N*h)
return n / (N * h)


data = [0.5, 0.7, 0.8, 1.9, 2.4, 6.1, 6.2, 7.3]
xs = np.arange(0, 8, 0.01)
h = 2
hist = [naive_estimator(x, data, h) for x in xs]


def setup():
plt.gcf().set_size_inches(8,6)
plt.gcf()
plt.suptitle("Naive Estimator for h = {}".format(h))


def frame(i):
plt.cla()

# plot original data
plt.plot(xs, hist)
plt.plot(data, [0]*len(data), 'xk')
plt.plot(data, [0] * len(data), 'xk')
plt.axhline(0, color='k', linewidth=0.5)

# calculate current interval
x = i / 10
x1, x2 = x-h/2, x+h/2
x1, x2 = x - h / 2, x + h / 2

# calculate relative width for visualization
axis_to_data = plt.gca().transAxes + plt.gca().transData.inverted()
Expand All @@ -45,11 +49,11 @@ def frame(i):

# highlight data in interval
highlight_data = [d for d in data if x1 < d <= x2]
plt.plot(highlight_data, [0]*len(highlight_data), 'oC3')
plt.plot(highlight_data, [0] * len(highlight_data), 'oC3')

plt.xlim(-0.5, 8.5)


a = Animator(name='NaiveEstimator', setup_cb=setup)
a.setFrameCallback(frame_cb=frame, max_frame=80)
a.run(clear=False, prerendered=True)
a.run(clear=False, prerendered=False)