import os
import numpy as np
import pandas as pd
import plotly.subplots
import plotly.graph_objects as go
import plotly.express as px
import sklearn.metrics
[docs]
def plot(
data,
x=None,
y=None,
output=None,
title=None,
mode='lines',
width=600,
height=800,
scale=1.0,
rows=None,
cols=None
) -> go.Figure:
"""Plot specified columns from a tab-separated log file.
Reads a tab-separated file and creates line plots using Plotly. Supports
multiple subplots where each subplot can contain one or more y columns.
Args:
data (str): Path to tab-separated log file.
x (str|None): Column name for x-axis. If None, defaults to 'epoch'. Default is None.
y (str|list): Column name(s) to plot on y-axis. Can be a single column name (str)
or a list of column names/nested lists for grouped subplots:
- 'loss': plots single column
- ['loss', 'acc']: plots loss and acc in separate subplots
- [['train_loss', 'valid_loss'], ['train_acc', 'valid_acc']]:
plots train_loss and valid_loss in subplot 1,
train_acc and valid_acc in subplot 2
output (str|None): File path to save the plot. If None, displays plot interactively.
title (str|None): Plot title. Default is None.
mode (str): Plotly trace mode ('lines', 'markers', 'lines+markers', etc.). Default is 'lines'.
width (int): Plot width in pixels. Default is 600.
height (int): Plot height in pixels. Default is 800.
scale (float): Scale factor for saved image resolution. Default is 1.0.
rows (int|None): Number of rows in subplot grid. If None, auto-calculated for near-square layout.
cols (int|None): Number of columns in subplot grid. If None, auto-calculated for near-square layout.
Returns:
plotly.graph_objects.Figure: The plotly figure object.
Raises:
TypeError: If y items are not str or list/tuple.
ValueError: If specified column names are not found in the data file.
Examples:
>>> from cvtk.viz import plot
>>> plot('train.log', y=['loss', 'acc'], output='plot.png')
>>> plot('train.log', x='step', y=[['train_loss', 'valid_loss'], ['train_acc', 'valid_acc']])
"""
log_data = pd.read_csv(data, sep='\t', header=0, comment='#')
# normalize y to nested list format
if isinstance(y, str):
y = [y]
y_labels = []
for item in y:
if isinstance(item, str):
y_labels.append([item])
elif isinstance(item, (list, tuple)):
y_labels.append(list(item))
else:
raise TypeError(f"y items must be str or list, got {type(item)}")
n_subplots = len(y_labels)
if rows is None and cols is None:
cols = int(np.ceil(np.sqrt(n_subplots)))
rows = int(np.ceil(n_subplots / cols))
elif rows is None:
rows = int(np.ceil(n_subplots / cols))
elif cols is None:
cols = int(np.ceil(n_subplots / rows))
fig = plotly.subplots.make_subplots(rows=rows, cols=cols)
colors = px.colors.qualitative.Plotly
color_idx = 0
for subplot_idx, y_cols in enumerate(y_labels):
row = (subplot_idx // cols) + 1
col = (subplot_idx % cols) + 1
for y_col in y_cols:
if y_col not in log_data.columns:
raise ValueError(f"Column '{y_col}' not found in log file. Available columns: {list(log_data.columns)}")
fig.add_trace(
go.Scatter(
x=log_data[x],
y=log_data[y_col],
mode=mode,
name=y_col,
line=dict(color=colors[color_idx % len(colors)])
),
row=row, col=col
)
color_idx += 1
fig.update_layout(title_text=title, template='ggplot2')
fig.update_xaxes(title_text=x)
if output is not None:
fig.write_image(output, width=width, height=height, scale=scale)
else:
fig.show()
return fig
[docs]
def plot_cm(
data,
output=None,
title='Confusion Matrix',
xlab='Predicted Label',
ylab='True Label',
colorscale='YlOrRd',
width=600,
height=600,
scale=1.0
) -> go.Figure:
"""Plot a confusion matrix from classification test outputs.
Plots a confusion matrix as a heatmap using Plotly. Also saves a text file
containing the confusion matrix values if output path is provided.
The input data should be a tab-separated file with columns:
- Column 1: image/sample path
- Column 2: true class label
- Columns 3+: predicted probabilities for each class
Example input format::
image label leaf flower root
1.JPG leaf 0.54791 0.20376 0.24833
2.JPG root 0.06158 0.02184 0.91658
3.JPG leaf 0.70320 0.04808 0.24872
4.JPG flower 0.04723 0.90061 0.05216
Args:
data (str): Path to tab-separated file containing test outputs.
output (str|None): File path to save the heatmap image. Also saves a `.txt` file
with the confusion matrix values. If None, displays plot interactively.
title (str): Plot title. Default is 'Confusion Matrix'.
xlab (str): X-axis label. Default is 'Predicted Label'.
ylab (str): Y-axis label. Default is 'True Label'.
colorscale (str): Plotly colorscale name (e.g., 'YlOrRd', 'Blues', 'Viridis').
Default is 'YlOrRd'.
width (int): Image width in pixels. Default is 600.
height (int): Image height in pixels. Default is 600.
scale (float): Scale factor for saved image resolution. Default is 1.0.
Returns:
plotly.graph_objects.Figure: The plotly figure object.
Examples:
>>> from cvtk.viz import plot_cm
>>> plot_cm('test_results.txt', output='confusion_matrix.png')
"""
# data preparation
test_outputs = pd.read_csv(data, sep='\t', header=0, comment='#')
class_labels = test_outputs.columns[2:]
y_true = test_outputs.iloc[:, 1].values.tolist()
y_pred = test_outputs.iloc[:, 2:].idxmax(axis=1).values.tolist()
# statistics calculation
cm = sklearn.metrics.confusion_matrix(y_true, y_pred, labels=test_outputs.columns[2:])
fig = go.Figure(data=go.Heatmap(x=class_labels, y=class_labels, z=cm,
colorscale=colorscale, hoverongaps=False))
fig.update_layout(title=title, xaxis_title=xlab, yaxis_title=ylab,
xaxis=dict(side='bottom'), yaxis=dict(side='left'))
fig.update_layout(template='ggplot2')
if output is not None:
fig.write_image(output, width=width, height=height, scale=scale)
cm = pd.DataFrame(cm, index=class_labels, columns=class_labels)
with open(os.path.splitext(output)[0] + '.txt', 'w') as oufh:
oufh.write('# Confusion Matrix\n')
oufh.write('#\tprediction\n')
cm.to_csv(oufh, sep='\t', header=True, index=True)
else:
fig.show()
return fig