From 0eb5ed9850a51d900016f26aedfbee7136b44895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=B6reth?= Date: Sun, 10 Sep 2017 17:54:29 +0200 Subject: [PATCH 1/7] Scale prerendered images to window size on draw. --- MPLAnimator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MPLAnimator.py b/MPLAnimator.py index 4decba1..35b1e44 100644 --- a/MPLAnimator.py +++ b/MPLAnimator.py @@ -134,7 +134,10 @@ def visualize(self, i=None): if self.stack.currentWidget() != self.label: self.stack.setCurrentWidget(self.label) 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. else: if self.stack.currentWidget() != self.canvas: self.stack.setCurrentWidget(self.canvas) From f50e62d6163d80f0958cd45ddf91095f7e968e1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=B6reth?= Date: Sun, 10 Sep 2017 19:00:58 +0200 Subject: [PATCH 2/7] Turn off prerendering by default and in the example. --- MPLAnimator.py | 8 +++++--- example.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/MPLAnimator.py b/MPLAnimator.py index 35b1e44..0881fdd 100644 --- a/MPLAnimator.py +++ b/MPLAnimator.py @@ -151,15 +151,17 @@ def clear(self): 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. """ diff --git a/example.py b/example.py index f0e060d..1121f59 100644 --- a/example.py +++ b/example.py @@ -15,7 +15,7 @@ def naive_estimator(x, data, h): 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): @@ -52,4 +52,4 @@ def frame(i): 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) From 8cac5e4e76a3d5437bc27248bc3072bf52308e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=B6reth?= Date: Sun, 10 Sep 2017 19:05:17 +0200 Subject: [PATCH 3/7] Center align figure in PyQt label. --- MPLAnimator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/MPLAnimator.py b/MPLAnimator.py index 0881fdd..e74cbb5 100644 --- a/MPLAnimator.py +++ b/MPLAnimator.py @@ -61,6 +61,7 @@ def initUI(self): 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) From 5f3fd041b979e3dffeefe30349834d8b484dc324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=B6reth?= Date: Sun, 10 Sep 2017 19:41:33 +0200 Subject: [PATCH 4/7] Re-render graphics on window resize. --- MPLAnimator.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/MPLAnimator.py b/MPLAnimator.py index e74cbb5..bb41e85 100644 --- a/MPLAnimator.py +++ b/MPLAnimator.py @@ -55,6 +55,7 @@ def initUI(self): 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 @@ -134,11 +135,7 @@ 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)) - 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. + self._renderFrameFromFile(i) else: if self.stack.currentWidget() != self.canvas: self.stack.setCurrentWidget(self.canvas) @@ -179,3 +176,18 @@ def run(self, clear=False, prerendered=False, 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() From 6be654c81372976fe38221dafbaabe169dfe3e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=B6reth?= Date: Sun, 10 Sep 2017 19:43:35 +0200 Subject: [PATCH 5/7] Apply some automatic (pep8) whitespace formating changes. --- MPLAnimator.py | 9 +-------- example.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/MPLAnimator.py b/MPLAnimator.py index bb41e85..498d7da 100644 --- a/MPLAnimator.py +++ b/MPLAnimator.py @@ -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): @@ -47,7 +47,6 @@ def __init__(self, name=None, setup_cb=None): if setup_cb: setup_cb() - def initUI(self): """Initialize the UI.""" @@ -78,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. @@ -92,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. @@ -102,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: @@ -112,13 +108,11 @@ 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__)) self.visualize() - def visualize(self, i=None): """Update visualization for set frame. @@ -142,7 +136,6 @@ def visualize(self, i=None): self.frame_cb(i) self.canvas.draw() - def clear(self): """Clear pre-rendered images.""" diff --git a/example.py b/example.py index 1121f59..63cf5e8 100644 --- a/example.py +++ b/example.py @@ -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() 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() @@ -45,7 +49,7 @@ 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) From e12cc2ba84f2de727ba7f0bfbc21beb227370460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=B6reth?= Date: Sun, 10 Sep 2017 21:41:57 +0200 Subject: [PATCH 6/7] Prevent error when no click handler is set. --- MPLAnimator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MPLAnimator.py b/MPLAnimator.py index 498d7da..4badf20 100644 --- a/MPLAnimator.py +++ b/MPLAnimator.py @@ -110,7 +110,8 @@ def prerender(self): 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): From 70b6d427275180523fcb2f9a5b718090bcb9e506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20H=C3=B6reth?= Date: Sun, 10 Sep 2017 22:27:42 +0200 Subject: [PATCH 7/7] Add a .gitignore for the project specific .prerendered folder. --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b12301 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.prerendered/