Repository: mlcommons/GaNDLF

Issue:
[BUG] final_preds_and_avg_probs.csv file is not generated after inference
### Describe the bug
A clear and concise description of what the bug is.

### To Reproduce
Steps to reproduce the behavior:
1. Use the data `BreastMNIST from https://medmnist.com/'`
2. Run command `'python /content/GaNDLF/gandlf_run -c /content/config.yaml -i /content/medmnist/dataset/test_path.csv -m /content/model/ -t False -d cuda'`

### Expected behavior
After running inference for the classification task following .csv files must be generated:
- output_predictions.csv
- final_preds_and_avg_probs.csv
- logits.csv

But final_preds_and_avg_probs.csv and logits.csv are not generating.

### Environment information
<!-- Put the output of the following command:
python ./gandlf_debugInfo

      GANDLF version: 0.0.20-dev
      Git hash: 50f369f9671f5b618e9e50568b349a419eca6a65
      Platform: Linux-6.1.58+-x86_64-with-glibc2.35
      Machine: x86_64
      Processor: x86_64
      Architecture: 64bit ELF
      Python environment:
        Version: 3.10.12
        Implementation: CPython
        Compiler: GCC 11.4.0
        Build: main Nov 20 2023 15:14:05




Discussion:
Thanks for the report, @ujjwalbaid0408. What are the details about the environment, i.e., the result of `python ./gandlf_debugInfo` after activing the appropriate virtual environment?
I am running this using Google Colab:

GANDLF version: 0.0.20-dev
Git hash: 50f369f9671f5b618e9e50568b349a419eca6a65
Platform: Linux-6.1.58+-x86_64-with-glibc2.35
Machine: x86_64
Processor: x86_64
Architecture: 64bit ELF
Python environment:
  Version: 3.10.12
  Implementation: CPython
  Compiler: GCC 11.4.0
  Build: main Nov 20 2023 15:14:05
Thanks! 
@sarthakpati Assigning this to myself 

Resolving patch:
diff --git a/GANDLF/inference_manager.py b/GANDLF/inference_manager.py
--- a/GANDLF/inference_manager.py
+++ b/GANDLF/inference_manager.py
@@ -1,10 +1,10 @@
 import os
-from typing import Optional
 from pathlib import Path
+from typing import Optional
+
 import pandas as pd
 import torch
 import torch.nn.functional as F
-
 from GANDLF.compute import inference_loop
 from GANDLF.utils import get_unique_timestamp
 
@@ -26,10 +26,6 @@ def InferenceManager(
         device (str): The device to be used for inference.
         outputDir (Optional[str], optional): The output directory for the inference results. Defaults to None.
     """
-    # get the indeces for kfold splitting
-    inferenceData_full = dataframe
-
-    # if outputDir is not provided, create a new directory with a unique timestamp
     if outputDir is None:
         outputDir = os.path.join(modelDir, get_unique_timestamp())
         print(
@@ -42,41 +38,36 @@ def InferenceManager(
 
     # initialize parameters for inference
     for key in ["penalty_weights", "sampling_weights", "class_weights"]:
-        parameters[key] = parameters.get(key, None)
+        parameters.setdefault(key, None)
 
     n_folds = parameters["nested_training"]["validation"]
-
-    modelDir_split = [modelDir]
-    if "," in modelDir:
-        modelDir_split = modelDir.split(",")
+    modelDir_split = modelDir.split(",") if "," in modelDir else [modelDir]
 
     averaged_probs_list = []
     for current_modelDir in modelDir_split:
-        fold_dirs = []
-        if n_folds > 1:
-            directories = sorted(os.listdir(current_modelDir))
-            for d in directories:
-                if d.isdigit():
-                    fold_dirs.append(os.path.join(current_modelDir, d, ""))
-        else:
-            fold_dirs = [current_modelDir]
-
-        # this is for the case where inference is happening using a single model
-        if len(fold_dirs) == 0:
-            fold_dirs = [current_modelDir]
+        fold_dirs = (
+            [
+                os.path.join(current_modelDir, d, "")
+                for d in sorted(os.listdir(current_modelDir))
+                if d.isdigit()
+            ]
+            if n_folds > 1
+            else [current_modelDir]
+        )
 
         probs_list = []
-        class_list = None
         is_classification = parameters["problem_type"] == "classification"
-
-        # initialize model type for processing: if not defined, default to torch
-        if not ("type" in parameters["model"]):
-            parameters["model"]["type"] = "torch"
+        parameters["model"].setdefault("type", "torch")
+        class_list = (
+            [str(c) for c in parameters["model"]["class_list"]]
+            if is_classification
+            else None
+        )
 
         for fold_dir in fold_dirs:
             parameters["current_fold_dir"] = fold_dir
             inference_loop(
-                inferenceDataFromPickle=inferenceData_full,
+                inferenceDataFromPickle=dataframe,
                 modelDir=fold_dir,
                 device=device,
                 parameters=parameters,
@@ -84,38 +75,37 @@ def InferenceManager(
             )
 
             if is_classification:
-                logits_dir = os.path.join(fold_dir, "logits.csv")
-                is_logits_dir_exist = os.path.isfile(logits_dir)
-
-                if is_logits_dir_exist:
-                    # fold_logits = np.genfromtxt(logits_dir, delimiter=",")
-                    class_list = [str(c) for c in parameters["model"]["class_list"]]
-                    fold_logits = pd.read_csv(logits_dir)[class_list].values
+                logits_path = os.path.join(fold_dir, "logits.csv")
+                if os.path.isfile(logits_path):
+                    fold_logits = pd.read_csv(logits_path)[class_list].values
                     fold_logits = torch.from_numpy(fold_logits)
                     fold_probs = F.softmax(fold_logits, dim=1)
                     probs_list.append(fold_probs)
 
-        if is_classification and (n_folds > 1):
+        if is_classification and probs_list:
             probs_list = torch.stack(probs_list)
             averaged_probs_list.append(torch.mean(probs_list, 0))
 
     # this logic should be changed if we want to do multi-fold inference for histo images
-    if (parameters["modality"] == "rad") and averaged_probs_list and is_classification:
-        columns = ["SubjectID", "PredictedClass"] + parameters["model"]["class_list"]
-        averaged_probs_df = pd.DataFrame(columns=columns)
-        averaged_probs_df.SubjectID = dataframe[0]
+    if averaged_probs_list and is_classification:
+        averaged_probs_df = pd.DataFrame(
+            columns=["SubjectID", "PredictedClass"] + class_list
+        )
+        averaged_probs_df["SubjectID"] = dataframe.iloc[:, 0]
 
-        averaged_probs_across_models = torch.stack(averaged_probs_list)
         averaged_probs_across_models = torch.mean(
-            averaged_probs_across_models, 0
+            torch.stack(averaged_probs_list), 0
         ).numpy()
         averaged_probs_df[class_list] = averaged_probs_across_models
-        averaged_probs_df.PredictedClass = [
-            class_list[a] for a in averaged_probs_across_models.argmax(1)
+        averaged_probs_df["PredictedClass"] = [
+            class_list[idx] for idx in averaged_probs_across_models.argmax(axis=1)
         ]
+
         filepath_to_save = os.path.join(outputDir, "final_preds_and_avg_probs.csv")
         if os.path.isfile(filepath_to_save):
             filepath_to_save = os.path.join(
-                outputDir, "final_preds_and_avg_probs" + get_unique_timestamp() + ".csv"
+                outputDir,
+                "final_preds_and_avg_probs_" + get_unique_timestamp() + ".csv",
             )
+
         averaged_probs_df.to_csv(filepath_to_save, index=False)


Test patch: