OSCR

Multimodal and Hyperspectral Dataset for Segmentation of Bulky Waste using VIS, IR, NIR, and Terahertz Imaging.

Code ↔ Paper

5 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 5 matches
  1. [1] § Technical Validation › Image classification task ↔ torchaskivit/cnn_classes.py, lines 751 to 802 · score 0.73 · depthwise separable convolutions, SpectrumNetDSBN, CNN, channel · Report a mismatch
  2. [2] § Technical Validation › Image classification task ↔ torchaskivit/evalutils.py, lines 596 to 663 · score 0.70 · normMCC, confusion matrix, precision, FN, TN, TP · Report a mismatch
  3. [3] § Technical Validation › Image classification task ↔ torchaskivit/evalutils.py, lines 596 to 663 · score 0.64 · normMCC, confusion matrix, Precision, recall, metrics, error · Report a mismatch
  4. [4] § Technical Validation › Image classification task ↔ torchaskivit/utils.py, lines 309 to 398 · score 0.58 · vertical flips, horizontal, augmentation, rotations, transformations · Report a mismatch
  5. [5] § Technical Validation › Semantic segmentation task ↔ askivit_V1.5_inference.py, lines 95 to 138 · score 0.57 · cross entropy, loss function, models, class · Report a mismatch

Paper

Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 950 lines · 35 KB · CC0-1.0 · 2 matches

  1. # -*- coding: utf-8 -*-
  2. import os
  3. import sys
  4. import numpy as np
  5. import matplotlib.pyplot as plt
  6. import matplotlib.colors
  7. import re
  8. import json
  9. import glob
  10. DIR= os.path.abspath(r"C:\Users")
  11. # if dir not in sys.path:
  12. sys.path.append(DIR)
  13. import torchaskivit.constants as constants
  14. def get_actual_label_ASKIVIT_V2(filepath):
  15. """ Get actual label from filepath. """
  16. # Get filename without path.
  17. npy_filename = os.path.basename(filepath)
  18. # get label out of file name
  19. if "GT_" not in npy_filename:
  20. npy_filename_sliced = "_".join(npy_filename.split(".")[1].split("_")[3:])
  21. if "Non_wood_" in npy_filename_sliced:
  22. main_label = "_".join(npy_filename_sliced.split("_")[0:2])
  23. sub_label = "_".join(npy_filename_sliced.split("_")[2:])
  24. elif "Wood_" in npy_filename_sliced:
  25. main_label = "_".join(npy_filename_sliced.split("_")[0:1])
  26. sub_label = "_".join(npy_filename_sliced.split("_")[1:])
  27. else:
  28. raise ValueError("GT file handed to get_acutal_label_AKSIVIT_V2 instead of regular npy.")
  29. # Map label to two main classes.
  30. if main_label == "Wood":
  31. main_class_idx = 0
  32. elif main_label == "Non_wood":
  33. main_class_idx = 1
  34. else:
  35. raise ValueError(f"Label {main_label} not in class_to_idx")
  36. sub_label_idx = constants.ASKIVIT_LABELS_V2.get(sub_label)
  37. main_label_idx = constants.ASKIVIT_LABELS_V2_MAIN.get(main_label)
  38. return main_label_idx, sub_label_idx
  39. def get_actual_label_ASKIVIT_V1(filepath):
  40. """ Get actual label from filepath. """
  41. filename = os.path.basename(filepath)
  42. label = "_".join(filename.split(".")[0].split("_")[-2:])
  43. main_label = label.split("_")[0]
  44. label_idx = constants.ASKIVIT_LABELS_V1.get(label)
  45. main_label_idx = constants.ASKIVIT_LABELS_V1_MAIN.get(main_label)
  46. return main_label_idx, label_idx
  47. def get_actual_label_ASKIVIT_V1_5(filepath):
  48. """ Get actual label from filepath. """
  49. filename = os.path.basename(filepath)
  50. label = "_".join(filename.split(".")[0].split("_")[5:])
  51. if "Non" in label:
  52. main_label = "_".join(label.split("_")[0:2])
  53. sub_label = "_".join(label.split("_")[2:])
  54. elif "Wood" in label:
  55. main_label = "_".join(label.split("_")[0:1])
  56. sub_label = "_".join(label.split("_")[1:])
  57. else:
  58. Warning('IF error in get_actual_label_ASKIVIT_V1_5')
  59. main_label=None
  60. sub_label=None
  61. sub_label_idx = constants.ASKIVIT_LABELS_V1_5.get(sub_label)
  62. main_label_idx = constants.ASKIVIT_LABELS_V1_5_MAIN.get(main_label)
  63. return main_label_idx, sub_label_idx
  64. def get_actual_label_ASKIVIT_V1_5_metal_expert(filepath):
  65. """ Get actual label from filepath. """
  66. filename = os.path.basename(filepath)
  67. label = "_".join(filename.split(".")[0].split("_")[5:])
  68. if "Non" in label:
  69. main_label = "_".join(label.split("_")[0:2])
  70. sub_label = "_".join(label.split("_")[2:])
  71. elif "Wood" in label:
  72. main_label = "_".join(label.split("_")[0:1])
  73. sub_label = "_".join(label.split("_")[1:])
  74. if "Metal" in sub_label:
  75. main_label = "Metal"
  76. else:
  77. main_label = "Non_metal"
  78. sub_label_idx = constants.ASKIVIT_LABELS_V1_5_METAL_EXPERT.get(sub_label)
  79. main_label_idx = constants.ASKIVIT_LABELS_V1_5_METAL_EXPERT_MAIN.get(main_label)
  80. return main_label_idx, sub_label_idx
  81. def get_actual_label_ASKIVIT_V2_metal_expert(filepath):
  82. """ Get actual label from filepath. """
  83. # Get filename without path.
  84. npy_filename = os.path.basename(filepath)
  85. # get label out of file name
  86. if "GT_" not in npy_filename:
  87. npy_filename_sliced = "_".join(npy_filename.split(".")[1].split("_")[3:])
  88. if "Non_wood_" in npy_filename_sliced:
  89. main_label = "_".join(npy_filename_sliced.split("_")[0:2])
  90. sub_label = "_".join(npy_filename_sliced.split("_")[2:])
  91. elif "Wood_" in npy_filename_sliced:
  92. main_label = "_".join(npy_filename_sliced.split("_")[0:1])
  93. sub_label = "_".join(npy_filename_sliced.split("_")[1:])
  94. else:
  95. raise ValueError("GT file handed to get_acutal_label_AKSIVIT_V2 instead of regular npy.")
  96. # Map label to two main classes.
  97. if sub_label == "Metal":
  98. main_class_idx = 0
  99. elif "metal" in sub_label:
  100. main_class_idx = 0
  101. else:
  102. main_class_idx = 1
  103. sub_label_idx = constants.ASKIVIT_LABELS_V2_METAL_EXPERT.get(sub_label)
  104. #main_label_idx = constants.ASKIVIT_LABELS_V2_MAIN_METAL_EXPERT.get(main_label)
  105. return main_class_idx, sub_label_idx
  106. def generate_prediction_dict(inference: dict) -> dict:
  107. """
  108. Build prediction data from an inference JSON for downstream evaluation.
  109. Supports wood ('Non_wood'|'Wood') and metal expert ('Non_metal'|'Metal').
  110. Returns
  111. -------
  112. data : dict
  113. {
  114. 'y_pred': list[int], # binary predictions (Non_* -> 1, * -> 0)
  115. 'y_true': list[int], # binary GT (Non_* -> 1, * -> 0)
  116. 'gt': list[int], # multi-class GT index from ASKIVIT_LABELS_V1_5 via SUBCLASS
  117. }
  118. """
  119. results = inference.get("results", [])
  120. if not isinstance(results, list):
  121. raise ValueError("inference['results'] must be a list")
  122. if not results:
  123. return {"y_pred": [], "y_true": [], "gt": []}
  124. wood_set = {"Non_wood", "Wood"}
  125. metal_set = {"Non_metal", "Metal"}
  126. # Prefer prediction to detect domain; fallback to filename marker.
  127. def _detect_domain(item0):
  128. pred = item0.get("predicted_label_name")
  129. if pred in metal_set:
  130. return "metal"
  131. if pred in wood_set:
  132. return "wood"
  133. stem = os.path.splitext(os.path.basename(item0.get("patch", "")))[0]
  134. m = re.search(r"_(Non_wood|Wood|Non_metal|Metal)_(.+)$", stem)
  135. mc = m.group(1) if m else None
  136. if mc in metal_set:
  137. return "metal"
  138. if mc in wood_set:
  139. return "wood"
  140. raise ValueError("Cannot detect domain (wood/metal).")
  141. domain = _detect_domain(results[0])
  142. # Encoding convention (your latest): Non_* -> 1, * -> 0
  143. if domain == "wood":
  144. pred_label_map = {"Non_wood": 1, "Wood": 0}
  145. filename_mainclass = {"Non_wood", "Wood"}
  146. tail_regex = re.compile(r"_(Non_wood|Wood)_(.+)$")
  147. else: # metal
  148. pred_label_map = {"Non_metal": 1, "Metal": 0}
  149. filename_mainclass = {"Non_metal", "Metal"}
  150. tail_regex = re.compile(r"_(Non_metal|Metal|Non_wood|Wood)_(.+)$") # allow wood MAINCLASS in filenames
  151. # For metal domain, derive GT Non_metal/Metal from SUBCLASS if filename MAINCLASS is wood-like.
  152. metal_subclasses = {
  153. "Metal", "Metal_covered_by_Wood", "Metal_covered_by_Upholstery", "Metal_covered_by_Plastic"
  154. }
  155. # Everything else (Background, Plastic, Mineralics, Upholstery, Wood_covered_by_*) -> Non_metal
  156. def _parse_tail(patch_path: str):
  157. stem = os.path.splitext(os.path.basename(patch_path))[0]
  158. m = tail_regex.search(stem)
  159. if not m:
  160. raise ValueError(
  161. f"Cannot extract MAINCLASS/SUBCLASS from filename: {os.path.basename(patch_path)}. "
  162. "Expected ..._{MAINCLASS}_{SUBCLASS}.npy"
  163. )
  164. return m.group(1), m.group(2) # MAINCLASS token found, SUBCLASS (may contain underscores)
  165. y_pred, y_true, gt = [], [], []
  166. for i, item in enumerate(results):
  167. # --- y_pred from predicted label ---
  168. pred_name = item.get("predicted_label_name")
  169. if pred_name not in pred_label_map:
  170. raise ValueError(
  171. f"[item {i}] Unknown predicted label {pred_name!r} for domain '{domain}'. "
  172. f"Expected one of {list(pred_label_map)}."
  173. )
  174. y_pred.append(pred_label_map[pred_name])
  175. # --- parse filename tail ---
  176. patch = item.get("patch")
  177. if patch is None:
  178. raise ValueError(f"[item {i}] Missing 'patch' in inference item.")
  179. mainclass_token, subclass = _parse_tail(patch)
  180. # --- y_true (binary) ---
  181. if domain == "wood":
  182. # MAINCLASS is expected to be Non_wood|Wood
  183. if mainclass_token not in filename_mainclass:
  184. raise ValueError(f"[item {i}] MAINCLASS {mainclass_token!r} not valid for wood domain.")
  185. y_true.append(1 if mainclass_token.startswith("Non_") else 0)
  186. else: # domain == "metal"
  187. # If filename has metal-style MAINCLASS, use it; if it has wood-style, infer from SUBCLASS.
  188. if mainclass_token in {"Non_metal", "Metal"}:
  189. y_true.append(1 if mainclass_token == "Non_metal" else 0)
  190. elif mainclass_token in {"Non_wood", "Wood"}:
  191. # Infer from SUBCLASS membership
  192. is_metal = subclass in metal_subclasses
  193. y_true.append(0 if is_metal else 1) # Non_metal->1, Metal->0
  194. else:
  195. raise ValueError(f"[item {i}] Unexpected MAINCLASS {mainclass_token!r} in metal domain.")
  196. # --- gt (multiclass) from SUBCLASS table ---
  197. gt_val = constants.ASKIVIT_LABELS_V1_5.get(subclass)
  198. if gt_val is None:
  199. raise ValueError(
  200. f"[item {i}] Unknown SUBCLASS/GT label {subclass!r} "
  201. "(not found in ASKIVIT_LABELS_V1_5)."
  202. )
  203. gt.append(gt_val)
  204. return {"y_pred": y_pred, "y_true": y_true, "gt": gt}
  205. # Optional quick test harness
  206. def TEST_generate_prediction_dict():
  207. print("TESTING: generate_prediction_dict()")
  208. # RGB
  209. # THz
  210. inference_path = r"D:\Askivit 1.5 2ter Datensatz mit korrigierter Datamap\Metall Experte\Varianten\Nur THz Bereich\Inference\Test Patches\metal_expert_inference_results.json"
  211. prediction_path = r"D:\Askivit 1.5 2ter Datensatz mit korrigierter Datamap\Metall Experte\Varianten\Nur THz Bereich\Inference\Test Patches\metal_expert_prediction.json.json"
  212. with open(inference_path, "r", encoding="utf-8") as f:
  213. inference = json.load(f)
  214. data = generate_prediction_dict(inference)
  215. with open(prediction_path, "w", encoding="utf-8") as f:
  216. json.dump(data, f, indent=4)
  217. print("Saved predictions.json")
  218. def generate_cm_correct_vs_uncorrect(data: dict, mode="v1.5") -> np.ndarray:
  219. """
  220. NOT CORRECT CM but usefull: Plots correct classified vs not_correct
  221. (no matter how the binding between subclass and main class actually is defined)
  222. For THz-ME can be hard to read
  223. Generates a confusion matrix based on predictions and ground truth labels.
  224. Parameters
  225. ----------
  226. data: dict
  227. A dictionary containing the following key-value pairs:
  228. 'y_pred': list or array-like
  229. The predicted labels.
  230. 'y_true': list or array-like
  231. The true labels.
  232. 'gt': list or array-like
  233. The ground truth categories that determine if a sample is
  234. considered 'wood' or 'non-wood'.
  235. mode: str, optional
  236. The operational mode that determines the number of classes
  237. and the categorization criteria. Should be either 'v1' or 'v2'.
  238. The default is 'v1'.
  239. Returns
  240. -------
  241. np.ndarray
  242. A 2D NumPy array representing the confusion matrix, where
  243. the first dimension is 'wood' vs. 'non-wood', and the
  244. second dimension represents the classes corresponding to
  245. the mode.
  246. """
  247. if mode not in ["v1", "v1.5", "v2"]:
  248. raise ValueError("Mode must be either 'v1' or 'v1.5' or 'v2'.")
  249. mode_num_classes = {"v1": 16, "v1.5": 16, "v2": 14}
  250. mode_wood_idxs = {"v1": [0, 1, 2, 3],"v1.5": [0, 1, 2, 3], "v2": [0, 1, 2, 3]}
  251. num_classes = mode_num_classes.get(mode)
  252. wood_idxs = mode_wood_idxs.get(mode)
  253. cm = np.zeros((2, num_classes))
  254. eval_results = np.equal(data["y_pred"], data["y_true"])
  255. for i in range(len(eval_results)):
  256. row = 0 if data["gt"][i] in wood_idxs else 1
  257. if eval_results[i]:
  258. cm[row, data["gt"][i]] += 1
  259. else:
  260. cm[int(np.abs(row - 1)), data["gt"][i]] += 1
  261. return cm
  262. import numpy as np
  263. def generate_cm(data):
  264. """
  265. Generate a 2×N confusion matrix for binary main-class predictions
  266. versus subclass ground truth.
  267. Rows:
  268. 0 → predicted 'Wood' (or 'Metal')
  269. 1 → predicted 'Non_*'
  270. Columns:
  271. subclass indices (0 .. N-1) taken from data['gt'].
  272. Parameters
  273. ----------
  274. data : dict
  275. Must contain:
  276. - 'y_pred': list[int] (binary predictions, 0/1)
  277. - 'gt' : list[int] (ground-truth subclass indices)
  278. Returns
  279. -------
  280. cm : np.ndarray of shape (2, num_classes)
  281. Confusion matrix with predicted class (rows) vs subclass index (columns).
  282. """
  283. y_pred = np.asarray(data["y_pred"], dtype=int)
  284. gt = np.asarray(data["gt"], dtype=int)
  285. # --- Sanity checks ---
  286. if len(y_pred) != len(gt):
  287. raise ValueError("y_pred and gt must have equal length.")
  288. if not np.isin(y_pred, [0, 1]).all():
  289. raise ValueError(f"y_pred must contain only 0/1 values, found {np.unique(y_pred)}.")
  290. if gt.size == 0:
  291. raise ValueError("gt array is empty.")
  292. num_classes = int(np.max(gt)) + 1
  293. cm = np.zeros((2, num_classes), dtype=int)
  294. # --- Count occurrences for each (prediction, subclass) pair ---
  295. for yp, g in zip(y_pred, gt):
  296. cm[int(yp), int(g)] += 1
  297. return cm
  298. def plot_cm_recall_dual(cm, mode="v1.5"):
  299. """
  300. Generalized confusion matrix visualization for ASKIVIT subclasses.
  301. Features:
  302. ---------------------------------------------------------------------
  303. - Plots subclass confusion matrix (2×N) with recall row below.
  304. - Adds a 2×2 main-class confusion matrix (right) without color mapping.
  305. - Uses a unified red–yellow–green colormap for subclass visualization.
  306. - Displays metrics for the main-class matrix (class_0 as positive).
  307. Interpretation:
  308. class_0 → the "main" class (e.g. Wood or Metal)
  309. class_1 → the complement (Non-Wood or Non-Metal)
  310. Confusion matrix cell meaning (Pred on Y, True on X):
  311. | True 0 | True 1 |
  312. ----+---------+--------+
  313. Pred 0 | TP=recall | FP=fpr
  314. Pred 1 | FN=fnr | TN=specificity
  315. """
  316. matplotlib.pyplot.close() # Not tested
  317. # --- Style ---
  318. plt.rcParams["font.family"] = "serif"
  319. plt.rcParams["font.serif"] = ["Times New Roman"] + plt.rcParams["font.serif"]
  320. small_fontsize = 11
  321. # --- Mode configuration ---
  322. if mode not in ["v1.5", "v1.5ME", "v2"]:
  323. raise ValueError("Mode must be 'v1.5', 'v1.5ME', or 'v2'.")
  324. labels = {
  325. "v1.5": constants.ASKIVIT_LABELS_V1_5_PLOT,
  326. "v1.5ME": constants.ASKIVIT_LABELS_V1_5_PLOT, # placeholder for Metal Expert
  327. "v2": constants.ASKIVIT_LABELS_V2,
  328. }
  329. label_list = labels[mode]
  330. n_cols = len(label_list)
  331. # --- Subclass → main-class mapping ---
  332. if mode == "v1.5":
  333. subclass_to_main = np.array([0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
  334. y_labels = ["Predicted as\n Wood", "Predicted as\n Non-Wood"]
  335. x_labels = ["True Wood", "True Non-Wood"]
  336. elif mode == "v2":
  337. subclass_to_main = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
  338. y_labels = ["Predicted as\n Wood", "Predicted as\n Non-Wood"]
  339. x_labels = ["True Wood", "True Non-Wood"]
  340. elif mode == "v1.5ME":
  341. subclass_to_main = np.array([1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0])
  342. y_labels = ["Predicted as\n Metal", "Predicted as\n Non-Metal"]
  343. x_labels = ["True\n Metal", "True\n Non-Metal"]
  344. # --- Compute main-class confusion matrix & metrics ---
  345. metrics_array, metrics_dict = compute_main_class_metrics_from_cm(cm, subclass_to_main)
  346. # --- Colormap (red→yellow→green) ---
  347. cmap = matplotlib.colors.LinearSegmentedColormap.from_list(
  348. "red_yellow_green", ["#d7191c", "#f4e071", "#00876C"], N=256
  349. )
  350. # === Figure layout ===
  351. fig = plt.figure(figsize=(10, 3.2))
  352. left, width_sub, width_main, left_gap = 0.13, 0.60, 0.075, 0.10
  353. ax_cm = fig.add_axes([left, 0.57, width_sub, 0.52])
  354. eps = 1.0 / (fig.get_dpi() * fig.get_size_inches()[1])
  355. rec_h = 0.28 * ax_cm.get_position().height
  356. ax_rec = fig.add_axes([
  357. ax_cm.get_position().x0,
  358. ax_cm.get_position().y0 - rec_h - eps,
  359. ax_cm.get_position().width,
  360. rec_h
  361. ])
  362. ax_main = fig.add_axes([left + width_sub + left_gap, 0.57, width_main, 0.52])
  363. # -------------------------------------------------------------------------
  364. # Subclass Confusion Matrix
  365. # -------------------------------------------------------------------------
  366. colsum = np.sum(cm, axis=0)
  367. colsum[colsum == 0] = 1
  368. cm_norm = cm / colsum
  369. mask = np.array([[subclass_to_main[j] != i for j in range(n_cols)] for i in range(2)])
  370. cm_masked = np.ma.masked_where(mask, cm_norm)
  371. cmap_with_white = cmap.copy()
  372. cmap_with_white.set_bad(color="white")
  373. ax_cm.matshow(cm_masked, cmap=cmap_with_white, vmin=0, vmax=1)
  374. # Axes and grid
  375. ax_cm.set_xticks(np.arange(n_cols))
  376. ax_cm.set_xticklabels([])
  377. ax_cm.set_yticks([0, 1])
  378. ax_cm.set_yticklabels(y_labels, fontsize=small_fontsize, va="center", ha="right")
  379. ax_cm.xaxis.set_ticks_position("bottom")
  380. ax_cm.set_xticks(np.arange(n_cols + 1) - 0.5, minor=True)
  381. ax_cm.set_yticks(np.arange(3) - 0.5, minor=True)
  382. ax_cm.grid(which="minor", color="black", linestyle="-", linewidth=1)
  383. ax_cm.axhline(y=0.5, color="black", linewidth=3)
  384. # Annotate absolute counts
  385. for i in range(2):
  386. for j in range(n_cols):
  387. ax_cm.text(j, i, f"{int(cm[i, j])}",
  388. ha="center", va="center", fontsize=small_fontsize)
  389. # -------------------------------------------------------------------------
  390. # Recall Row
  391. # -------------------------------------------------------------------------
  392. correct_row = subclass_to_main.copy()
  393. recalls = cm[correct_row, np.arange(n_cols)] / colsum
  394. recalls = np.clip(recalls, 0, 1)
  395. rec_matrix = np.expand_dims(recalls, axis=0)
  396. ax_rec.matshow(rec_matrix, cmap=cmap, vmin=0, vmax=1)
  397. ax_rec.set_xticks(np.arange(n_cols))
  398. ax_rec.set_xticklabels(label_list, rotation=45, ha="right", fontsize=small_fontsize)
  399. ax_rec.set_yticks([0])
  400. ax_rec.set_yticklabels(["Recall"], fontsize=small_fontsize)
  401. ax_rec.xaxis.set_ticks_position("bottom")
  402. ax_rec.set_xticks(np.arange(n_cols + 1) - 0.5, minor=True)
  403. ax_rec.set_yticks(np.arange(2) - 0.5, minor=True)
  404. ax_rec.grid(which="minor", color="black", linestyle="-", linewidth=1)
  405. # Recall text annotations (0.xx)
  406. for j in range(n_cols):
  407. ax_rec.text(j, 0, f"{recalls[j]:.2f}",
  408. ha="center", va="center", fontsize=small_fontsize)
  409. # -------------------------------------------------------------------------
  410. # Main-Class Matrix (2×2, white background + metrics text)
  411. # -------------------------------------------------------------------------
  412. # White background fill
  413. bg = np.ones((2, 2, 3), dtype=float)
  414. ax_main.imshow(bg, extent=(-0.5, 1.5, 1.5, -0.5), zorder=0)
  415. ax_main.set_xlim(-0.5, 1.5)
  416. ax_main.set_ylim(1.5, -0.5)
  417. ax_main.set_aspect("equal", adjustable="box")
  418. # Axis labels
  419. ax_main.set_xticks([0, 1])
  420. ax_main.set_xticklabels(x_labels, rotation=45, ha="right", fontsize=small_fontsize)
  421. ax_main.set_yticks([0, 1])
  422. ax_main.set_yticklabels(y_labels, fontsize=small_fontsize, va="center", ha="right")
  423. ax_main.xaxis.set_ticks_position("bottom")
  424. # Grid lines (thin)
  425. ax_main.set_xticks([-0.5, 0.5, 1.5], minor=True)
  426. ax_main.set_yticks([-0.5, 0.5, 1.5], minor=True)
  427. ax_main.grid(which="minor", color="black", linestyle="-", linewidth=1)
  428. for spine in ax_main.spines.values():
  429. spine.set_visible(False)
  430. # --- Metrics text (class_0 as positive reference) ---
  431. m = metrics_dict["class_0"]
  432. ax_main.text(0, 0, f"{m['recall']:.2f}", ha="center", va="center", fontsize=small_fontsize)
  433. ax_main.text(1, 0, f"{m['fpr']:.2f}", ha="center", va="center", fontsize=small_fontsize)
  434. ax_main.text(0, 1, f"{m['fnr']:.2f}", ha="center", va="center", fontsize=small_fontsize)
  435. ax_main.text(1, 1, f"{m['specificity']:.2f}", ha="center", va="center", fontsize=small_fontsize)
  436. return fig, (ax_cm, ax_rec, ax_main)
  437. def compute_main_class_metrics_from_cm(
  438. cm: np.ndarray,
  439. subclass_to_main: np.ndarray | None = None
  440. ) -> tuple[np.ndarray, dict]:
  441. """
  442. Compute 2×2 main-class confusion matrix and full set of metrics for both
  443. main classes (each considered as the positive class once).
  444. Args:
  445. cm (np.ndarray): 2×N confusion matrix: Where rows = true main classes,
  446. columns = predicted subclasses.
  447. I repeat we use intentionally a swapped confusion matrix with rows: true main classes
  448. and columns = predictions
  449. subclass_to_main (np.ndarray, optional): length-N array assigning each
  450. subclass column to its main class (0 or 1).
  451. Defaults to ASKIVIT v1.5 layout:
  452. [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1].
  453. Returns:
  454. tuple:
  455. metrics_array (np.ndarray): shape (2, 9)
  456. columns = [
  457. recall, fnr, fpr, specificity, f1, acc,
  458. recall_macro, f1_macro, acc_macro
  459. ]
  460. row 0: class 0 positive
  461. row 1: class 1 positive
  462. metrics_dict (dict): keyworded dictionary with:
  463. {
  464. "cm_main": 2×2 confusion matrix,
  465. "class_0": {...metrics...},
  466. "class_1": {...metrics...},
  467. "macro": {...macro_averages...}
  468. }
  469. """
  470. # --- Default subclass-to-main mapping (v1.5 Wood/Non-Wood) ---
  471. if subclass_to_main is None:
  472. subclass_to_main = np.array(
  473. [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], dtype=int)
  474. Warning("Using default subclass to main class in compute_main_class_metrics_from_cm")
  475. # class 0 is Wood, class 1 is Non-Wood
  476. # class 0 is This, class 1 is Non-this
  477. n_cols = cm.shape[1]
  478. if len(subclass_to_main) != n_cols:
  479. raise ValueError(
  480. f"Length of subclass_to_main ({len(subclass_to_main)}) "
  481. f"must match cm columns ({n_cols})."
  482. )
  483. # --- Aggregate subclass confusion matrix into main-class (2×2) ---
  484. cm_main = np.zeros((2, 2), dtype=float)
  485. for j in range(n_cols):
  486. pred_main = subclass_to_main[j]
  487. cm_main[0, pred_main] += cm[0, j]
  488. cm_main[1, pred_main] += cm[1, j]
  489. eps = 1e-12
  490. metrics_array = np.zeros((2, 13), dtype=float) # rows = classes, cols = metrics
  491. # helper for MCC
  492. def _mcc(tp, tn, fp, fn):
  493. denom = (tp + fp) * (tp + fn) * (tn + fp) * (tn + fn)
  494. return ((tp * tn) - (fp * fn)) / (np.sqrt(denom) + eps)
  495. def _mcc_norm(tp, tn, fp, fn):
  496. normMCC = (_mcc(tp, tn, fp, fn)+1)/2
  497. return normMCC
  498. # --- Compute metrics for both main classes explicitly ---
  499. per_class = {}
  500. # class 0 positive
  501. tp0 = cm_main[0, 0]
  502. fp0 = cm_main[0, 1]
  503. fn0 = cm_main[1, 0]
  504. tn0 = cm_main[1, 1]
  505. total0 = tp0 + tn0 + fp0 + fn0
  506. recall0 = tp0 / (tp0 + fn0 + eps)
  507. fnr0 = fn0 / (tp0 + fn0 + eps)
  508. fpr0 = fp0 / (fp0 + tn0 + eps)
  509. specificity0 = tn0 / (fp0 + tn0 + eps)
  510. precision0 = tp0 / (tp0 + fp0 + eps)
  511. f10 = 2 * (precision0 * recall0) / (precision0 + recall0 + eps)
  512. acc0 = (tp0 + tn0) / (total0 + eps)
  513. mcc0 = _mcc(tp0, tn0, fp0, fn0)
  514. norm_mcc0 = _mcc_norm(tp0, tn0, fp0, fn0)
  515. metrics_array[0, 0:8] = [
  516. recall0, fnr0, fpr0, specificity0, f10, acc0, mcc0,norm_mcc0
  517. ]
  518. per_class["class_0"] = {
  519. "tp": tp0,
  520. "fp": fp0,
  521. "fn": fn0,
  522. "tn": tn0,
  523. "recall": recall0,
  524. "fnr": fnr0,
  525. "fpr": fpr0,
  526. "specificity": specificity0,
  527. "precision": precision0,
  528. "f1": f10,
  529. "acc": acc0,
  530. "mcc": mcc0,
  531. "norm_mcc": norm_mcc0,
  532. }
  533. # === Class 1 as positive (e.g. Non-Wood or Non-Metal) ===
  534. tp1 = cm_main[1, 1]
  535. fp1 = cm_main[1, 0]
  536. fn1 = cm_main[0, 1]
  537. tn1 = cm_main[0, 0]
  538. total1 = tp1 + tn1 + fp1 + fn1
  539. recall1 = tp1 / (tp1 + fn1 + eps)
  540. fnr1 = fn1 / (tp1 + fn1 + eps)
  541. fpr1 = fp1 / (fp1 + tn1 + eps)
  542. specificity1 = tn1 / (fp1 + tn1 + eps)
  543. precision1 = tp1 / (tp1 + fp1 + eps)
  544. f11 = 2 * (precision1 * recall1) / (precision1 + recall1 + eps)
  545. acc1 = (tp1 + tn1) / (total1 + eps)
  546. mcc1 = _mcc(tp1, tn1, fp1, fn1)
  547. norm_mcc1 = _mcc_norm(tp1, tn1, fp1, fn1)
  548. metrics_array[1, 0:8] = [
  549. recall1, fnr1, fpr1, specificity1, f11, acc1, mcc1, norm_mcc1
  550. ]
  551. per_class["class_1"] = {
  552. "tp": tp1,
  553. "fp": fp1,
  554. "fn": fn1,
  555. "tn": tn1,
  556. "recall": recall1,
  557. "fnr": fnr1,
  558. "fpr": fpr1,
  559. "specificity": specificity1,
  560. "precision": precision1,
  561. "f1": f11,
  562. "acc": acc1,
  563. "mcc": mcc1,
  564. "norm_mcc": norm_mcc1,
  565. }
  566. # --- Macro-averaged metrics ---
  567. recall_macro = np.mean(metrics_array[:, 0])
  568. f1_macro = np.mean(metrics_array[:, 4])
  569. acc_macro = np.mean(metrics_array[:, 5])
  570. mcc_macro = metrics_array[:, 6].mean().item()
  571. norm_mcc_macro = metrics_array[:, 7].mean().item()
  572. metrics_array[:, 8:] = [recall_macro, f1_macro, acc_macro, mcc_macro, norm_mcc_macro]
  573. macro_dict = {
  574. "recall_macro": recall_macro,
  575. "f1_macro": f1_macro,
  576. "acc_macro": acc_macro,
  577. "mcc_macro": mcc_macro,
  578. "norm_mcc_macro": norm_mcc_macro,
  579. }
  580. # --- Build structured dictionary output ---
  581. metrics_dict = {
  582. "class_0": per_class["class_0"],
  583. "class_1": per_class["class_1"],
  584. "macro": macro_dict,
  585. }
  586. return metrics_array, metrics_dict
  587. def save_metric_array_to_csv(array: np.ndarray, filename: str):
  588. """
  589. Save a 2×9 metrics array as a German-style CSV file:
  590. - Semicolon-separated (;)
  591. - Comma as decimal separator (,)
  592. - UTF-8 encoded
  593. - No comment symbols
  594. Columns: recall; fnr; fpr; specificity; f1; acc;
  595. recall_macro; f1_macro; acc_macro
  596. """
  597. header = "recall;fnr;fpr;specificity;f1;acc;recall_macro;f1_macro;acc_macro"
  598. # 1. Save with dot decimal (temporary text)
  599. temp_str = np.array2string(
  600. array,
  601. formatter={"float_kind": lambda x: f"{x:.6f}"},
  602. separator=";",
  603. )
  604. # 2. Clean up the numpy string output
  605. temp_str = temp_str.replace("[[", "").replace("]]", "")
  606. temp_str = temp_str.replace("[", "").replace("]", "").strip()
  607. # 3. Replace dots with commas for decimal separator
  608. temp_str = temp_str.replace(".", ",")
  609. # 4. Split rows and rebuild CSV text
  610. rows = temp_str.split("\n")
  611. csv_text = header + "\n" + "\n".join(rows)
  612. # 5. Write to file (always .csv)
  613. if not filename.lower().endswith(".csv"):
  614. filename += ".csv"
  615. with open(filename, "w", encoding="utf-8") as f:
  616. f.write(csv_text)
  617. print(f"Metrics saved (German CSV): {filename}")
  618. def _detect_json_kind(payload: dict) -> str:
  619. """
  620. Returns 'inference' if payload looks like an inference JSON (has 'results'),
  621. 'predictions' if it looks like the simplified dict (has 'y_pred' & 'y_true'),
  622. otherwise raises.
  623. """
  624. if isinstance(payload, dict) and "results" in payload:
  625. return "inference"
  626. if isinstance(payload, dict) and all(k in payload for k in ("y_pred", "y_true", "gt")):
  627. return "predictions"
  628. raise ValueError("Unknown JSON structure: expected inference{'results':[...]}"
  629. " or predictions{'y_pred','y_true','gt'}.")
  630. def full_evaluate_on_json(json_filepath: str,
  631. save_path: str | None = None,
  632. write_intermediate_predictions: bool = True,
  633. mode = "v1.5",
  634. silent = False):
  635. """
  636. Evaluate either an inference JSON (converts to predictions) or a predictions JSON.
  637. Parameters
  638. ----------
  639. json_filepath : str
  640. Path to either:
  641. - ...*_inference_results.json (with key 'results'), or
  642. - ...*_prediction.json (with keys 'y_pred','y_true','gt')
  643. save_path : str | None
  644. Folder to store outputs. Defaults to the parent folder of json_filepath.
  645. write_intermediate_predictions : bool
  646. If True and input is inference JSON, writes the derived predictions JSON next to outputs.
  647. mode : str
  648. Returns
  649. -------
  650. cm: Confusion matrix with absolut values cm_metrics
  651. metrics_array: different metrics per relevant class: in form class0: row0 and class1: row1
  652. metric_dict: ALL metric values as a large dict
  653. """
  654. # ---- Load file ----
  655. with open(json_filepath, "r", encoding="utf-8") as f:
  656. payload = json.load(f)
  657. kind = _detect_json_kind(payload)
  658. # ---- Convert to 'data' dict if needed ----
  659. if kind == "inference":
  660. data = generate_prediction_dict(payload) # uses your previously defined function
  661. base_name = os.path.splitext(os.path.basename(json_filepath))[0]
  662. pred_filename = f"{base_name}_prediction.json"
  663. else: # 'predictions'
  664. data = payload
  665. base_name = os.path.splitext(os.path.basename(json_filepath))[0]
  666. pred_filename = os.path.basename(json_filepath) # already a predictions file
  667. # ---- Save dir ----
  668. save_dir = save_path if save_path is not None else os.path.dirname(json_filepath)
  669. os.makedirs(save_dir, exist_ok=True)
  670. # ---- Optionally write the (new) predictions JSON ----
  671. if kind == "inference" and write_intermediate_predictions:
  672. pred_out_path = os.path.join(save_dir, pred_filename)
  673. with open(pred_out_path, "w", encoding="utf-8") as f:
  674. json.dump(data, f, indent=4)
  675. print(f"Derived predictions written to: {pred_out_path}")
  676. # ---- Sanity checks ----
  677. n = len(data["y_pred"])
  678. if not (len(data["y_true"]) == len(data["gt"]) == n):
  679. raise ValueError(f"Length mismatch: y_pred={len(data['y_pred'])}, "
  680. f"y_true={len(data['y_true'])}, gt={len(data['gt'])}")
  681. # ---- Compute CM + metrics ----
  682. cm = generate_cm(data)
  683. if mode =="v1.5ME":
  684. subclass_to_main = np.array([1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0])
  685. metrics_array, metrics_dict = compute_main_class_metrics_from_cm(cm, subclass_to_main)
  686. else:
  687. metrics_array, metrics_dict = compute_main_class_metrics_from_cm(cm)
  688. if not silent:
  689. print("=== Main-Class Confusion Matrix ===")
  690. cm_main_arr = np.array([
  691. [metrics_dict["class_0"]["tp"], metrics_dict["class_0"]["fp"]],
  692. [metrics_dict["class_0"]["fn"], metrics_dict["class_0"]["tn"]],
  693. ])
  694. print(cm_main_arr)
  695. print("\n=== Metrics Array (rounded) ===")
  696. print(np.round(metrics_array, 3))
  697. # ---- Save CM ----
  698. cm_filename="cm"
  699. cm_filepath = os.path.join(save_dir, cm_filename)
  700. np.savetxt(cm_filepath+".txt", cm, fmt="%d")
  701. # ---- Save metrics ----
  702. metrics_path = os.path.join(save_dir, "cm_metrics")
  703. save_metric_array_to_csv(metrics_array, metrics_path)
  704. # ---- Plot & save CM ----
  705. plot_cm_recall_dual(cm, mode=mode) # assumes it draws on current figure
  706. parent_folder = os.path.basename(os.path.dirname(json_filepath))
  707. base_filename = f"cm_recall_dual_{parent_folder}_{base_name}"
  708. pdf_path = os.path.join(save_dir, base_filename + ".pdf")
  709. png_path = os.path.join(save_dir, base_filename + ".png")
  710. # Save both formats
  711. plt.savefig(pdf_path, format="pdf", dpi=600, bbox_inches="tight")
  712. plt.savefig(png_path, format="png", dpi=600, bbox_inches="tight")
  713. if not silent:
  714. plt.show() # or plt.close() if generating multiple figures
  715. print(f"Saved metrics to: {metrics_path}")
  716. print(f"Saved confusion matrix PDF to: {pdf_path}")
  717. return cm, metrics_array, metrics_dict
  718. def TEST_full_evaluate_on_json():
  719. mode = "v1.5"
  720. #RGB test
  721. inference_path = r"D:\GIT_tree\Askivit 1.5 2ter Datensatz mit korrigierter Datamap\Vier Sensor Modelle\Models\RGB\predictions_250407_163150_tested.json"
  722. full_evaluate_on_json(inference_path,mode=mode)
  723. def run_evaluations_on_4S_for_one_run(parent_path, mode=None):
  724. """
  725. For each expected model subfolder (EF/IR/NIR/RGB/THz):
  726. - find the newest 'predictions*.json' file
  727. - call full_evaluate_on_json on it (if found)
  728. Parameters
  729. ----------
  730. parent_path : str
  731. Path containing the subdirectories EF, IR, NIR, RGB, THz.
  732. mode : optional
  733. Optional argument forwarded to full_evaluate_on_json.
  734. """
  735. subfolder_names = ["EF", "IR", "NIR", "RGB", "THz"]
  736. subdirs = [os.path.join(parent_path, n) for n in subfolder_names]
  737. for d in subdirs:
  738. if not os.path.isdir(d):
  739. print(f"[SKIP] Not a directory or missing: {d}")
  740. continue
  741. # find predictions*.json; pick the most recently modified
  742. candidates = glob.glob(os.path.join(d, "predictions*.json"))
  743. if not candidates:
  744. print(f"[SKIP] No predictions*.json found in: {d}")
  745. continue
  746. newest = max(candidates, key=os.path.getmtime)
  747. print(f"[RUN ] Evaluating predictions file: {newest}")
  748. full_evaluate_on_json(newest, mode=mode)
  749. return subdirs
  750. def Batch_run_evaluations_on_4S_for_one_run():
  751. """
  752. Use THIS to run the base modells. You have to change the 1ter bis 4ter path on your own and run it 4 times to create
  753. all plots for the base models
  754. Wrapper for run_evaluations_on_4S_for_one_run
  755. using the Askivit 1.5 dataset results path.
  756. """
  757. base_path = r"D:\GIT_tree\ba_wagner\06_results\Askivit 1.5 4ter Datensatz mit korrigierter Datamap"
  758. parent_path = os.path.join(base_path, "Vier Sensor Modelle", "Models")
  759. Mode=r'v1.5'
  760. print(f"[TEST] Running evaluations in:\n{parent_path}\n")
  761. run_evaluations_on_4S_for_one_run(parent_path, mode=Mode)
  762. if __name__ == "__main__":
  763. #TEST_generate_prediction_dict()
  764. #TEST_full_evaluate_on_json()
  765. #TEST_run_evaluations_on_4S_for_one_run()
  766. Batch_run_evaluations_on_4S_for_one_run()

evalutils.py at commit ea65400, under CC0-1.0 · at the source

Overview

Authors: Manuel Bihler1, Lukas Roming2, Dovilė Čibiraitė-Lukenskienė3, Jochen Aderhold4, Andreas Keil3, Friedrich Schlüter4, Robin Gruna2, Michael Heizmann1
  1. Karlsruhe Institute of Technology, Institute of Industrial Information Technology (IIIT), Karlsruhe, Germany
  2. Fraunhofer Institute of Optronics, System Technologies and Image Exploitation (IOSB), Karlsruhe, Germany
  3. Fraunhofer Institute for Industrial Mathematics (ITWM), Kaiserslautern, Germany
  4. Fraunhofer Institute for Wood Research, Wilhelm-Klauditz-Institut (WKI), Braunschweig, Germany
Journal: Scientific data, volume 13, issue 1, article 498
Dates: received 4 June 2025; accepted 9 March 2026; published online 27 March 2026
Type: Data paper · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41597-026-07053-1 · PMID 41896252 · PMCID PMC13035887 · OpenAlex W7141379162
Open access: gold, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Categories: methods / tools (subfield)
Methods: Connectivity, Machine learning
Keywords: Electrical and electronic engineering, Imaging and sensing, Scientific data, Information technology, Sustainability
Topic: Advanced Neural Network Applications (Computer Vision and Pattern Recognition, Computer Science), according to OpenAlex
Funding: Bundesministerium für Ernährung und Landwirtschaft (2220HV048B)
Citations: cited by 1 paper (Europe PMC); 30 references in the paper

Abstract

This study presents an annotated multi-sensor, multimodal, and hyperspectral dataset designed to support deep learning-based classification and segmentation of bulky waste. The dataset comprises four distinct sensor modalities: high-resolution visible RGB images (VIS), hyperspectral near-infrared (NIR), temporally resolved thermal infrared (IR), and terahertz (THz) imaging with depth information, providing complementary multimodal information. An image registration process aligns all modalities to a common reference frame, enabling near pixel-precise fusion across sensors. WoodVIT contains 56 registered multi-sensor scenes, partitioned into 22,659 annotated patches with two main classes (wood and non-wood) and 16 subclass labels. It includes pixel-masks and patch-wise annotations to facilitate both segmentation and classification tasks. The primary benchmark task is binary discrimination of wood versus non-wood. The dataset also includes challenging scenarios involving occlusion and concealed contaminants (e.g., embedded metals) to motivate robust multimodal fusion approaches. We provide predefined train/validation/test splits and report baseline results using convolutional neural networks and fusion architectures to establish reference performance. WoodVIT is publicly available to support research on multi-sensor learning for waste sorting.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 5 matches between paragraphs and lines of code.

ASKIVIT-Project/WoodVIT-V1-training

License: CC0-1.0
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: ea65400a53192120b98ac8dac1ceb221c82711e4, 29 January 2026
Languages: Python (12)
Size: 28 files, 12 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: PyTorch (8 files), NumPy (6 files), Matplotlib (3 files)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
14 files

Code availability

Registration and data processing were implemented in Python using Spectral (version 0.23.1), Datashader, and OpenCV (version 4.10.0). Model training and evaluation for the baselines reported in this work were carried out in PyTorch30 (version 2.1.2) with CUDA (version 12.1). We provide the baseline training and evaluation scripts, example configurations, and selected model checkpoints at: https://github.com/ASKIVIT-Project/WoodVIT-V1-training.

Reproduced under the paper's license (CC BY), from the paper cited above.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 12 scripts, each with its path and the digest of its content;
  • 5 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data availability

Both versions of the WoodVIT dataset are publicly available via the RADAR4KIT repository: the raw, unregistered multi-sensor recordings (10.35097/yhanb6twk8t9pqku) and the registered, annotated patch dataset used in this work (10.35097/aj4ve1c03pkan0dr). The data are released under the Creative Commons Attribution 4.0 International License (CC BY 4.0).

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 8 authors, 5 keywords, 1 funder, 16 references.

Cite

This paper

Bihler, M., Roming, L., Čibiraitė-Lukenskienė, D., Aderhold, J., Keil, A., Schlüter, F., Gruna, R., & Heizmann, M. (2026). Multimodal and Hyperspectral Dataset for Segmentation of Bulky Waste using VIS, IR, NIR, and Terahertz Imaging. Scientific data, 13(1), 498. https://doi.org/10.1038/s41597-026-07053-1

BibTeX

@article{bihler2026multimodal,
author = {Bihler, Manuel and Roming, Lukas and Čibiraitė-Lukenskienė, Dovilė and Aderhold, Jochen and Keil, Andreas and Schlüter, Friedrich and Gruna, Robin and Heizmann, Michael},
title = {{Multimodal and Hyperspectral Dataset for Segmentation of Bulky Waste using VIS, IR, NIR, and Terahertz Imaging}},
journal = {Scientific data},
year = {2026},
month = mar,
volume = {13},
number = {1},
pages = {498},
publisher = {Nature Publishing Group},
issn = {2052-4463},
doi = {10.1038/s41597-026-07053-1},
url = {https://doi.org/10.1038/s41597-026-07053-1},
pmid = {41896252},
pmcid = {PMC13035887}
}

RIS

TY - JOUR
AU - Bihler, Manuel
AU - Roming, Lukas
AU - Čibiraitė-Lukenskienė, Dovilė
AU - Aderhold, Jochen
AU - Keil, Andreas
AU - Schlüter, Friedrich
AU - Gruna, Robin
AU - Heizmann, Michael
TI - Multimodal and Hyperspectral Dataset for Segmentation of Bulky Waste using VIS, IR, NIR, and Terahertz Imaging
T2 - Scientific data
J2 - Sci Data
PY - 2026
DA - 2026/03/27
VL - 13
IS - 1
SP - 498
SN - 2052-4463
PB - Nature Publishing Group
DO - 10.1038/s41597-026-07053-1
UR - https://doi.org/10.1038/s41597-026-07053-1
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41597-026-07053-1",
"type": "article-journal",
"title": "Multimodal and Hyperspectral Dataset for Segmentation of Bulky Waste using VIS, IR, NIR, and Terahertz Imaging",
"container-title": "Scientific data",
"author": [
{
"family": "Bihler",
"given": "Manuel"
},
{
"family": "Roming",
"given": "Lukas"
},
{
"family": "Čibiraitė-Lukenskienė",
"given": "Dovilė"
},
{
"family": "Aderhold",
"given": "Jochen"
},
{
"family": "Keil",
"given": "Andreas"
},
{
"family": "Schlüter",
"given": "Friedrich"
},
{
"family": "Gruna",
"given": "Robin"
},
{
"family": "Heizmann",
"given": "Michael"
}
],
"container-title-short": "Sci Data",
"volume": "13",
"issue": "1",
"page": "498",
"DOI": "10.1038/s41597-026-07053-1",
"PMID": "41896252",
"PMCID": "PMC13035887",
"ISSN": "2052-4463",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41597-026-07053-1",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
27
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1021/acsomega.5c04016 [code]
Active Stacking-Deep Learning with Strategic Sampling for Small and Imbalanced Chemical Toxicity Prediction.
Journal: ACS omega
In common: Matplotlib, NumPy, 2 references
[2] doi:10.1016/j.nicl.2023.103482 [code]
An extended clinical EEG dataset with 15,300 automatically labelled recordings for pathology decoding.
Journal: NeuroImage. Clinical
In common: PyTorch, Matplotlib, NumPy, 1 reference
[3] doi:10.1038/s41598-026-61801-2 [code]
Detection of early-stage Parkinson's disease using wearable sensors at multiple body locations and convolutional neural networks.
Journal: Scientific reports
In common: PyTorch, Matplotlib, NumPy, 1 reference
[4] doi:10.1038/s41746-026-02651-0 [code]
AI Augmented Confocal Laser Endomicroscopy for Rapid Intraoperative Diagnosis of Brain Tumors.
Journal: NPJ digital medicine
In common: PyTorch, Matplotlib, NumPy, 1 reference
[5] doi:10.3389/frai.2025.1731062 [code]
Deep multimodal learning for domain-level cognitive decline prediction in Alzheimer's disease.
Journal: Frontiers in artificial intelligence
In common: PyTorch, Matplotlib, NumPy, 1 reference
[6] doi:10.1038/s41746-024-01416-x [code]
Scaling convolutional neural networks achieves expert level seizure detection in neonatal EEG.
Journal: NPJ digital medicine
In common: PyTorch, Matplotlib, NumPy, 1 reference
[7] doi:10.1093/bib/bbae673 [code]
Explainable deep neural networks for predicting sample phenotypes from single-cell transcriptomics.
Journal: Briefings in bioinformatics
In common: PyTorch, Matplotlib, NumPy, 1 reference
[8] doi:10.1177/20552076261437428 [code]
Developing a clinical decision support tool for stratifying stroke risk in patients presenting with dizziness to the emergency department: A retrospective cohort study.
Journal: Digital health
In common: PyTorch, Matplotlib, NumPy, 1 reference
[9] doi:10.1038/s41598-025-90881-9 [code]
Improving ALS detection and cognitive impairment stratification with attention-enhanced deep learning models.
Journal: Scientific reports
In common: PyTorch, Matplotlib, NumPy, 1 reference
[10] doi:10.1038/s41598-024-73428-2 [code]
Length-scale study in deep learning prediction for non-small cell lung cancer brain metastasis.
Journal: Scientific reports
In common: PyTorch, Matplotlib, NumPy, 1 reference

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Add the badge to the code's README

OSCR: the badge of this paper

If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.

Markdown
<!-- oscr-badge:start -->
[![OSCR](https://openscicode.org/badge/doi/10.1038/s41597-026-07053-1.svg)](https://openscicode.org/badge/doi/10.1038/s41597-026-07053-1)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.1038/s41597-026-07053-1.svg
   :target: https://openscicode.org/badge/doi/10.1038/s41597-026-07053-1
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1038/s41597-026-07053-1"><img src="https://openscicode.org/badge/doi/10.1038/s41597-026-07053-1.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->

On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".

If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.

Report a mismatch between the code and the paper · Report a code error (signed in)

Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.

Report a reproduction failure (signed in)

Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.