From 445995e8258f394ce6c4d4cd9053317a652d8f10 Mon Sep 17 00:00:00 2001 From: Szymon Kuklewicz Date: Thu, 16 Apr 2020 11:04:58 +0200 Subject: [PATCH 1/5] Added proposition of log_chart function that logs to neptune figures from matplotlib and plotly libraries --- neptunecontrib/log/__init__.py | 69 ++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 neptunecontrib/log/__init__.py diff --git a/neptunecontrib/log/__init__.py b/neptunecontrib/log/__init__.py new file mode 100644 index 0000000..f3244d0 --- /dev/null +++ b/neptunecontrib/log/__init__.py @@ -0,0 +1,69 @@ +# +# Copyright (c) 2020, Neptune Labs Sp. z o.o. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import neptune + + +def log_chart(name, chart): + if is_matplotlib_pyplot(chart) or is_matplotlib_figure(chart): + if is_matplotlib_pyplot(chart): + chart = chart.gcf() + + try: + from plotly import tools + chart = tools.mpl_to_plotly(chart) + + neptune.log_artifact(export_plotly_figure(chart), "charts/" + name + '.html') + except ImportError: + neptune.log_artifact(export_matplotlib_figure(chart), "charts/" + name + '.png') + + elif is_plotly_figure(chart): + neptune.log_artifact(export_plotly_figure(chart), "charts/" + name + '.html') + + else: + raise ValueError("Currently supported are matplotlib and plotly figures") + + +def is_matplotlib_pyplot(chart): + return hasattr(chart, '__name__') and chart.__name__.startswith('matplotlib.') + + +def is_matplotlib_figure(chart): + return chart.__class__.__module__.startswith('matplotlib.') and chart.__class__.__name__ == 'Figure' + + +def is_plotly_figure(chart): + return chart.__class__.__module__.startswith('plotly.') and chart.__class__.__name__ == 'Figure' + + +def export_plotly_figure(chart): + from io import StringIO + + buffer = StringIO() + chart.write_html(buffer) + buffer.seek(0) + + return buffer + + +def export_matplotlib_figure(chart): + from io import BytesIO + + buffer = BytesIO() + chart.savefig(buffer, format='png') + buffer.seek(0) + + return buffer From 010911756fedca105cfe8c1814c1d03128270262 Mon Sep 17 00:00:00 2001 From: Szymon Kuklewicz Date: Thu, 16 Apr 2020 14:27:46 +0200 Subject: [PATCH 2/5] Review fix + added docs --- docs/index.rst | 1 + docs/user_guide/logging/chart.rst | 6 ++ neptunecontrib/log/__init__.py | 69 ------------------- neptunecontrib/logging/__init__.py | 15 ++++ neptunecontrib/logging/chart.py | 107 +++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 69 deletions(-) create mode 100644 docs/user_guide/logging/chart.rst delete mode 100644 neptunecontrib/log/__init__.py create mode 100644 neptunecontrib/logging/__init__.py create mode 100644 neptunecontrib/logging/chart.py diff --git a/docs/index.rst b/docs/index.rst index 3038c1a..dc812d6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -54,6 +54,7 @@ And the best thing is you can extend it yourself or... tell us to do it for you api.utils hpo.utils bots.telegram_bot + logging.chart monitoring.lightgbm monitoring.xgboost monitoring.fastai diff --git a/docs/user_guide/logging/chart.rst b/docs/user_guide/logging/chart.rst new file mode 100644 index 0000000..0e2466b --- /dev/null +++ b/docs/user_guide/logging/chart.rst @@ -0,0 +1,6 @@ +Chart +=========== + +.. automodule:: neptunecontrib.logging.chart + :members: + :show-inheritance: diff --git a/neptunecontrib/log/__init__.py b/neptunecontrib/log/__init__.py deleted file mode 100644 index f3244d0..0000000 --- a/neptunecontrib/log/__init__.py +++ /dev/null @@ -1,69 +0,0 @@ -# -# Copyright (c) 2020, Neptune Labs Sp. z o.o. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import neptune - - -def log_chart(name, chart): - if is_matplotlib_pyplot(chart) or is_matplotlib_figure(chart): - if is_matplotlib_pyplot(chart): - chart = chart.gcf() - - try: - from plotly import tools - chart = tools.mpl_to_plotly(chart) - - neptune.log_artifact(export_plotly_figure(chart), "charts/" + name + '.html') - except ImportError: - neptune.log_artifact(export_matplotlib_figure(chart), "charts/" + name + '.png') - - elif is_plotly_figure(chart): - neptune.log_artifact(export_plotly_figure(chart), "charts/" + name + '.html') - - else: - raise ValueError("Currently supported are matplotlib and plotly figures") - - -def is_matplotlib_pyplot(chart): - return hasattr(chart, '__name__') and chart.__name__.startswith('matplotlib.') - - -def is_matplotlib_figure(chart): - return chart.__class__.__module__.startswith('matplotlib.') and chart.__class__.__name__ == 'Figure' - - -def is_plotly_figure(chart): - return chart.__class__.__module__.startswith('plotly.') and chart.__class__.__name__ == 'Figure' - - -def export_plotly_figure(chart): - from io import StringIO - - buffer = StringIO() - chart.write_html(buffer) - buffer.seek(0) - - return buffer - - -def export_matplotlib_figure(chart): - from io import BytesIO - - buffer = BytesIO() - chart.savefig(buffer, format='png') - buffer.seek(0) - - return buffer diff --git a/neptunecontrib/logging/__init__.py b/neptunecontrib/logging/__init__.py new file mode 100644 index 0000000..63b3072 --- /dev/null +++ b/neptunecontrib/logging/__init__.py @@ -0,0 +1,15 @@ +# +# Copyright (c) 2020, Neptune Labs Sp. z o.o. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/neptunecontrib/logging/chart.py b/neptunecontrib/logging/chart.py new file mode 100644 index 0000000..ac656f1 --- /dev/null +++ b/neptunecontrib/logging/chart.py @@ -0,0 +1,107 @@ +# +# Copyright (c) 2020, Neptune Labs Sp. z o.o. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import neptune + + +def log_chart(name, chart, experiment=None): + """Logs charts from matplotlib, plotly to neptune. + + Plotly figures are converted to interactive HTML and then uploaded to Neptune as an artifact with path + charts/{name}.html. + + Matplotlib figures are converted optionally. If plotly is installed, matplotlib figures are converted + to plotly figures and then converted to interactive HTML and uploaded to Neptune as an artifact with + path charts/{name}.html. If plotly is not installed, matplotlib figures are converted to PNG images + and uploaded to Neptune as an artifact with path charts/{name}.png + + Args: + name (str): name of the chart (without extension) that will be used as a part of artifact's destination. + chart (`matplotlib` or `plotly` Figure): Figure from matplotlib or plotly. + experiment(`neptune.experiments.Experiment`): Neptune experiment. Default is None. + + Examples: + Start an experiment:: + + import neptune + + neptune.init(project_qualified_name='USER_NAME/PROJECT_NAME') + neptune.create_experiment(name='experiment_with_chart') + + Create some figure:: + + import matplotlib.pyplot as plt + + plt.plot([1, 2, 3, 4]) + plt.ylabel('some numbers') + + Log the figure to Neptune:: + + from neptunecontrib.logging.chart import log_chart + + log_chart('matplotlib_chart', plt) + """ + _exp = experiment if experiment else neptune + + if is_matplotlib_pyplot(chart) or is_matplotlib_figure(chart): + if is_matplotlib_pyplot(chart): + chart = chart.gcf() + + try: + from plotly import tools + chart = tools.mpl_to_plotly(chart) + + _exp.log_artifact(export_plotly_figure(chart), "charts/" + name + '.html') + except ImportError: + _exp.log_artifact(export_matplotlib_figure(chart), "charts/" + name + '.png') + + elif is_plotly_figure(chart): + _exp.log_artifact(export_plotly_figure(chart), "charts/" + name + '.html') + + else: + raise ValueError("Currently supported are matplotlib and plotly figures") + + +def is_matplotlib_pyplot(chart): + return hasattr(chart, '__name__') and chart.__name__.startswith('matplotlib.') + + +def is_matplotlib_figure(chart): + return chart.__class__.__module__.startswith('matplotlib.') and chart.__class__.__name__ == 'Figure' + + +def is_plotly_figure(chart): + return chart.__class__.__module__.startswith('plotly.') and chart.__class__.__name__ == 'Figure' + + +def export_plotly_figure(chart): + from io import StringIO + + buffer = StringIO() + chart.write_html(buffer) + buffer.seek(0) + + return buffer + + +def export_matplotlib_figure(chart): + from io import BytesIO + + buffer = BytesIO() + chart.savefig(buffer, format='png') + buffer.seek(0) + + return buffer From 431858802406c17ed590bef5de45f52903b8c2e0 Mon Sep 17 00:00:00 2001 From: kamil-kaczmarek Date: Thu, 16 Apr 2020 15:46:11 +0200 Subject: [PATCH 3/5] minor updates, prepare for version 0.19.0 --- docs/conf.py | 4 ++-- docs/user_guide/logging/chart.rst | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 869242b..11e4744 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ author = 'Neptune Dev Team' # The short X.Y version -version = '0.18' +version = '0.19' # The full version, including alpha/beta/rc tags -release = '0.18.4' +release = '0.19.0' # -- General configuration --------------------------------------------------- diff --git a/docs/user_guide/logging/chart.rst b/docs/user_guide/logging/chart.rst index 0e2466b..36cda99 100644 --- a/docs/user_guide/logging/chart.rst +++ b/docs/user_guide/logging/chart.rst @@ -1,5 +1,5 @@ Chart -=========== +===== .. automodule:: neptunecontrib.logging.chart :members: diff --git a/setup.py b/setup.py index 9c9ef96..2b498be 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ def main(): setup( name='neptune-contrib', - version='0.18.4', + version='0.19.0', description='Neptune.ai contributions library', author='neptune.ai', support='contact@neptune.ai', From bf6ae3eb471a59f9a6a329b40e52386f43131556 Mon Sep 17 00:00:00 2001 From: Szymon Kuklewicz Date: Thu, 16 Apr 2020 16:45:29 +0200 Subject: [PATCH 4/5] Review fix + added docs --- neptunecontrib/logging/chart.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/neptunecontrib/logging/chart.py b/neptunecontrib/logging/chart.py index ac656f1..3d0f660 100644 --- a/neptunecontrib/logging/chart.py +++ b/neptunecontrib/logging/chart.py @@ -29,9 +29,16 @@ def log_chart(name, chart, experiment=None): and uploaded to Neptune as an artifact with path charts/{name}.png Args: - name (str): name of the chart (without extension) that will be used as a part of artifact's destination. - chart (`matplotlib` or `plotly` Figure): Figure from matplotlib or plotly. - experiment(`neptune.experiments.Experiment`): Neptune experiment. Default is None. + name (:obj:`str`): + | Name of the chart (without extension) that will be used as a part of artifact's destination. + chart (:obj:`matplotlib` or :obj:`plotly` Figure): + | Figure from `matplotlib` or `plotly`. If you want to use global figure from `matplotlib`, you + can also pass reference to `matplotlib.pyplot` module. + experiment (:obj:`neptune.experiments.Experiment`, optional, default is ``None``): + | For advanced users only. Pass Neptune + `Experiment `_ + object if you want to control to which experiment data is logged. + | If ``None``, log to currently active, and most recent experiment. Examples: Start an experiment:: From 7cab85097d54dd887b1e9a18a9231b228db8d98c Mon Sep 17 00:00:00 2001 From: Szymon Kuklewicz Date: Fri, 17 Apr 2020 09:04:22 +0200 Subject: [PATCH 5/5] Added requirement of minimal version of neptune-client to 0.4.110 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2b498be..b4bbd5d 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ def main(): all_deps += extras[group_name] extras['all'] = all_deps - base_libs = ['attrdict>=2.0.0', 'neptune-client', 'joblib>=0.13', 'pandas', 'matplotlib', 'Pillow>=6.2.0'] + base_libs = ['attrdict>=2.0.0', 'neptune-client>=0.4.110', 'joblib>=0.13', 'pandas', 'matplotlib', 'Pillow>=6.2.0'] setup( name='neptune-contrib',