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

DM-22073: Add support for writing matplotlib figures #205

Merged
merged 1 commit into from
Nov 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ bin/*
.coverage
.pytest_cache/
.mypy_cache/
.vscode/
1 change: 1 addition & 0 deletions config/datastores/formatters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ Packages: lsst.daf.butler.formatters.pickleFormatter.PickleFormatter
PropertyList: lsst.daf.butler.formatters.pickleFormatter.PickleFormatter
PropertySet: lsst.daf.butler.formatters.pickleFormatter.PickleFormatter
NumpyArray: lsst.daf.butler.formatters.pickleFormatter.PickleFormatter
Plot: lsst.daf.butler.formatters.matplotlibFormatter.MatplotlibFormatter
2 changes: 2 additions & 0 deletions config/storageClasses.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,5 @@ storageClasses:
pytype: lsst.ip.isr.StrayLightData
Thumbnail:
pytype: numpy.ndarray
Plot:
pytype: matplotlib.figure.Figure
43 changes: 43 additions & 0 deletions python/lsst/daf/butler/formatters/matplotlibFormatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# This file is part of daf_butler.
#
# Developed for the LSST Data Management System.
# This product includes software developed by the LSST Project
# (http://www.lsst.org).
# See the COPYRIGHT file at the top-level directory of this distribution
# for details of code ownership.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Support for writing matplotlib figures."""

__all__ = ("MatplotlibFormatter",)

from lsst.daf.butler.formatters.fileFormatter import FileFormatter


class MatplotlibFormatter(FileFormatter):
"""Interface for writing matplotlib figures.
"""

extension = ".png"
"""Matplotlib figures are always written in PNG format."""

def _readFile(self, path, pytype=None):
# docstring inherited from FileFormatter._readFile
raise NotImplementedError(
f"matplotlib figures cannot be read by the butler; path is {path}")

def _writeFile(self, inMemoryDataset):
# docstring inherited from FileFormatter._writeFile
inMemoryDataset.savefig(self.fileDescriptor.location.path)
85 changes: 85 additions & 0 deletions tests/test_matplotlibFormatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# This file is part of daf_butler.
#
# Developed for the LSST Data Management System.
# This product includes software developed by the LSST Project
# (http://www.lsst.org).
# See the COPYRIGHT file at the top-level directory of this distribution
# for details of code ownership.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Tests for MatplotlibFormatter.
"""

import unittest
import tempfile
import os
import shutil

try:
import matplotlib
matplotlib.use("Agg") # noqa:E402
from matplotlib import pyplot
except ImportError:
pyplot = None

import numpy as np
from lsst.daf.butler import Butler, DatasetType
import filecmp
import urllib

TESTDIR = os.path.abspath(os.path.dirname(__file__))


@unittest.skipIf(pyplot is None,
"skipping test because matplotlib import failed")
class MatplotlibFormatterTestCase(unittest.TestCase):
"""Test for MatplotlibFormatter.
"""

def setUp(self):
self.root = tempfile.mkdtemp(dir=TESTDIR)
Butler.makeRepo(self.root)

def tearDown(self):
if os.path.exists(self.root):
shutil.rmtree(self.root, ignore_errors=True)

def testMatplotlibFormatter(self):
butler = Butler(self.root, run="testrun")
datasetType = DatasetType("test_plot", [], "Plot",
universe=butler.registry.dimensions)
butler.registry.registerDatasetType(datasetType)
pyplot.imshow(np.random.randn(3, 4))
ref = butler.put(pyplot.gcf(), datasetType)
parsed = urllib.parse.urlparse(butler.getUri(ref))
with tempfile.NamedTemporaryFile(suffix=".png") as file:
pyplot.gcf().savefig(file.name)
self.assertTrue(
filecmp.cmp(
parsed.path,
file.name,
shallow=True
)
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add calls to butler.datasetExists and butler.remove so that we can check that these PNG images are being treated like a normal dataset. Also try to do a butler.get and check that it fails with with self.assertRaises.

self.assertTrue(butler.datasetExists(ref))
with self.assertRaises(ValueError):
butler.get(ref)
butler.remove(ref)
with self.assertRaises(LookupError):
butler.datasetExists(ref)


if __name__ == "__main__":
unittest.main()