//  [python]
def visualize_test_results(
	predictions,
	test_classes,
	test_classes_orig,
	test_filenames,
	test_labels,
	test_labels_orig,
	title,
	output_dir):

	if output_dir is not None:
		os.makedirs(output_dir, exist_ok=True)

	set_is_multilabel_set = test_labels.ndim > 1

	if not set_is_multilabel_set:
		predictions_classes = [test_classes[prediction_class_index] for prediction_class_index in predictions.argmax(axis=-1)]
	else:
		predictions_classes = (predictions > 0.5).astype(np.uint8) # TODO: label translation

	classification_report = sklearn.metrics.classification_report(test_labels, predictions_classes, target_names=test_classes_orig, output_dict=True)

	classification_report['macro avg']['jaccard']     = sklearn.metrics.jaccard_score(test_labels, predictions_classes, average='macro')
	classification_report['weighted avg']['jaccard']  = sklearn.metrics.jaccard_score(test_labels, predictions_classes, average='weighted')
	classification_report['macro avg']['f0-score']    = sklearn.metrics.fbeta_score(test_labels, predictions_classes, beta=0, average='macro')
	classification_report['weighted avg']['f0-score'] = sklearn.metrics.fbeta_score(test_labels, predictions_classes, beta=0, average='weighted')
	classification_report['macro avg']['f2-score']    = sklearn.metrics.fbeta_score(test_labels, predictions_classes, beta=2, average='macro')
	classification_report['weighted avg']['f2-score'] = sklearn.metrics.fbeta_score(test_labels, predictions_classes, beta=2, average='weighted')

	if not set_is_multilabel_set:
		confusion_matrix                    = sklearn.metrics.confusion_matrix(test_labels, predictions_classes)
		confusion_matrix_normalized_columns = confusion_matrix / confusion_matrix.sum(axis=0)
		confusion_matrix_normalized_rows    = confusion_matrix / confusion_matrix.sum(axis=1, keepdims=True)
		confusion_matrix_normalized_global  = confusion_matrix / confusion_matrix.sum()


		predictions_classes_unique = {list(test_classes).index(prediction_class): test_classes_orig[list(test_classes).index(prediction_class)] for prediction_class in list(set(predictions_classes))}

		for prediction_class_index, prediction_class in predictions_classes_unique.items():
			TP = confusion_matrix[prediction_class_index][prediction_class_index] # true positive (TP)
			TN = confusion_matrix.trace() - TP                                    # true negative (TN)
			FP = confusion_matrix[:, prediction_class_index].sum() - TP           # false positive (FP)
			FN = confusion_matrix[prediction_class_index, :].sum() - TP           # false negative (FN)

			P = TP + FN # condition positive (P)
			N = TN + FP # condition negative (N)
			T = P  + N  # total

			sensitivity_recall_TPR      = TP / P            # sensitivity, recall, hit rate, or true positive rate (TPR)
			specificity_selectivity_TNR = TN / N            # specificity, selectivity, or true negative rate (TNR)
			precision_PPV               = TP / (TP + FP)    # precision or positive predictive value (PPV)
			NPV                         = TN / (TN + FN)    # negative predictive value (NPV)
			FNR                         = FN / P            # miss rate or false negative rate (FNR)
			FPR                         = FP / N            # fall-out or false positive rate (FPR)
			FDR                         = 1 - precision_PPV # false discovery rate (FDR)
			FOR                         = 1 - NPV           # false omission rate (FOR)

			prevalence        = P / T
			accuracy          = (TP + TN) / T
			balanced_accuracy = (sensitivity_recall_TPR + specificity_selectivity_TNR) / 2


			classification_report[prediction_class]['Hexnet_metric__TP']                          = TP
			classification_report[prediction_class]['Hexnet_metric__TN']                          = TN
			classification_report[prediction_class]['Hexnet_metric__FP']                          = FP
			classification_report[prediction_class]['Hexnet_metric__FN']                          = FN
			classification_report[prediction_class]['Hexnet_metric__P']                           = P
			classification_report[prediction_class]['Hexnet_metric__N']                           = N
			classification_report[prediction_class]['Hexnet_metric__T']                           = T
			classification_report[prediction_class]['Hexnet_metric__sensitivity_recall_TPR']      = sensitivity_recall_TPR
			classification_report[prediction_class]['Hexnet_metric__specificity_selectivity_TNR'] = specificity_selectivity_TNR
			classification_report[prediction_class]['Hexnet_metric__precision_PPV']               = precision_PPV
			classification_report[prediction_class]['Hexnet_metric__NPV']                         = NPV
			classification_report[prediction_class]['Hexnet_metric__FNR']                         = FNR
			classification_report[prediction_class]['Hexnet_metric__FPR']                         = FPR
			classification_report[prediction_class]['Hexnet_metric__FDR']                         = FDR
			classification_report[prediction_class]['Hexnet_metric__FOR']                         = FOR
			classification_report[prediction_class]['Hexnet_metric__prevalence']                  = prevalence
			classification_report[prediction_class]['Hexnet_metric__accuracy']                    = accuracy
			classification_report[prediction_class]['Hexnet_metric__balanced_accuracy']           = balanced_accuracy

		classification_report['Hexnet_metric__TP']                          = np.mean([classification_report[pc]['Hexnet_metric__TP']                          for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__TN']                          = np.mean([classification_report[pc]['Hexnet_metric__TN']                          for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__FP']                          = np.mean([classification_report[pc]['Hexnet_metric__FP']                          for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__FN']                          = np.mean([classification_report[pc]['Hexnet_metric__FN']                          for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__P']                           = np.mean([classification_report[pc]['Hexnet_metric__P']                           for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__N']                           = np.mean([classification_report[pc]['Hexnet_metric__N']                           for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__T']                           = np.mean([classification_report[pc]['Hexnet_metric__T']                           for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__sensitivity_recall_TPR']      = np.mean([classification_report[pc]['Hexnet_metric__sensitivity_recall_TPR']      for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__specificity_selectivity_TNR'] = np.mean([classification_report[pc]['Hexnet_metric__specificity_selectivity_TNR'] for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__precision_PPV']               = np.mean([classification_report[pc]['Hexnet_metric__precision_PPV']               for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__NPV']                         = np.mean([classification_report[pc]['Hexnet_metric__NPV']                         for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__FNR']                         = np.mean([classification_report[pc]['Hexnet_metric__FNR']                         for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__FPR']                         = np.mean([classification_report[pc]['Hexnet_metric__FPR']                         for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__FDR']                         = np.mean([classification_report[pc]['Hexnet_metric__FDR']                         for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__FOR']                         = np.mean([classification_report[pc]['Hexnet_metric__FOR']                         for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__prevalence']                  = np.mean([classification_report[pc]['Hexnet_metric__prevalence']                  for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__accuracy']                    = np.mean([classification_report[pc]['Hexnet_metric__accuracy']                    for pc in predictions_classes_unique.values()])
		classification_report['Hexnet_metric__balanced_accuracy']           = np.mean([classification_report[pc]['Hexnet_metric__balanced_accuracy']           for pc in predictions_classes_unique.values()])


		output_dir_confusion_matrix                    = os.path.join(output_dir, f'{title}_confusion_matrix')
		output_dir_confusion_matrix_normalized_columns = f'{output_dir_confusion_matrix}_normalized_columns'
		output_dir_confusion_matrix_normalized_rows    = f'{output_dir_confusion_matrix}_normalized_rows'
		output_dir_confusion_matrix_normalized_global  = f'{output_dir_confusion_matrix}_normalized_global'

	with open(os.path.join(output_dir, f'{title}_predictions.csv'), 'w') as predictions_file:
		print('label_orig,filename,label,prediction_class,prediction', file=predictions_file)

		for label_orig, filename, label, prediction_class, prediction in zip(test_labels_orig, test_filenames, test_labels, predictions_classes, predictions):
			prediction = [float(format(class_confidence, '.8f')) for class_confidence in prediction]
			print(f'{label_orig},{filename},{label},{prediction_class},{prediction}', file=predictions_file)

	with open(os.path.join(output_dir, f'{title}_classification_report.dat'), 'w') as classification_report_file:
		pprint(classification_report, stream=classification_report_file)

	if not set_is_multilabel_set:
		np.savetxt(f'{output_dir_confusion_matrix}.csv',                    confusion_matrix,                    fmt='%d',   delimiter=',')
		np.savetxt(f'{output_dir_confusion_matrix_normalized_columns}.csv', confusion_matrix_normalized_columns, fmt='%.8f', delimiter=',')
		np.savetxt(f'{output_dir_confusion_matrix_normalized_rows}.csv',    confusion_matrix_normalized_rows,    fmt='%.8f', delimiter=',')
		np.savetxt(f'{output_dir_confusion_matrix_normalized_global}.csv',  confusion_matrix_normalized_global,  fmt='%.8f', delimiter=',')

		confusion_matrix_dataframe                    = pd.DataFrame(confusion_matrix,                    test_classes_orig, test_classes_orig)
		confusion_matrix_normalized_columns_dataframe = pd.DataFrame(confusion_matrix_normalized_columns, test_classes_orig, test_classes_orig)
		confusion_matrix_normalized_rows_dataframe    = pd.DataFrame(confusion_matrix_normalized_rows,    test_classes_orig, test_classes_orig)
		confusion_matrix_normalized_global_dataframe  = pd.DataFrame(confusion_matrix_normalized_global,  test_classes_orig, test_classes_orig)

		ax = sn.heatmap(confusion_matrix_dataframe, cmap='viridis', annot=True, fmt='d')
		ax.set(title='model test confusion matrix', xlabel='predicted label', ylabel='true label')
		plt.savefig(f'{output_dir_confusion_matrix}.png')
		plt.savefig(f'{output_dir_confusion_matrix}.pdf')
		plt.clf()

		ax = sn.heatmap(confusion_matrix_normalized_columns_dataframe, cmap='viridis', annot=True, fmt='.2f')
		ax.set(title='model test column-wise normalized confusion matrix', xlabel='predicted label', ylabel='true label')
		plt.savefig(f'{output_dir_confusion_matrix_normalized_columns}.png')
		plt.savefig(f'{output_dir_confusion_matrix_normalized_columns}.pdf')
		plt.clf()

		ax = sn.heatmap(confusion_matrix_normalized_rows_dataframe, cmap='viridis', annot=True, fmt='.2f')
		ax.set(title='model test row-wise normalized confusion matrix', xlabel='predicted label', ylabel='true label')
		plt.savefig(f'{output_dir_confusion_matrix_normalized_rows}.png')
		plt.savefig(f'{output_dir_confusion_matrix_normalized_rows}.pdf')
		plt.clf()

		ax = sn.heatmap(confusion_matrix_normalized_global_dataframe, cmap='viridis', annot=True, fmt='.2f')
		ax.set(title='model test globally normalized confusion matrix', xlabel='predicted label', ylabel='true label')
		plt.savefig(f'{output_dir_confusion_matrix_normalized_global}.png')
		plt.savefig(f'{output_dir_confusion_matrix_normalized_global}.pdf')

	plt.close()

	return classification_report