text: import logging
  import math
  import warnings
  from typing import Any, Sequence, Union, List, Optional, Tuple, Dict
  
  import numpy as np
  import six
  
  try:
      import pandas as pd
  except ImportError:
      pd = None
  from PIL import Image
  from pathlib2 import Path
  
  from .backend_interface.logger import StdStreamPatch, LogFlusher
  from .backend_interface.task import Task as _Task
  from .backend_interface.task.development.worker import DevWorker
  from .backend_interface.task.log import TaskHandler
  from .backend_interface.util import mutually_exclusive
  from .config import running_remotely, get_cache_dir, config
  from .debugging.log import LoggerRoot
  from .errors import UsageError
  from .storage.helper import StorageHelper
  from .utilities.plotly_reporter import SeriesInfo
  
  # Make sure that DeprecationWarning within this package always gets printed
  warnings.filterwarnings('always', category=DeprecationWarning, module=__name__)
  
  
  class Logger(object):
      """
      The ``Logger`` class is the Trains console log and metric statistics interface, and contains methods for explicit
      reporting.
  
      Explicit reporting extends Trains automagical capturing of inputs and output. Explicit reporting
      methods include scalar plots, line plots, histograms, confusion matrices, 2D and 3D scatter
      diagrams, text logging, tables, and image uploading and reporting.
  
      In the **Trains Web-App (UI)**, ``Logger`` output appears in the **RESULTS** tab, **LOG**, **SCALARS**,
      **PLOTS**, and **DEBUG SAMPLES** sub-tabs. When you compare experiments, ``Logger`` output appears in the
      comparisons.
  
      .. warning::
  
         Do not construct Logger objects directly.
  
      You must get a Logger object before calling any of the other ``Logger`` class methods by calling
      :meth:`.Task.get_logger` or :meth:`Logger.current_logger`.
  
  
      """
      SeriesInfo = SeriesInfo
      _tensorboard_logging_auto_group_scalars = False
      _tensorboard_single_series_per_graph = config.get('metrics.tensorboard_single_series_per_graph', False)
  
      def __init__(self, private_task):
          """
          .. warning::
              **Do not construct Logger manually!**
              Please use :meth:`Logger.get_current`
          """
          assert isinstance(private_task, _Task), \
              'Logger object cannot be instantiated externally, use Logger.current_logger()'
          super(Logger, self).__init__()
          self._task = private_task
          self._default_upload_destination = None
          self._flusher = None
          self._report_worker = None
          self._task_handler = None
          self._graph_titles = {}
  
          StdStreamPatch.patch_std_streams(self)
  
      @classmethod
      def current_logger(cls):
          # type: () -> Logger
          """
          Get the Logger object for the main execution Task, the current running Task, if one exists. If no Logger object
          exists, this method creates one and returns it. Therefore, you can call this method from anywhere
          in the code.
  
          .. code-block:: py
  
             logger = Logger.current_logger()
  
          :return: The Logger object (a singleton) for the current running Task.
          """
          from .task import Task
          task = Task.current_task()
          if not task:
              return None
          return task.get_logger()
  
      def report_text(self, msg, level=logging.INFO, print_console=True, *args, **_):
          # type: (str, int, bool, Any, Any) -> None
          """
          For explicit reporting, print text to the log. Optionally, print a log level and print to the console.
  
          For example:
  
          .. code-block:: py
  
             logger.report_text('log some text', level=logging.DEBUG, print_console=False)
  
          You can view the reported text in the **Trains Web-App (UI)**, **RESULTS** tab, **LOG** sub-tab.
  
          :param str msg: The text to log.
          :param int level: The log level from the Python ``logging`` package. The default value is ``logging.INFO``.
          :param bool print_console: In addition to the log, print to the console?
  
              The values are:
  
              - ``True`` - Print to the console. (default)
              - ``False`` - Do not print to the console.
          """
          return self._console(msg, level, not print_console, *args, **_)
  
      def report_scalar(self, title, series, value, iteration):
          # type: (str, str, float, int) -> None
          """
          For explicit reporting, plot a scalar series.
  
          For example, plot a scalar series:
  
          .. code-block:: py
  
             scalar_series = [random.randint(0,10) for i in range(10)]
             logger.report_scalar(title='scalar metrics','series', value=scalar_series[iteration], iteration=0)
  
          You can view the scalar plots in the **Trains Web-App (UI)**, **RESULTS** tab, **SCALARS** sub-tab.
  
          :param str title: The title (metric) of the plot. Plot more than one scalar series on the same plot by using
              the same ``title`` for each call to this method.
          :param str series: The series name (variant) of the reported scalar.
          :param float value: The value to plot per iteration.
          :param int iteration: The iteration number. Iterations are on the x-axis.
          """
  
          # if task was not started, we have to start it
          self._start_task_if_needed()
          self._touch_title_series(title, series)
          return self._task.reporter.report_scalar(title=title, series=series, value=float(value), iter=iteration)
  
      def report_vector(
              self,
              title,  # type: str
              series,  # type: str
              values,  # type: Sequence[Union[int, float]]
              iteration,  # type: int
              labels=None,  # type: Optional[List[str]]
              xlabels=None,  # type: Optional[List[str]]
              xaxis=None,  # type: Optional[str]
              yaxis=None,  # type: Optional[str]
              mode=None,  # type: Optional[str]
              extra_layout=None,  # type: Optional[dict]
      ):
          """
          For explicit reporting, plot a vector as (default stacked) histogram.
  
          For example:
  
          .. code-block:: py
  
             vector_series = np.random.randint(10, size=10).reshape(2,5)
             logger.report_vector(title='vector example', series='vector series', values=vector_series, iteration=0,
                  labels=['A','B'], xaxis='X axis label', yaxis='Y axis label')
  
          You can view the vectors plots in the **Trains Web-App (UI)**, **RESULTS** tab, **PLOTS** sub-tab.
  
          :param str title: The title (metric) of the plot.
          :param str series: The series name (variant) of the reported histogram.
          :param list(float) values: The series values. A list of floats, or an N-dimensional Numpy array containing
              data for each histogram bar.
          :type values: list(float), numpy.ndarray
          :param int iteration: The iteration number. Each ``iteration`` creates another plot.
          :param list(str) labels: Labels for each bar group, creating a plot legend labeling each series. (Optional)
          :param list(str) xlabels: Labels per entry in each bucket in the histogram (vector), creating a set of labels
              for each histogram bar on the x-axis. (Optional)
          :param str xaxis: The x-axis title. (Optional)
          :param str yaxis: The y-axis title. (Optional)
          :param str mode: Multiple histograms mode, stack / group / relative. Default is 'group'.
          :param dict extra_layout: optional dictionary for layout configuration, passed directly to plotly
              example: extra_layout={'xaxis': {'type': 'date', 'range': ['2020-01-01', '2020-01-31']}}
          """
          self._touch_title_series(title, series)
          return self.report_histogram(title, series, values, iteration, labels=labels, xlabels=xlabels,
                                       xaxis=xaxis, yaxis=yaxis, mode=mode, extra_layout=extra_layout)
  
      def report_histogram(
              self,
              title,  # type: str
              series,  # type: str
              values,  # type: Sequence[Union[int, float]]
              iteration,  # type: int
              labels=None,  # type: Optional[List[str]]
              xlabels=None,  # type: Optional[List[str]]
              xaxis=None,  # type: Optional[str]
              yaxis=None,  # type: Optional[str]
              mode=None,  # type: Optional[str]
              extra_layout=None,  # type: Optional[dict]
      ):
          """
          For explicit reporting, plot a (default grouped) histogram.
          Notice this function will not calculate the histogram,
          it assumes the histogram was already calculated in `values`
  
          For example:
  
          .. code-block:: py
  
             vector_series = np.random.randint(10, size=10).reshape(2,5)
             logger.report_histogram(title='histogram example', series='histogram series',
                  values=vector_series, iteration=0, labels=['A','B'], xaxis='X axis label', yaxis='Y axis label')
  
          You can view the reported histograms in the **Trains Web-App (UI)**, **RESULTS** tab, **PLOTS** sub-tab.
  
          :param str title: The title (metric) of the plot.
          :param str series: The series name (variant) of the reported histogram.
          :param list(float) values: The series values. A list of floats, or an N-dimensional Numpy array containing
              data for each histogram bar.
          :type values: list(float), numpy.ndarray
          :param int iteration: The iteration number. Each ``iteration`` creates another plot.
          :param list(str) labels: Labels for each bar group, creating a plot legend labeling each series. (Optional)
          :param list(str) xlabels: Labels per entry in each bucket in the histogram (vector), creating a set of labels
              for each histogram bar on the x-axis. (Optional)
          :param str xaxis: The x-axis title. (Optional)
          :param str yaxis: The y-axis title. (Optional)
          :param str mode: Multiple histograms mode, stack / group / relative. Default is 'group'.
          :param dict extra_layout: optional dictionary for layout configuration, passed directly to plotly
              example: extra_layout={'xaxis': {'type': 'date', 'range': ['2020-01-01', '2020-01-31']}}
          """
  
          if not isinstance(values, np.ndarray):
              values = np.array(values)
  
          # if task was not started, we have to start it
          self._start_task_if_needed()
          self._touch_title_series(title, series)
          return self._task.reporter.report_histogram(
              title=title,
              series=series,
              histogram=values,
              iter=iteration,
              labels=labels,
              xlabels=xlabels,
              xtitle=xaxis,
              ytitle=yaxis,
              mode=mode or 'group',
              layout_config=extra_layout,
          )
  
      def report_table(
              self,
              title,  # type: str
              series,  # type: str
              iteration,  # type: int
              table_plot=None,  # type: Optional[pd.DataFrame]
              csv=None,  # type: Optional[str]
              url=None,  # type: Optional[str]
              extra_layout=None,  # type: Optional[dict]
      ):
          """
          For explicit report, report a table plot.
  
          One and only one of the following parameters must be provided.
  
          - ``table_plot`` - Pandas DataFrame
          - ``csv`` - CSV file
          - ``url`` - URL to CSV file
  
          For example:
  
          .. code-block:: py
  
             df = pd.DataFrame({'num_legs': [2, 4, 8, 0],
                     'num_wings': [2, 0, 0, 0],
                     'num_specimen_seen': [10, 2, 1, 8]},
                     index=['falcon', 'dog', 'spider', 'fish'])
  
             logger.report_table(title='table example',series='pandas DataFrame',iteration=0,table_plot=df)
  
          You can view the reported tables in the **Trains Web-App (UI)**, **RESULTS** tab, **PLOTS** sub-tab.
  
          :param str title: The title (metric) of the table.
          :param str series: The series name (variant) of the reported table.
          :param int iteration: The iteration number.
          :param table_plot: The output table plot object
          :type table_plot: pandas.DataFrame
          :param csv: path to local csv file
          :type csv: str
          :param url: A URL to the location of csv file.
          :type url: str
          :param extra_layout: optional dictionary for layout configuration, passed directly to plotly
              example: extra_layout={'xaxis': {'type': 'date', 'range': ['2020-01-01', '2020-01-31']}}
          :type extra_layout: dict
          """
          mutually_exclusive(
              UsageError, _check_none=True,
              table_plot=table_plot, csv=csv, url=url
          )
          table = table_plot
          if url or csv:
              if not pd:
                  raise UsageError(
                      "pandas is required in order to support reporting tables using CSV or a URL, "
                      "please install the pandas python package"
                  )
              if url:
                  table = pd.read_csv(url)
              elif csv:
                  table = pd.read_csv(csv)
  
          def replace(dst, *srcs):
              for src in srcs:
                  reporter_table.replace(src, dst, inplace=True)
  
          reporter_table = table.fillna(str(np.nan))
          replace("NaN", np.nan, math.nan)
          replace("Inf", np.inf, math.inf)
          replace("-Inf", -np.inf, np.NINF, -math.inf)
  
          return self._task.reporter.report_table(
              title=title,
              series=series,
              table=reporter_table,
              iteration=iteration,
              layout_config=extra_layout,
          )
  
      def report_line_plot(
              self,
              title,  # type: str
              series,  # type: Sequence[SeriesInfo]
              iteration,  # type: int
              xaxis,  # type: str
              yaxis,  # type: str
              mode='lines',  # type: str
              reverse_xaxis=False,  # type: bool
              comment=None,  # type: Optional[str]
              extra_layout=None,  # type: Optional[dict]
      ):
          """
          For explicit reporting, plot one or more series as lines.
  
          :param str title: The title (metric) of the plot.
          :param list series: All the series data, one list element for each line in the plot.
          :param int iteration: The iteration number.
          :param str xaxis: The x-axis title. (Optional)
          :param str yaxis: The y-axis title. (Optional)
          :param str mode: The type of line plot.
  
              The values are:
  
              - ``lines`` (default)
              - ``markers``
              - ``lines+markers``
  
          :param bool reverse_xaxis: Reverse the x-axis?
  
              The values are:
  
              - ``True`` - The x-axis is high to low  (reversed).
              - ``False`` - The x-axis is low to high  (not reversed). (default)
  
          :param str comment: A comment displayed with the plot, underneath the title.
          :param dict extra_layout: optional dictionary for layout configuration, passed directly to plotly
              example: extra_layout={'xaxis': {'type': 'date', 'range': ['2020-01-01', '2020-01-31']}}
          """
  
          series = [self.SeriesInfo(**s) if isinstance(s, dict) else s for s in series]
  
          # if task was not started, we have to start it
          self._start_task_if_needed()
          self._touch_title_series(title, series[0].name if series else '')
          return self._task.reporter.report_line_plot(
              title=title,
              series=series,
              iter=iteration,
              xtitle=xaxis,
              ytitle=yaxis,
              mode=mode,
              reverse_xaxis=reverse_xaxis,
              comment=comment,
              layout_config=extra_layout,
          )
  
      def report_scatter2d(
              self,
              title,  # type: str
              series,  # type: str
              scatter,  # type: Union[Sequence[Tuple[float, float]], np.ndarray]
              iteration,  # type: int
              xaxis=None,  # type: Optional[str]
              yaxis=None,  # type: Optional[str]
              labels=None,  # type: Optional[List[str]]
              mode='lines',  # type: str
              comment=None,  # type: Optional[str]
              extra_layout=None,  # type: Optional[dict]
      ):
          """
          For explicit reporting, report a 2d scatter plot.
  
          For example:
  
          .. code-block:: py
  
             scatter2d = np.hstack((np.atleast_2d(np.arange(0, 10)).T, np.random.randint(10, size=(10, 1))))
             logger.report_scatter2d(title="example_scatter", series="series", iteration=0, scatter=scatter2d,
                  xaxis="title x", yaxis="title y")
  
          Plot multiple 2D scatter series on the same plot by passing the same ``title`` and ``iteration`` values
          to this method:
  
          .. code-block:: py
  
             scatter2d_1 = np.hstack((np.atleast_2d(np.arange(0, 10)).T, np.random.randint(10, size=(10, 1))))
             logger.report_scatter2d(title="example_scatter", series="series_1", iteration=1, scatter=scatter2d_1,
                  xaxis="title x", yaxis="title y")
  
             scatter2d_2 = np.hstack((np.atleast_2d(np.arange(0, 10)).T, np.random.randint(10, size=(10, 1))))
             logger.report_scatter2d("example_scatter", "series_2", iteration=1, scatter=scatter2d_2,
                  xaxis="title x", yaxis="title y")
  
          :param str title: The title (metric) of the plot.
          :param str series: The series name (variant) of the reported scatter plot.
          :param list scatter: The scatter data. numpy.ndarray or list of (pairs of x,y) scatter:
          :param int iteration: The iteration number. To set an initial iteration, for example to continue a previously
          :param str xaxis: The x-axis title. (Optional)
          :param str yaxis: The y-axis title. (Optional)
          :param list(str) labels: Labels per point in the data assigned to the ``scatter`` parameter. The labels must be
              in the same order as the data.
          :param str mode: The type of scatter plot.
  
              The values are:
  
              - ``lines``
              - ``markers``
              - ``lines+markers``
  
          :param str comment: A comment displayed with the plot, underneath the title.
          :param dict extra_layout: optional dictionary for layout configuration, passed directly to plotly
              example: extra_layout={'xaxis': {'type': 'date', 'range': ['2020-01-01', '2020-01-31']}}
          """
  
          if not isinstance(scatter, np.ndarray):
              if not isinstance(scatter, list):
                  scatter = list(scatter)
              scatter = np.array(scatter)
  
          # if task was not started, we have to start it
          self._start_task_if_needed()
          self._touch_title_series(title, series)
          return self._task.reporter.report_2d_scatter(
              title=title,
              series=series,
              data=scatter.astype(np.float32),
              iter=iteration,
              mode=mode,
              xtitle=xaxis,
              ytitle=yaxis,
              labels=labels,
              comment=comment,
              layout_config=extra_layout,
          )
  
      def report_scatter3d(
              self,
              title,  # type: str
              series,  # type: str
              scatter,  # type: Union[Sequence[Tuple[float, float, float]], np.ndarray]
              iteration,  # type: int
              xaxis=None,  # type: Optional[str]
              yaxis=None,  # type: Optional[str]
              zaxis=None,  # type: Optional[str]
              labels=None,  # type: Optional[List[str]]
              mode='markers',  # type: str
              fill=False,  # type: bool
              comment=None,  # type: Optional[str]
              extra_layout=None,  # type: Optional[dict]
      ):
          """
          For explicit reporting, plot a 3d scatter graph (with markers).
  
          :param str title: The title (metric) of the plot.
          :param str series: The series name (variant) of the reported scatter plot.
          :param Union[numpy.ndarray, list] scatter: The scatter data.
              list of (pairs of x,y,z), list of series [[(x1,y1,z1)...]], or numpy.ndarray
          :param int iteration: The iteration number.
          :param str xaxis: The x-axis title. (Optional)
          :param str yaxis: The y-axis title. (Optional)
          :param str zaxis: The z-axis title. (Optional)
          :param list(str) labels: Labels per point in the data assigned to the ``scatter`` parameter. The labels must be
              in the same order as the data.
          :param str mode: The type of scatter plot.
  
              The values are:
  
              - ``lines``
              - ``markers``
              - ``lines+markers``
  
          For example:
  
          .. code-block:: py
  
             scatter3d = np.random.randint(10, size=(10, 3))
             logger.report_scatter3d(title="example_scatter_3d", series="series_xyz", iteration=1, scatter=scatter3d,
                  xaxis="title x", yaxis="title y", zaxis="title z")
  
          :param bool fill: Fill the area under the curve?
  
              The values are:
  
              - ``True`` - Fill
              - ``False`` - Do not fill (default)
  
          :param str comment: A comment displayed with the plot, underneath the title.
          :param dict extra_layout: optional dictionary for layout configuration, passed directly to plotly
              example: extra_layout={'xaxis': {'type': 'date', 'range': ['2020-01-01', '2020-01-31']}}
          """
          # check if multiple series
          multi_series = (
              isinstance(scatter, list)
              and (
                  isinstance(scatter[0], np.ndarray)
                  or (
                      scatter[0]
                      and isinstance(scatter[0], list)
                      and isinstance(scatter[0][0], list)
                  )
              )
          )
  
          if not multi_series:
              if not isinstance(scatter, np.ndarray):
                  if not isinstance(scatter, list):
                      scatter = list(scatter)
                  scatter = np.array(scatter)
              try:
                  scatter = scatter.astype(np.float32)
              except ValueError:
                  pass
  
          # if task was not started, we have to start it
          self._start_task_if_needed()
          self._touch_title_series(title, series)
          return self._task.reporter.report_3d_scatter(
              title=title,
              series=series,
              data=scatter,
              iter=iteration,
              labels=labels,
              mode=mode,
              fill=fill,
              comment=comment,
              xtitle=xaxis,
              ytitle=yaxis,
              ztitle=zaxis,
              layout_config=extra_layout,
          )
  
      def report_confusion_matrix(
              self,
              title,  # type: str
              series,  # type: str
              matrix,  # type: np.ndarray
              iteration,  # type: int
              xaxis=None,  # type: Optional[str]
              yaxis=None,  # type: Optional[str]
              xlabels=None,  # type: Optional[List[str]]
              ylabels=None,  # type: Optional[List[str]]
              comment=None,  # type: Optional[str]
              extra_layout=None,  # type: Optional[dict]
      ):
          """
          For explicit reporting, plot a heat-map matrix.
  
          For example:
  
          .. code-block:: py
  
             confusion = np.random.randint(10, size=(10, 10))
             logger.report_confusion_matrix("example confusion matrix", "ignored", iteration=1, matrix=confusion,
                  xaxis="title X", yaxis="title Y")
  
          :param str title: The title (metric) of the plot.
          :param str series: The series name (variant) of the reported confusion matrix.
          :param numpy.ndarray matrix: A heat-map matrix (example: confusion matrix)
          :param int iteration: The iteration number.
          :param str xaxis: The x-axis title. (Optional)
          :param str yaxis: The y-axis title. (Optional)
          :param list(str) xlabels: Labels for each column of the matrix. (Optional)
          :param list(str) ylabels: Labels for each row of the matrix. (Optional)
          :param str comment: A comment displayed with the plot, underneath the title.
          :param dict extra_layout: optional dictionary for layout configuration, passed directly to plotly
              example: extra_layout={'xaxis': {'type': 'date', 'range': ['2020-01-01', '2020-01-31']}}
          """
  
          if not isinstance(matrix, np.ndarray):
              matrix = np.array(matrix)
  
          # if task was not started, we have to start it
          self._start_task_if_needed()
          self._touch_title_series(title, series)
          return self._task.reporter.report_value_matrix(
              title=title,
              series=series,
              data=matrix.astype(np.float32),
              iter=iteration,
              xtitle=xaxis,
              ytitle=yaxis,
              xlabels=xlabels,
              ylabels=ylabels,
              comment=comment,
              layout_config=extra_layout,
          )
  
      def report_matrix(
              self,
              title,  # type: str
              series,  # type: str
              matrix,  # type: np.ndarray
              iteration,  # type: int
              xaxis=None,  # type: Optional[str]
              yaxis=None,  # type: Optional[str]
              xlabels=None,  # type: Optional[List[str]]
              ylabels=None,  # type: Optional[List[str]]
              extra_layout=None,  # type: Optional[dict]
      ):
          """
          For explicit reporting, plot a confusion matrix.
  
          .. note::
              This method is the same as :meth:`Logger.report_confusion_matrix`.
  
          :param str title: The title (metric) of the plot.
          :param str series: The series name (variant) of the reported confusion matrix.
          :param numpy.ndarray matrix: A heat-map matrix (example: confusion matrix)
          :param int iteration: The iteration number.
          :param str xaxis: The x-axis title. (Optional)
          :param str yaxis: The y-axis title. (Optional)
          :param list(str) xlabels: Labels for each column of the matrix. (Optional)
          :param list(str) ylabels: Labels for each row of the matrix. (Optional)
          :param dict extra_layout: optional dictionary for layout configuration, passed directly to plotly
              example: extra_layout={'xaxis': {'type': 'date', 'range': ['2020-01-01', '2020-01-31']}}
          """
          self._touch_title_series(title, series)
          return self.report_confusion_matrix(title, series, matrix, iteration,
                                              xaxis=xaxis, yaxis=yaxis, xlabels=xlabels, ylabels=ylabels,
                                              extra_layout=extra_layout)
  
      def report_surface(
              self,
              title,  # type: str
              series,  # type: str
              matrix,  # type: np.ndarray
              iteration,  # type: int
              xaxis=None,  # type: Optional[str]
              yaxis=None,  # type: Optional[str]
              zaxis=None,  # type: Optional[str]
              xlabels=None,  # type: Optional[List[str]]
              ylabels=None,  # type: Optional[List[str]]
              camera=None,  # type: Optional[Sequence[float]]
              comment=None,  # type: Optional[str]
              extra_layout=None,  # type: Optional[dict]
      ):
          """
          For explicit reporting, report a 3d surface plot.
  
          .. note::
             This method plots the same data as :meth:`Logger.report_confusion_matrix`, but presents the
             data as a surface diagram not a confusion matrix.
  
          .. code-block:: py
  
             surface_matrix = np.random.randint(10, size=(10, 10))
             logger.report_surface("example surface", "series", iteration=0, matrix=surface_matrix,
                  xaxis="title X", yaxis="title Y", zaxis="title Z")
  
          :param str title: The title (metric) of the plot.
          :param str series: The series name (variant) of the reported surface.
          :param numpy.ndarray matrix: A heat-map matrix (example: confusion matrix)
          :param int iteration: The iteration number.
          :param str xaxis: The x-axis title. (Optional)
          :param str yaxis: The y-axis title. (Optional)
          :param str zaxis: The z-axis title. (Optional)
          :param list(str) xlabels: Labels for each column of the matrix. (Optional)
          :param list(str) ylabels: Labels for each row of the matrix. (Optional)
          :param list(float) camera: X,Y,Z coordinates indicating the camera position. The default value is ``(1,1,1)``.
          :param str comment: A comment displayed with the plot, underneath the title.
          :param dict extra_layout: optional dictionary for layout configuration, passed directly to plotly
              example: extra_layout={'xaxis': {'type': 'date', 'range': ['2020-01-01', '2020-01-31']}}
          """
  
          if not isinstance(matrix, np.ndarray):
              matrix = np.array(matrix)
  
          # if task was not started, we have to start it
          self._start_task_if_needed()
          self._touch_title_series(title, series)
          return self._task.reporter.report_value_surface(
              title=title,
              series=series,
              data=matrix.astype(np.float32),
              iter=iteration,
              xlabels=xlabels,
              ylabels=ylabels,
              xtitle=xaxis,
              ytitle=yaxis,
              ztitle=zaxis,
              camera=camera,
              comment=comment,
              layout_config=extra_layout,
          )
  
      def report_image(
              self,
              title,  # type: str
              series,  # type: str
              iteration,  # type: int
              local_path=None,  # type: Optional[str]
              image=None,  # type: Optional[Union[np.ndarray, Image.Image]]
              matrix=None,  # type: Optional[np.ndarray]
              max_image_history=None,  # type: Optional[int]
              delete_after_upload=False,  # type: bool
              url=None  # type: Optional[str]
      ):
          """
          For explicit reporting, report an image and upload its contents.
  
          This method uploads the image to a preconfigured bucket (see :meth:`Logger.setup_upload`) with a key (filename)
          describing the task ID, title, series and iteration.
  
          For example:
  
          .. code-block:: py
  
             matrix = np.eye(256, 256, dtype=np.uint8)*255
             matrix = np.concatenate((np.atleast_3d(matrix), np.zeros((256, 256, 2), dtype=np.uint8)), axis=2)
             logger.report_image("test case", "image color red", iteration=1, image=m)
  
             image_open = Image.open(os.path.join("<image_path>", "<image_filename>"))
             logger.report_image("test case", "image PIL", iteration=1, image=image_open)
  
          One and only one of the following parameters must be provided.
  
          - ``local_path``
          - ``url``
          - ``image``
          - ``matrix``
  
          :param str title: The title (metric) of the image.
          :param str series: The series name (variant) of the reported image.
          :param int iteration: The iteration number.
          :param str local_path: A path to an image file.
          :param str url: A URL for the location of a pre-uploaded image.
          :param image: Image data (RGB).
          :type image: numpy.ndarray, PIL.Image.Image
          :param numpy.ndarray matrix: Image data (RGB).
  
              .. note::
                 The ``matrix`` paramater is deprecated. Use the ``image`` parameters.
          :type matrix: 3D numpy.ndarray
          :param int max_image_history: The maximum number of images to store per metric/variant combination.
              For an unlimited number, use a negative value. The default value is set in global configuration
              (default=``5``).
          :param bool delete_after_upload: After the upload, delete the local copy of the image?
  
              The values are:
  
              - ``True`` - Delete after upload.
              - ``False`` - Do not delete after upload. (default)
          """
          mutually_exclusive(
              UsageError, _check_none=True,
              local_path=local_path or None, url=url or None, image=image, matrix=matrix
          )
          if matrix is not None:
              warnings.warn("'matrix' variable is deprecated; use 'image' instead.", DeprecationWarning)
          if image is None:
              image = matrix
          if image is not None and not isinstance(image, (np.ndarray, Image.Image)):
              raise ValueError("Supported 'image' types are: numpy.ndarray or PIL.Image")
  
          # if task was not started, we have to start it
          self._start_task_if_needed()
  
          self._touch_title_series(title, series)
  
          if url:
              self._task.reporter.report_image(
                  title=title,
                  series=series,
                  src=url,
                  iter=iteration,
              )
  
          else:
              upload_uri = self.get_default_upload_destination()
              if not upload_uri:
                  upload_uri = Path(get_cache_dir()) / 'debug_images'
                  upload_uri.mkdir(parents=True, exist_ok=True)
                  # Verify that we can upload to this destination
                  upload_uri = str(upload_uri)
                  storage = StorageHelper.get(upload_uri)
                  upload_uri = storage.verify_upload(folder_uri=upload_uri)
  
              if isinstance(image, Image.Image):
                  image = np.array(image)
  
              self._task.reporter.report_image_and_upload(
                  title=title,
                  series=series,
                  path=local_path,
                  image=image,
                  iter=iteration,
                  upload_uri=upload_uri,
                  max_image_history=max_image_history,
                  delete_after_upload=delete_after_upload,
              )
  
      def report_media(
              self,
              title,  # type: str
              series,  # type: str
              iteration,  # type: int
              local_path=None,  # type: Optional[str]
              stream=None,  # type: Optional[six.BytesIO]
              file_extension=None,  # type: Optional[str]
              max_history=None,  # type: Optional[int]
              delete_after_upload=False,  # type: bool
              url=None  # type: Optional[str]
      ):
          """
          Report media upload its contents, including images, audio, and video.
  
          Media is uploaded to a preconfigured bucket (see setup_upload()) with a key (filename)
          describing the task ID, title, series and iteration.
  
          One and only one of the following parameters must be provided
  
          - ``local_path``
          - ``stream``
          - ``url``
  
          If you use ``stream`` for a BytesIO stream to upload, ``file_extension`` must be provided.
  
          :param str title: The title (metric) of the media.
          :param str series: The series name (variant) of the reported media.
          :param int iteration: The iteration number.
          :param str local_path: A path to an media file.
          :param stream: BytesIO stream to upload. If provided, ``file_extension`` must also be provided.
          :param str url: A URL to the location of a pre-uploaded media.
          :param file_extension: A file extension to use when ``stream`` is passed.
          :param int max_history: The maximum number of media files to store per metric/variant combination
              use negative value for unlimited. default is set in global configuration (default=5)
          :param bool delete_after_upload: After the file is uploaded, delete the local copyu?
  
              - ``True`` - Delete
              - ``False`` - Do not delete
  
          """
          mutually_exclusive(
              UsageError, _check_none=True,
              local_path=local_path or None, url=url or None, stream=stream,
          )
          if stream is not None and not file_extension:
              raise ValueError("No file extension provided for stream media upload")
  
          # if task was not started, we have to start it
          self._start_task_if_needed()
  
          self._touch_title_series(title, series)
  
          if url:
              self._task.reporter.report_media(
                  title=title,
                  series=series,
                  src=url,
                  iter=iteration,
              )
  
          else:
              upload_uri = self.get_default_upload_destination()
              if not upload_uri:
                  upload_uri = Path(get_cache_dir()) / 'debug_images'
                  upload_uri.mkdir(parents=True, exist_ok=True)
                  # Verify that we can upload to this destination
                  upload_uri = str(upload_uri)
                  storage = StorageHelper.get(upload_uri)
                  upload_uri = storage.verify_upload(folder_uri=upload_uri)
  
              self._task.reporter.report_media_and_upload(
                  title=title,
                  series=series,
                  path=local_path,
                  stream=stream,
                  iter=iteration,
                  upload_uri=upload_uri,
                  max_history=max_history,
                  delete_after_upload=delete_after_upload,
                  file_extension=file_extension,
              )
  
      def report_plotly(
              self,
              title,  # type: str
              series,  # type: str
              iteration,  # type: int
              figure,  # type: Union[Dict, "Figure"]
      ):
          """
          Report a ``Plotly`` figure (plot) directly
  
          ``Plotly`` figure can be a ``plotly.graph_objs._figure.Figure`` or a dictionary as defined by ``plotly.js``
  
          :param str title: The title (metric) of the plot.
          :param str series: The series name (variant) of the reported plot.
          :param int iteration: The iteration number.
          :param dict figure: A ``plotly`` Figure object or a ``poltly`` dictionary
          """
          # if task was not started, we have to start it
          self._start_task_if_needed()
  
          self._touch_title_series(title, series)
  
          plot = figure if isinstance(figure, dict) else figure.to_plotly_json()
          # noinspection PyBroadException
          try:
              plot['layout']['title'] = series
          except Exception:
              pass
          self._task.reporter.report_plot(
              title=title,
              series=series,
              plot=plot,
              iter=iteration,
          )
  
      def set_default_upload_destination(self, uri):
          # type: (str) -> None
          """
          Set the destination storage URI (for example, S3, Google Cloud Storage, a file path) for uploading debug images.
  
          The images are uploaded separately. A link to each image is reported.
  
          .. note::
             Credentials for the destination storage are specified in the  Trains configuration file,
             ``~/trains.conf``.
  
          :param str uri: example: 's3://bucket/directory/' or 'file:///tmp/debug/'
  
          :return: True, if the destination scheme is supported (for example, ``s3://``, ``file://``, or ``gc://``).
              False, if not supported.
  
          """
  
          # Create the storage helper
          storage = StorageHelper.get(uri)
  
          # Verify that we can upload to this destination
          uri = storage.verify_upload(folder_uri=uri)
  
          self._default_upload_destination = uri
  
      def get_default_upload_destination(self):
          # type: () -> str
          """
          Get the destination storage URI (for example, S3, Google Cloud Storage, a file path) for uploading debug images
          (see :meth:`Logger.set_default_upload_destination`).
  
          :return: The default upload destination URI.
  
              For example: ``s3://bucket/directory/``, or ``file:///tmp/debug/``.
          """
          return self._default_upload_destination or self._task._get_default_report_storage_uri()
  
      def flush(self):
          # type: () -> bool
          """
          Flush cached reports and console outputs to backend.
  
          :return: True, if successfully flushed the cache. False, if failed.
          """
          self._flush_stdout_handler()
          if self._task:
              return self._task.flush()
          return False
  
      def get_flush_period(self):
          # type: () -> Optional[float]
          """
          Get the Logger flush period.
  
          :return: The logger flush period in seconds.
          """
          if self._flusher:
              return self._flusher.period
          return None
  
      def set_flush_period(self, period):
          # type: (float) -> None
          """
          Set the logger flush period.
  
          :param float period: The period to flush the logger in seconds. To set no periodic flush,
              specify ``None`` or ``0``.
          """
          if self._task.is_main_task() and DevWorker.report_stdout and DevWorker.report_period and \
                  not running_remotely() and period is not None:
              period = min(period or DevWorker.report_period, DevWorker.report_period)
  
          if not period:
              if self._flusher:
                  self._flusher.exit()
                  self._flusher = None
          elif self._flusher:
              self._flusher.set_period(period)
          else:
              self._flusher = LogFlusher(self, period)
              self._flusher.start()
  
      def report_image_and_upload(
              self,
              title,  # type: str
              series,  # type: str
              iteration,  # type: int
              path=None,  # type: Optional[str]
              matrix=None,  # type: # type: Optional[Union[np.ndarray, Image.Image]]
              max_image_history=None,  # type: Optional[int]
              delete_after_upload=False  # type: bool
      ):
          """
          .. deprecated:: 0.13.0
              Use :meth:`Logger.report_image` instead
          """
          self.report_image(title=title, series=series, iteration=iteration, local_path=path, image=matrix,
                            max_image_history=max_image_history, delete_after_upload=delete_after_upload)
  
      @classmethod
      def tensorboard_auto_group_scalars(cls, group_scalars=False):
          # type: (bool) -> None
          """
          Group together TensorBoard scalars that do not have a title, or assign a title/series with the same tag.
  
          :param group_scalars: Group TensorBoard scalars without a title?
  
              The values are:
  
              - ``True`` - Scalars without specific titles are grouped together in the "Scalars" plot, preserving
                backward compatibility with Trains automagical behavior.
              - ``False`` - TensorBoard scalars without titles get a title/series with the same tag. (default)
          :type group_scalars: bool
          """
          cls._tensorboard_logging_auto_group_scalars = group_scalars
  
      @classmethod
      def tensorboard_single_series_per_graph(cls, single_series=False):
          # type: (bool) -> None
          """
          Group TensorBoard scalar series together or in separate plots.
  
          :param single_series: Group TensorBoard scalar series together?
  
              The values are:
  
              - ``True`` - Generate a separate plot for each TensorBoard scalar series.
              - ``False`` - Group the TensorBoard scalar series together in the same plot. (default)
  
          :type single_series: bool
          """
          cls._tensorboard_single_series_per_graph = single_series
  
      @classmethod
      def _remove_std_logger(cls):
          StdStreamPatch.remove_std_logger()
  
      def _console(self, msg, level=logging.INFO, omit_console=False, *args, **kwargs):
          # type: (str, int, bool, Any, Any) -> None
          """
          print text to log (same as print to console, and also prints to console)
  
          :param str msg: text to print to the console (always send to the backend and displayed in console)
          :param level: logging level, default: logging.INFO
          :type level: Logging Level
          :param bool omit_console: Omit the console output, and only send the ``msg`` value to the log?
  
              - ``True`` - Omit the console output.
              - ``False`` - Print the console output. (default)
  
          """
          try:
              level = int(level)
          except (TypeError, ValueError):
              self._task.log.log(level=logging.ERROR,
                                 msg='Logger failed casting log level "%s" to integer' % str(level))
              level = logging.INFO
  
          if not running_remotely():
              # noinspection PyBroadException
              try:
                  record = self._task.log.makeRecord(
                      "console", level=level, fn='', lno=0, func='', msg=msg, args=args, exc_info=None
                  )
                  # find the task handler that matches our task
                  if not self._task_handler:
                      self._task_handler = [h for h in LoggerRoot.get_base_logger().handlers
                                            if isinstance(h, TaskHandler) and h.task_id == self._task.id][0]
                  self._task_handler.emit(record)
              except Exception:
                  # avoid infinite loop, output directly to stderr
                  try:
                      # make sure we are writing to the original stdout
                      StdStreamPatch.stderr_original_write(
                          'trains.Logger failed sending log [level {}]: "{}"\n'.format(level, msg))
                  except Exception:
                      pass
  
          if not omit_console:
              # if we are here and we grabbed the stdout, we need to print the real thing
              if DevWorker.report_stdout and not running_remotely():
                  # noinspection PyBroadException
                  try:
                      # make sure we are writing to the original stdout
                      StdStreamPatch.stdout_original_write(str(msg) + '\n')
                  except Exception:
                      pass
              else:
                  print(str(msg))
  
          # if task was not started, we have to start it
          self._start_task_if_needed()
  
      def _report_image_plot_and_upload(
              self,
              title,  # type: str
              series,  # type: str
              iteration,  # type: int
              path=None,  # type: Optional[str]
              matrix=None,  # type: Optional[np.ndarray]
              max_image_history=None,  # type: Optional[int]
              delete_after_upload=False  # type: bool
      ):
          """
          Report an image, upload its contents, and present in plots section using plotly
  
          Image is uploaded to a preconfigured bucket (see :meth:`Logger.setup_upload`) with a key (filename)
          describing the task ID, title, series and iteration.
  
          :param title: Title (AKA metric)
          :type title: str
          :param series: Series (AKA variant)
          :type series: str
          :param iteration: Iteration number
          :type iteration: int
          :param path: A path to an image file. Required unless matrix is provided.
          :type path: str
          :param matrix: A 3D numpy.ndarray object containing image data (RGB). Required unless filename is provided.
          :type matrix: str
          :param max_image_history: maximum number of image to store per metric/variant combination \
          use negative value for unlimited. default is set in global configuration (default=5)
          :type max_image_history: int
          :param delete_after_upload: if True, one the file was uploaded the local copy will be deleted
          :type delete_after_upload: boolean
          """
  
          # if task was not started, we have to start it
          self._start_task_if_needed()
          upload_uri = self.get_default_upload_destination()
          if not upload_uri:
              upload_uri = Path(get_cache_dir()) / 'debug_images'
              upload_uri.mkdir(parents=True, exist_ok=True)
              # Verify that we can upload to this destination
              upload_uri = str(upload_uri)
              storage = StorageHelper.get(upload_uri)
              upload_uri = storage.verify_upload(folder_uri=upload_uri)
  
          self._task.reporter.report_image_plot_and_upload(
              title=title,
              series=series,
              path=path,
              matrix=matrix,
              iter=iteration,
              upload_uri=upload_uri,
              max_image_history=max_image_history,
              delete_after_upload=delete_after_upload,
          )
  
      def _report_file_and_upload(
              self,
              title,  # type: str
              series,  # type: str
              iteration,  # type: int
              path=None,  # type: Optional[str]
              max_file_history=None,  # type: Optional[int]
              delete_after_upload=False  # type: bool
      ):
          """
          Upload a file and report it as link in the debug images section.
  
          File is uploaded to a preconfigured storage (see :meth:`Logger.setup_upload`) with a key (filename)
          describing the task ID, title, series and iteration.
  
          :param title: Title (AKA metric)
          :type title: str
          :param series: Series (AKA variant)
          :type series: str
          :param iteration: Iteration number
          :type iteration: int
          :param path: A path to file to be uploaded
          :type path: str
          :param max_file_history: maximum number of files to store per metric/variant combination \
          use negative value for unlimited. default is set in global configuration (default=5)
          :type max_file_history: int
          :param delete_after_upload: if True, one the file was uploaded the local copy will be deleted
          :type delete_after_upload: boolean
          """
  
          # if task was not started, we have to start it
          self._start_task_if_needed()
          upload_uri = self.get_default_upload_destination()
          if not upload_uri:
              upload_uri = Path(get_cache_dir()) / 'debug_images'
              upload_uri.mkdir(parents=True, exist_ok=True)
              # Verify that we can upload to this destination
              upload_uri = str(upload_uri)
              storage = StorageHelper.get(upload_uri)
              upload_uri = storage.verify_upload(folder_uri=upload_uri)
  
          self._task.reporter.report_image_and_upload(
              title=title,
              series=series,
              path=path,
              image=None,
              iter=iteration,
              upload_uri=upload_uri,
              max_image_history=max_file_history,
              delete_after_upload=delete_after_upload,
          )
  
      def _start_task_if_needed(self):
          # deprecated
          pass
  
      def _flush_stdout_handler(self):
          if self._task_handler and DevWorker.report_stdout:
              self._task_handler.flush()
  
      def _close_stdout_handler(self, wait=True):
          # detach the sys stdout/stderr
          StdStreamPatch.remove_std_logger(self)
  
          if self._task_handler and DevWorker.report_stdout:
              t = self._task_handler
              self._task_handler = None
              t.close(wait)
  
      def _touch_title_series(self, title, series):
          # type: (str, str) -> None
          if title not in self._graph_titles:
              self._graph_titles[title] = set()
          self._graph_titles[title].add(series)
  
      def _get_used_title_series(self):
          return self._graph_titles
  
      @classmethod
      def _get_tensorboard_auto_group_scalars(cls):
          """
          :return: True, if we preserve Tensorboard backward compatibility behaviour,
              i.e., scalars without specific title will be under the "Scalars" graph
              default is False: Tensorboard scalars without title will have title/series with the same tag
          """
          return cls._tensorboard_logging_auto_group_scalars
  
      @classmethod
      def _get_tensorboard_single_series_per_graph(cls):
          """
          :return: True, if we generate a separate graph (plot) for each Tensorboard scalar series
              default is False: Tensorboard scalar series will be grouped according to their title
          """
          return cls._tensorboard_single_series_per_graph
  