Repository: kubeflow/pipelines

Issue:
[Sample] Create visualization samples
Visualization doc:
https://www.kubeflow.org/docs/components/pipelines/sdk/output-viewer/#available-output-viewers

Create pipeline examples with sample data in v1 format, so we can test them after the following issues are unblocked:

- https://github.com/kubeflow/pipelines/issues/5831
- https://github.com/kubeflow/pipelines/issues/5830

This is to prepare for validation of https://github.com/kubeflow/pipelines/issues/5666 changes.


List of visualization types is:

- [x] Confusion matrix
- [x] Markdown
- [x] ROC curve
- [x] Table
- [x] TensorBoard
- [x] Web app



Discussion:
Consider the possibility of adding yaml sample for visualization. 
https://github.com/kubeflow/pipelines/pull/5947#issuecomment-871704880

Resolving patch:
diff --git a/samples/core/visualization/table.py b/samples/core/visualization/table.py
new file mode 100644
--- /dev/null
+++ b/samples/core/visualization/table.py
@@ -0,0 +1,53 @@
+# Copyright 2021 The Kubeflow Authors
+#
+# 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 kfp.dsl as dsl
+from kfp.components import create_component_from_func
+
+# Advanced function
+# Demonstrates imports, helper functions and multiple outputs
+from typing import NamedTuple
+
+
+@create_component_from_func
+def table_visualization(train_file_path: str = 'https://raw.githubusercontent.com/zijianjoy/pipelines/5651f41071816594b2ed27c88367f5efb4c60b50/samples/core/visualization/table.csv') -> NamedTuple('VisualizationOutput', [('mlpipeline_ui_metadata', 'UI_metadata')]):
+  """Provide number to visualize as table metrics."""
+  import json
+    
+  header = ['Average precision ', 'Precision', 'Recall']
+  metadata = {
+      'outputs' : [{
+          'type': 'table',
+          'storage': 'gcs',
+          'format': 'csv',
+          'header': header,
+          'source': train_file_path
+          }]
+      }
+
+  from collections import namedtuple
+  visualization_output = namedtuple('VisualizationOutput', [
+    'mlpipeline_ui_metadata'])
+  return visualization_output(json.dumps(metadata))
+
+
+@dsl.pipeline(
+    name='table-pipeline',
+    description='A sample pipeline to generate scalar metrics for UI visualization.'
+)
+def table_pipeline():
+    table_visualization_task = table_visualization()
+    # You can also upload samples/core/visualization/table.csv to Google Cloud Storage.
+    # And call the component function with gcs path parameter like below:
+    # table_visualization_task = table_visualization('gs://<bucket-name>/<path>/table.csv')


Test patch:
diff --git a/samples/core/visualization/table_test.py b/samples/core/visualization/table_test.py
new file mode 100644
--- /dev/null
+++ b/samples/core/visualization/table_test.py
@@ -0,0 +1,52 @@
+# Copyright 2021 The Kubeflow Authors
+#
+# 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 kfp_server_api
+import unittest
+from pprint import pprint
+from .table import table_pipeline
+from ...test.util import KfpMlmdClient, run_pipeline_func, TestCase
+
+import kfp
+
+
+def verify(
+    run: kfp_server_api.ApiRun, mlmd_connection_config, argo_workflow_name: str,
+    **kwargs
+):
+    t = unittest.TestCase()
+    t.maxDiff = None  # we always want to see full diff
+    t.assertEqual(run.status, 'Succeeded')
+    client = KfpMlmdClient(mlmd_connection_config=mlmd_connection_config)
+    tasks = client.get_tasks(argo_workflow_name=argo_workflow_name)
+    pprint(tasks)
+
+    table_visualization = tasks['table-visualization']
+    output = [
+        a for a in table_visualization.outputs.artifacts if a.name == 'mlpipeline_ui_metadata'
+    ][0]
+    pprint(output)
+
+    t.assertEqual(table_visualization.get_dict()['outputs']['artifacts'][0]['name'],
+                  'mlpipeline_ui_metadata')
+
+
+run_pipeline_func([TestCase(pipeline_func=table_pipeline,
+                            verify_func=verify,
+                            mode=kfp.dsl.PipelineExecutionMode.V2_COMPATIBLE,
+                            arguments={
+                            }),
+                   TestCase(pipeline_func=table_pipeline,
+                            mode=kfp.dsl.PipelineExecutionMode.V1_LEGACY
+                            )])
diff --git a/samples/test/metrics_visualization_v1.py b/samples/test/metrics_visualization_v1.py
new file mode 100644
--- /dev/null
+++ b/samples/test/metrics_visualization_v1.py
@@ -0,0 +1,32 @@
+# Copyright 2021 The Kubeflow Authors
+#
+# 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 kfp.dsl as dsl
+from ..core.visualization.confusion_matrix import confusion_visualization
+from ..core.visualization.html import html_visualization
+from ..core.visualization.markdown import markdown_visualization
+from ..core.visualization.roc import roc_visualization
+from ..core.visualization.table import table_visualization
+
+# Note: This test is to verify that visualization metrics on V1 is runnable by KFP.
+# However, this pipeline is only runnable on V1 mode, but not V2 compatible mode.
+
+@dsl.pipeline(
+    name='metrics-visualization-v1-pipeline')
+def metrics_visualization_v1_pipeline():
+    confusion_visualization_task = confusion_visualization()
+    html_visualization_task = html_visualization("")
+    markdown_visualization_task = markdown_visualization()
+    roc_visualization_task = roc_visualization()
+    table_visualization_task = table_visualization()
diff --git a/samples/test/metrics_visualization_v1_test.py b/samples/test/metrics_visualization_v1_test.py
new file mode 100644
--- /dev/null
+++ b/samples/test/metrics_visualization_v1_test.py
@@ -0,0 +1,56 @@
+# Copyright 2021 The Kubeflow Authors
+#
+# 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 kfp_server_api
+import unittest
+from pprint import pprint
+from .metrics_visualization_v1 import metrics_visualization_v1_pipeline
+from .util import KfpMlmdClient, run_pipeline_func, TestCase
+
+import kfp
+import kfp_server_api
+
+
+def verify(
+    run: kfp_server_api.ApiRun, mlmd_connection_config, argo_workflow_name: str,
+    **kwargs
+):
+    t = unittest.TestCase()
+    t.maxDiff = None  # we always want to see full diff
+    t.assertEqual(run.status, 'Succeeded')
+    client = KfpMlmdClient(mlmd_connection_config=mlmd_connection_config)
+    tasks = client.get_tasks(argo_workflow_name=argo_workflow_name)
+    pprint(tasks)
+
+    table_visualization = tasks['table-visualization']
+    markdown_visualization = tasks['markdown-visualization']
+    roc_visualization = tasks['roc-visualization']
+    html_visualization = tasks['html-visualization']
+    confusion_visualization = tasks['confusion-visualization']
+
+    t.assertEqual(table_visualization.get_dict()['outputs']['artifacts'][0]['name'],
+                  'mlpipeline_ui_metadata')
+    t.assertEqual(markdown_visualization.get_dict()['outputs']['artifacts'][0]['name'],
+                  'mlpipeline_ui_metadata')
+    t.assertEqual(roc_visualization.get_dict()['outputs']['artifacts'][0]['name'],
+                  'mlpipeline_ui_metadata')
+    t.assertEqual(html_visualization.get_dict()['outputs']['artifacts'][0]['name'],
+                  'mlpipeline_ui_metadata')
+    t.assertEqual(confusion_visualization.get_dict()['outputs']['artifacts'][0]['name'],
+                  'mlpipeline_ui_metadata')
+
+
+run_pipeline_func([TestCase(pipeline_func=metrics_visualization_v1_pipeline,
+                            mode=kfp.dsl.PipelineExecutionMode.V2_COMPATIBLE
+                            )])