diff --git a/README.rst b/README.rst index 790594a..c6c76c7 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,8 @@ resulting visualizations and use the editor tool `on this website `_. Downloads: - https://pypi.python.org/pypi/viscm/ + * https://pypi.python.org/pypi/viscm/ + * https://anaconda.org/conda-forge/viscm/ Code and bug tracker: https://github.com/matplotlib/viscm @@ -23,10 +24,10 @@ Contact: Nathaniel J. Smith and Stéfan van der Walt Dependencies: - * Python 2.6+, or 3.3+ + * Python 3.7+ * `colorspacious `_ * Matplotlib * NumPy License: - MIT, see LICENSE.txt for details. + MIT, see `LICENSE `__ for details. diff --git a/doc/contributing.md b/doc/contributing.md new file mode 100644 index 0000000..fc5825b --- /dev/null +++ b/doc/contributing.md @@ -0,0 +1,23 @@ +# Contributing + +Install development dependencies: + +``` +conda env create # or `mamba env create` +``` + + +## Development install + +``` +pip install -e . +``` + + +## Testing the build + +``` +rm -rf dist +python -m build +pip install dist/*.whl # or `dist/*.tar.gz` +``` diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..33c0a90 --- /dev/null +++ b/environment.yml @@ -0,0 +1,12 @@ +name: "viscm" +channels: + - "conda-forge" + - "nodefaults" +dependencies: + - "python ~=3.11" + - "numpy ~=1.24" + - "matplotlib ~=3.7" + - "colorspacious ~=1.1" + - "scipy ~=1.10" + - pip: + - "build ~=0.10" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1b53c90 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,48 @@ +[project] +name = "viscm" +dynamic = ["version"] +description = "A colormap tool" +readme = "README.rst" +authors = [ + {name = "Nathaniel J. Smith", email = "njs@pobox.com"}, + {name = "Stefan van der Walt", email = "stefanv@berkeley.edu"}, +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", +] + +requires-python = "~=3.7" +dependencies = [ + "numpy", + "matplotlib", + "colorspacious", + "scipy", +] + +[project.urls] +repository = "https://github.com/matplotlib/viscm" +# documentation = "https://viscm.readthedocs.io" + +[project.license] +text = "MIT" +files = ["LICENSE"] + +[project.scripts] +viscm = "viscm.cli:cli" + + +[build-system] +requires = ["setuptools", "setuptools_scm"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +zip-safe = false +packages = {find = {}} +package-data = {viscm = ["examples/*"]} + + +# [tool.black] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 5c6311d..0000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[bdist_wheel] -universal=1 - -[metadata] -license_file = LICENSE diff --git a/setup.py b/setup.py index 796b780..d5d43d7 100644 --- a/setup.py +++ b/setup.py @@ -1,32 +1,3 @@ -from setuptools import setup, find_packages -import sys -import os.path +from setuptools import setup -import numpy as np - -# Must be one line or PyPI will cut it off -DESC = ("A colormap tool") - -LONG_DESC = open("README.rst").read() - -setup( - name="viscm", - version="0.9", - description=DESC, - long_description=LONG_DESC, - author="Nathaniel J. Smith, Stefan van der Walt", - author_email="njs@pobox.com, stefanv@berkeley.edu", - url="https://github.com/bids/viscm", - license="MIT", - classifiers = - [ "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 3", - ], - packages=find_packages(), - install_requires=["numpy", "matplotlib", "colorspacious", "scipy"], - package_data={'viscm': ['examples/*']}, -) +setup(use_scm_version=True) diff --git a/viscm/__init__.py b/viscm/__init__.py index 2152218..f51aa85 100644 --- a/viscm/__init__.py +++ b/viscm/__init__.py @@ -1,4 +1,3 @@ -# This file is part of pycam02ucs # Copyright (C) 2014 Nathaniel Smith # See file LICENSE.txt for license information. diff --git a/viscm/__main__.py b/viscm/__main__.py index db0cdb5..f4b3e03 100644 --- a/viscm/__main__.py +++ b/viscm/__main__.py @@ -3,6 +3,5 @@ # Copyright (C) 2015 Stefan van der Walt # See file LICENSE.txt for license information. -import sys -from .gui import main -main(sys.argv[1:]) +from .cli import cli +cli() diff --git a/viscm/cli.py b/viscm/cli.py new file mode 100644 index 0000000..246ae46 --- /dev/null +++ b/viscm/cli.py @@ -0,0 +1,106 @@ +import sys + +import matplotlib.pyplot as plt + +from viscm import gui + + +def cli(): + import argparse + argv = sys.argv[1:] + + parser = argparse.ArgumentParser( + prog="python -m viscm", + description="A colormap tool.", + ) + parser.add_argument("action", metavar="ACTION", + help="'edit' or 'view' (or 'show', same as 'view')", + choices=["edit", "view", "show"], + default="edit", + nargs="?") + parser.add_argument("colormap", metavar="COLORMAP", + default=None, + help="A .json file saved from the editor, a .py file containing" + " a global named `test_cm`, or the name of a matplotlib" + " builtin colormap", + nargs="?") + parser.add_argument("--uniform-space", metavar="SPACE", + default="CAM02-UCS", + dest="uniform_space", + help="The perceptually uniform space to use. Usually " + "you should leave this alone. You can pass 'CIELab' " + "if you're curious how uniform some colormap is in " + "CIELab space. You can pass 'buggy-CAM02-UCS' if " + "you're trying to reproduce the matplotlib colormaps " + "(which turn out to have had a small bug in the " + "assumed sRGB viewing conditions) from their bezier " + "curves.") + parser.add_argument("-t", "--type", type=str, + default="linear", choices=["linear", "diverging", "diverging-continuous"], + help="Choose a colormap type. Supported options are 'linear', 'diverging', and 'diverging-continuous") + parser.add_argument("-m", "--method", type=str, + default="CatmulClark", choices=["Bezier", "CatmulClark"], + help="Choose a spline construction method. 'CatmulClark' is the default, but you may choose the legacy option 'Bezier'") + parser.add_argument("--save", metavar="FILE", + default=None, + help="Immediately save visualization to a file " + "(view-mode only).") + parser.add_argument("--quit", default=False, action="store_true", + help="Quit immediately after starting " + "(useful with --save).") + args = parser.parse_args(argv) + + cm = gui.Colormap(args.type, args.method, args.uniform_space) + app = gui.QtWidgets.QApplication([]) + + if args.colormap: + cm.load(args.colormap) + + + # Easter egg! I keep typing 'show' instead of 'view' so accept both + if args.action in ("view", "show"): + if cm is None: + sys.exit("Please specify a colormap") + fig = plt.figure() + figureCanvas = gui.FigureCanvas(fig) + v = gui.viscm(cm.cmap, name=cm.name, figure=fig, uniform_space=cm.uniform_space) + mainwindow = gui.ViewerWindow(figureCanvas, v, cm.name) + if args.save is not None: + v.figure.set_size_inches(20, 12) + v.figure.savefig(args.save) + elif args.action == "edit": + if not cm.can_edit: + sys.exit("Sorry, I don't know how to edit the specified colormap") + # Hold a reference so it doesn't get GC'ed + fig = plt.figure() + figureCanvas = gui.FigureCanvas(fig) + v = gui.viscm_editor(figure=fig, uniform_space=cm.uniform_space, cmtype=cm.cmtype, method=cm.method, **cm.params) + mainwindow = gui.EditorWindow(figureCanvas, v) + else: + raise RuntimeError("can't happen") + + if args.quit: + sys.exit() + + figureCanvas.setSizePolicy(gui.QtWidgets.QSizePolicy.Expanding, + gui.QtWidgets.QSizePolicy.Expanding) + figureCanvas.updateGeometry() + + mainwindow.resize(800, 600) + mainwindow.show() + + # PyQt messes up signal handling by default. Python signal handlers (e.g., + # the default handler for SIGINT that raises KeyboardInterrupt) can only + # run when we enter the Python interpreter, which doesn't happen while + # idling in the Qt mainloop. (Unless we register a timer to poll + # explicitly.) So here we unregister Python's default signal handler and + # replace it with... the *operating system's* default signal handler, so + # instead of a KeyboardInterrupt our process just exits. + import signal + signal.signal(signal.SIGINT, signal.SIG_DFL) + + app.exec_() + + +if __name__ == "__main__": + cli() diff --git a/viscm/gui.py b/viscm/gui.py index ecd3bc6..71e44ce 100644 --- a/viscm/gui.py +++ b/viscm/gui.py @@ -948,116 +948,13 @@ def load(self, path): self.name = path -def main(argv): - import argparse - - # Usage: - # python -m viscm - # python -m viscm edit - # python -m viscm edit - # (file.py must define some appropriate globals) - # python -m viscm view - # (file.py must define a global named "test_cm") - # python -m viscm view "matplotlib builtin colormap" - # python -m viscm view --save=foo.png ... - - parser = argparse.ArgumentParser( - prog="python -m viscm", - description="A colormap tool.", - ) - parser.add_argument("action", metavar="ACTION", - help="'edit' or 'view' (or 'show', same as 'view')", - choices=["edit", "view", "show"], - default="edit", - nargs="?") - parser.add_argument("colormap", metavar="COLORMAP", - default=None, - help="A .json file saved from the editor, or " - "the name of a matplotlib builtin colormap", - nargs="?") - parser.add_argument("--uniform-space", metavar="SPACE", - default="CAM02-UCS", - dest="uniform_space", - help="The perceptually uniform space to use. Usually " - "you should leave this alone. You can pass 'CIELab' " - "if you're curious how uniform some colormap is in " - "CIELab space. You can pass 'buggy-CAM02-UCS' if " - "you're trying to reproduce the matplotlib colormaps " - "(which turn out to have had a small bug in the " - "assumed sRGB viewing conditions) from their bezier " - "curves.") - parser.add_argument("-t", "--type", type=str, - default="linear", choices=["linear", "diverging", "diverging-continuous"], - help="Choose a colormap type. Supported options are 'linear', 'diverging', and 'diverging-continuous") - parser.add_argument("-m", "--method", type=str, - default="CatmulClark", choices=["Bezier", "CatmulClark"], - help="Choose a spline construction method. 'CatmulClark' is the default, but you may choose the legacy option 'Bezier'") - parser.add_argument("--save", metavar="FILE", - default=None, - help="Immediately save visualization to a file " - "(view-mode only).") - parser.add_argument("--quit", default=False, action="store_true", - help="Quit immediately after starting " - "(useful with --save).") - args = parser.parse_args(argv) - - cm = Colormap(args.type, args.method, args.uniform_space) - app = QtWidgets.QApplication([]) - - if args.colormap: - cm.load(args.colormap) - - - # Easter egg! I keep typing 'show' instead of 'view' so accept both - if args.action in ("view", "show"): - if cm is None: - sys.exit("Please specify a colormap") - fig = plt.figure() - figureCanvas = FigureCanvas(fig) - v = viscm(cm.cmap, name=cm.name, figure=fig, uniform_space=cm.uniform_space) - mainwindow = ViewerWindow(figureCanvas, v, cm.name) - if args.save is not None: - v.figure.set_size_inches(20, 12) - v.figure.savefig(args.save) - elif args.action == "edit": - if not cm.can_edit: - sys.exit("Sorry, I don't know how to edit the specified colormap") - # Hold a reference so it doesn't get GC'ed - fig = plt.figure() - figureCanvas = FigureCanvas(fig) - v = viscm_editor(figure=fig, uniform_space=cm.uniform_space, cmtype=cm.cmtype, method=cm.method, **cm.params) - mainwindow = EditorWindow(figureCanvas, v) - else: - raise RuntimeError("can't happen") - - if args.quit: - sys.exit() - - figureCanvas.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) - figureCanvas.updateGeometry() - - mainwindow.resize(800, 600) - mainwindow.show() - - # PyQt messes up signal handling by default. Python signal handlers (e.g., - # the default handler for SIGINT that raises KeyboardInterrupt) can only - # run when we enter the Python interpreter, which doesn't happen while - # idling in the Qt mainloop. (Unless we register a timer to poll - # explicitly.) So here we unregister Python's default signal handler and - # replace it with... the *operating system's* default signal handler, so - # instead of a KeyboardInterrupt our process just exits. - import signal - signal.signal(signal.SIGINT, signal.SIG_DFL) - - app.exec_() - def about(): QtWidgets.QMessageBox.about(None, "VISCM", "Copyright (C) 2015-2016 Nathaniel Smith\n" + "Copyright (C) 2015-2016 Stéfan van der Walt\n" "Copyright (C) 2016 Hankun Zhao") + class ViewerWindow(QtWidgets.QMainWindow): def __init__(self, figurecanvas, viscm, cmapname, parent=None): QtWidgets.QMainWindow.__init__(self, parent) @@ -1328,7 +1225,3 @@ def loadviewer(self): newwindow.resize(800, 600) newwindow.show() - - -if __name__ == "__main__": - main(sys.argv[1:])