Skip to content
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,10 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#.idea/
dataset/agg_metablockse_jbhi.csv
dataset/arquivo_de_teste_pad-25.csv
dataset/agg_metablockse_jbhi_pad-25.csv
a_topsis.png
images/*
dataset/*
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ inside the folder :test_tube:
All methods are documented using the docstring format. However, to understand how the algorithms work, you must
refer to their paper linked in the [references section](#references).

# Running an automatic A-TOPSIS test

Now, you can export your benchmarck's results from `deep-hub-pipelines`

Add your aggregated results inside the folder 'dataset', change the configuration of the a-topsis in the script 'scripts/atopsis_from_file.py' and then run the command: `python3 scripts/atopsis_from_file.py`


## References
All implementations follow the standard approach described in the paper. However, for TODIM algorithm, we included the
Expand Down
79 changes: 79 additions & 0 deletions scripts/atopsis_from_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import os
import pandas as pd
import numpy as np
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src')))
from decision_making import ATOPSIS

def load_dataset(file_folder_path):
try:
dataset = pd.read_csv(file_folder_path, sep=",")
return dataset
except Exception as e:
print(f"Erro ao carregar os dados! Error:{e}\n")
return None


if __name__ == "__main__":
file_folder_path = "./dataset/agg_metablockse_jbhi_pad-25.csv"
dataset = load_dataset(file_folder_path)

if dataset is None:
sys.exit("Dataset could not be loaded. Exiting...")

# Group the dataset by the 'comb_method'
grouped_data = dataset.groupby('comb_method')

# Initialize an empty list to store the ordered groups
ordered_dataset = []

# Iterate through each group (i.e., each unique comb_method)
for comb_method, group in grouped_data:
ordered_dataset.append(group) # Append each group to the list

# Concatenate all the groups into a single ordered DataFrame
ordered_dataset = pd.concat(ordered_dataset)

# ordered_dataset = ordered_dataset[ordered_dataset["metric"]]
print(f"Dataset reordenado:\n{ordered_dataset}\n")

# Filtrar os dados do
# Get the unique 'alg_names' for the ordered dataset
alg_names = ordered_dataset['comb_method'].unique()

# Prepare the matrices for A-TOPSIS
avg_mat = np.array(ordered_dataset['AVG']).reshape(len(alg_names), -1)
std_mat = np.array(ordered_dataset['STD']).reshape(len(alg_names), -1)

# Weights for decision-making
weights = [0.5, 0.5]

try:
atop = ATOPSIS(avg_mat, std_mat, avg_cost_ben="benefit", std_cost_ben="cost", weights=weights, normalize=False)
atop.get_ranking(True)
# atop.plot_ranking(alg_names=alg_names)
except Exception as e:
print(f"An error occurred during A-TOPSIS processing: {e}")

# 2) Extrai scores do atributo final_ranking
scores = atop.final_ranking

# 3) Monta o DataFrame de resultado
result = pd.DataFrame({
"attention_mecanism": alg_names,
"atopsis_score": scores
})

# 4) Calcula o rank (maior score = melhor posição)
result["rank"] = (
result["atopsis_score"]
.rank(ascending=True, method="min")
.astype(int)
)

print("\n=== Ranking Final ===")
print(result.sort_values("rank"))

# 6) Plota com os próprios nomes
atop.plot_ranking(save_path="./images/a_topsis_PAD_20_extended.png", alg_names=alg_names, show=True, font_size=25, title="", y_axis_title="Scores", x_axis_title="", ascending=True, fig_size=(21, 14))

88 changes: 88 additions & 0 deletions scripts/gated-cross-attention-models-atopsis-test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import os
import sys
import pandas as pd
import numpy as np

# 1) Ajuste do path para importar o ATOPSIS
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src')))
from decision_making import ATOPSIS


def main(file_path):
# 2) Carrega o CSV e limpa nomes de coluna
df = pd.read_csv(file_path, sep=",")
df.columns = df.columns.str.strip()
print("Column names:", df.columns.tolist())

# 3) Métricas consideradas
metrics = ["accuracy", "balanced_accuracy", "auc"]


# wanted_model_name = "densenet169"

# Obter as informações referentes ao modelo desejado
# df=df[df['model_name'] == wanted_model_name]
# df = df[df['attention_mecanism']==["no-metadata", "att-intramodal+residual+cross-attention-metadados"]]
print(df)
# 4) Extrai média e desvio padrão
for m in metrics:
df[[f"{m}_mean", f"{m}_std"]] = (
df[m]
.str.extract(r"([\d\.]+)\s*±\s*([\d\.]+)")
.astype(float)
)

# 5) Agrupa por attention_mecanism e computa média de cada grupo
grouped = df.groupby("attention_mecanism").agg(
**{f"{m}_mean": (f"{m}_mean", "mean") for m in metrics},
**{f"{m}_std": (f"{m}_std", "mean") for m in metrics},
).reset_index()

# 6) Prepara as matrizes em memória
avg_mat = grouped[[f"{m}_mean" for m in metrics]].values.tolist()
std_mat = grouped[[f"{m}_std" for m in metrics]].values.tolist()
alg_names = grouped["attention_mecanism"].tolist()
# alg_names = grouped["model_name"].tolist()
# 7) Executa A‑TOPSIS (listas de listas)
# avg_cost_ben="benefit"
# std_cost_ben="cost"
weights_presetted = [0.7, 0.3]

# Executa A‑TOPSIS em memória (listas de listas)
atop = ATOPSIS(
avg_mat,
std_mat,
avg_cost_ben="benefit",
std_cost_ben="cost",
weights=weights_presetted,
normalize=True
)
# 1) Gera o ranking (printa se verbose=True)
atop.get_ranking(verbose=True)

# 2) Extrai scores do atributo final_ranking
scores = atop.final_ranking

# 3) Monta o DataFrame de resultado
result = pd.DataFrame({
"attention_mecanism": alg_names,
"atopsis_score": scores
})

# 4) Calcula o rank (maior score = melhor posição)
result["rank"] = (
result["atopsis_score"]
.rank(ascending=False, method="min")
.astype(int)
)

print("\n=== Ranking Final ===")
print(result.sort_values("rank"))

# 6) Plota com os próprios nomes
atop.plot_ranking(save_path="./images/a_topsis_PAD_UFES_20.png", alg_names=alg_names, show=True, font_size=16, title="", y_axis_title="Scores", x_axis_title="Methods", ascending=True)

if __name__=="__main__":
file_path = "dataset/agg_residual-block-pad-ufes-20.csv"
# Função principal
main(file_path=file_path)
4 changes: 2 additions & 2 deletions src/decision_making/a_topsis.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def get_ranking(self, verbose=True):
self.final_topsis.get_closeness_coefficient(verbose)
self.final_ranking = self.final_topsis.clos_coefficient

def plot_ranking(self, alg_names=None, save_path=None, show=True):
def plot_ranking(self, alg_names=None, save_path=None, show=True, font_size=16, title="A-TOPSIS test", y_axis_title="Scores", x_axis_title="Methods", ascending=False, fig_size=(6,4)):
"""
This method plots the ranking, according to the final_ranking, in a bar plot.

Expand All @@ -125,7 +125,7 @@ def plot_ranking(self, alg_names=None, save_path=None, show=True):
"""
if alg_names is None:
alg_names = self.final_topsis.alternatives
self.final_topsis.plot_ranking(alg_names, save_path, show)
self.final_topsis.plot_ranking(alg_names, save_path, show, font_size, title, y_axis_title, x_axis_title, ascending=ascending, fig_size=fig_size)



Expand Down
87 changes: 59 additions & 28 deletions src/decision_making/topsis.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,43 +214,74 @@ def get_closeness_coefficient(self, verbose=False, compute_distance_ideal=True):
if verbose:
print (self.clos_coefficient)

def plot_ranking(self, alt_names=None, save_path=None, show=True):
def plot_ranking(self, alt_names=None, save_path=None, show=True, font_size=16,
title="A-TOPSIS Test", y_axis_title="Scores", x_axis_title="Methods",
ascending=True, fig_size=(6, 4)):
"""
This method plots the ranking, according to the closeness coefficient, in a bar plot.
Plots a bar chart representing the ranking based on the closeness coefficient.

Parameters:
-----------
alt_names: (list), optional
This is a list of names for each alternative within the decision matrix. If you're using a DataFrame, you have
already defined when you set the alt_col_name. So, you may let it as None. However, if you're using a matrix
list or a numpy array, you may pass the alternatives name here. If you let it as None, there will be a default
alternatives name in the plot (ex: A1, A2, etc). Default is None

save_path: (str), optional
It's the full path (including the figure name and extension) to save the plot. If you let it None, the plot
won't be saved. Default is None.

show: (boolean), optional
Set is as True if you want to show the plot on the screen. Default is False.
Parameters
----------
alt_names : list, optional
Names for each alternative. If None and using a DataFrame, the names from `self.alternatives` are used.
Otherwise, defaults to ["A1", "A2", ...].

save_path : str, optional
Full path (including filename and extension) to save the plot. If None, the plot is not saved.

show : bool, optional
If True, displays the plot.

font_size : int, optional
Base font size for labels and ticks.

title : str, optional
Title of the plot.

y_axis_title : str, optional
Label for the Y-axis.

x_axis_title : str, optional
Label for the X-axis.

ascending : bool, optional
If True, sorts the alternatives by score in ascending order. Default is False (descending).
"""

sns.set_style("whitegrid")
if self.alternatives is not None:
alt_names = self.alternatives
if alt_names is not None:
a = sns.barplot(alt_names, self.clos_coefficient, palette="BuGn_d")
else:
temp = [f"A{n}" for n in range(1, len(self.clos_coefficient)+1, 1)]
a = sns.barplot(temp, self.clos_coefficient, palette="BuGn_d")
a.set_ylabel("Closeness Coefficient")
a.set_xlabel('Alternatives')
fig = a.get_figure()

# Determine alternative names
if alt_names is None:
if hasattr(self, 'alternatives') and self.alternatives is not None:
alt_names = self.alternatives
else:
alt_names = [f"A{i+1}" for i in range(len(self.clos_coefficient))]

# Sort based on closeness coefficient
sorted_data = sorted(zip(alt_names, self.clos_coefficient), key=lambda x: x[1], reverse=not ascending)
sorted_names, sorted_scores = zip(*sorted_data)

# Criar figura com tamanho específico
fig, ax = plt.subplots(figsize=fig_size)

# Plot
sns.barplot(x=list(sorted_names), y=list(sorted_scores), palette="Blues", ax=ax)
ax.set_title(title, fontsize=font_size)
ax.set_ylabel(y_axis_title, fontsize=font_size)
ax.set_xlabel(x_axis_title, fontsize=font_size)
ax.tick_params(labelsize=font_size)
# plt.xticks(rotation=30, ha='right')


if show:
plt.show()
# Save or show
if save_path:
fig.savefig(save_path, dpi=400, bbox_inches='tight')
else:
plt.close(fig)

if save_path is not None:
fig.savefig(save_path)
return fig

########################################################################################################################
# Static methods
Expand Down