# HG changeset patch # User goeckslab # Date 1757371115 0 # Node ID b0d893d04d4c6292a67c41691e6e1f189e17922b # Parent 9e912fce264c5fcc762fa665fc24e97438ff2b3b planemo upload for repository https://github.com/goeckslab/gleam.git commit 1594d503179f28987720594eb49b48a15486f073 diff -r 9e912fce264c -r b0d893d04d4c image_learner_cli.py --- a/image_learner_cli.py Wed Aug 27 21:02:48 2025 +0000 +++ b/image_learner_cli.py Mon Sep 08 22:38:35 2025 +0000 @@ -69,7 +69,6 @@ ] rows = [] - for key in display_keys: val = config.get(key, None) if key == "threshold": @@ -136,15 +135,15 @@ val_str = val else: val_str = val if val is not None else "N/A" - if val_str == "N/A" and key not in [ - "task_type" - ]: # Skip if N/A for non-essential + if val_str == "N/A" and key not in ["task_type"]: continue rows.append( f"" - f"" + f"" f"{key.replace('_', ' ').title()}" - f"" + f"" f"{val_str}" f"" ) @@ -153,13 +152,17 @@ types = [str(a.get("type", "")) for a in aug_cfg] aug_val = ", ".join(types) rows.append( - f"Augmentation" - f"{aug_val}" + f"Augmentation" + f"{aug_val}" ) if split_info: rows.append( - f"Data Split" - f"{split_info}" + f"Data Split" + f"{split_info}" ) html = f"""

Model and Training Summary

@@ -946,6 +949,66 @@ test_viz_dir = base_viz_dir / "test" html = get_html_template() + + # Extra CSS & JS: center Plotly and enable CSV download for predictions table + html += """ + + +""" html += f"

{title}

" metrics_html = "" @@ -983,31 +1046,38 @@ except Exception as e: logger.warning(f"Could not load config for HTML report: {e}") + # ---------- image rendering with exclusions ---------- def render_img_section( - title: str, dir_path: Path, output_type: str = None + title: str, + dir_path: Path, + output_type: str = None, + exclude_names: Optional[set] = None, ) -> str: if not dir_path.exists(): return f"

{title}

Directory not found.

" - # collect every PNG + + exclude_names = exclude_names or set() + imgs = list(dir_path.glob("*.png")) - # --- EXCLUDE Ludwig's base confusion matrix and any top-N confusion_matrix files --- + + default_exclude = {"confusion_matrix.png", "roc_curves.png"} + imgs = [ img for img in imgs - if not ( - img.name == "confusion_matrix.png" - or img.name.startswith("confusion_matrix__label_top") - or img.name == "roc_curves.png" - ) + if img.name not in default_exclude + and img.name not in exclude_names + and not img.name.startswith("confusion_matrix__label_top") ] + if not imgs: return f"

{title}

No plots found.

" + if output_type == "binary": order = [ "roc_curves_from_prediction_statistics.png", "compare_performance_label.png", "confusion_matrix_entropy__label_top2.png", - # ...you can tweak ordering as needed ] img_names = {img.name: img for img in imgs} ordered = [img_names[n] for n in order if n in img_names] @@ -1019,14 +1089,13 @@ "compare_classifiers_multiclass_multimetric__label_top10.png", "compare_classifiers_multiclass_multimetric__label_worst10.png", } + valid_imgs = [img for img in imgs if img.name not in unwanted] display_order = [ "roc_curves.png", "compare_performance_label.png", "compare_classifiers_performance_from_prob.png", "confusion_matrix_entropy__label_top10.png", ] - # filter and order - valid_imgs = [img for img in imgs if img.name not in unwanted] img_map = {img.name: img for img in valid_imgs} ordered = [img_map[n] for n in display_order if n in img_map] others = sorted( @@ -1034,27 +1103,36 @@ ) imgs = ordered + others else: - # regression: just sort whatever's left imgs = sorted(imgs) - # render each remaining PNG - html = "" + + html_section = "" for img in imgs: b64 = encode_image_to_base64(str(img)) img_title = img.stem.replace("_", " ").title() - html += ( + html_section += ( f"

{img_title}

" f'
' f'' f"
" ) - return html + return html_section tab1_content = config_html + metrics_html + tab2_content = train_val_metrics_html + render_img_section( - "Training and Validation Visualizations", train_viz_dir + "Training and Validation Visualizations", + train_viz_dir, + output_type, + exclude_names={ + "compare_classifiers_performance_from_prob.png", + "roc_curves_from_prediction_statistics.png", + "precision_recall_curves_from_prediction_statistics.png", + "precision_recall_curve.png", + }, ) - # --- Predictions vs Ground Truth table --- + + # --- Predictions vs Ground Truth table (REGRESSION ONLY) --- preds_section = "" parquet_path = exp_dir / PREDICTIONS_PARQUET_FILE_NAME if output_type == "regression" and parquet_path.exists(): @@ -1081,13 +1159,19 @@ preds_html = df_table.to_html(index=False, classes="predictions-table") preds_section = ( "

Ground Truth vs. Predictions

" - "
" + "
" + "" + "
" + "
" + preds_html + "
" ) except Exception as e: logger.warning(f"Could not build Predictions vs GT table: {e}") + tab3_content = test_metrics_html + preds_section + + # Classification-only interactive Plotly panels (centered) if output_type in ("binary", "category"): training_stats_path = exp_dir / "training_statistics.json" interactive_plots = build_classification_plots( @@ -1095,31 +1179,16 @@ str(training_stats_path), ) for plot in interactive_plots: - # 2) inject the static "roc_curves_from_prediction_statistics.png" - if plot["title"] == "ROC-AUC": - static_img = ( - test_viz_dir / "roc_curves_from_prediction_statistics.png" - ) - if static_img.exists(): - b64 = encode_image_to_base64(str(static_img)) - tab3_content += ( - "

" - "Roc Curves From Prediction Statistics" - "

" - f'
' - f'' - "
" - ) - # always render the plotly panels exactly as before tab3_content += ( f"

{plot['title']}

" - + plot["html"] + f"
{plot['html']}
" ) - tab3_content += render_img_section( - "Test Visualizations", test_viz_dir, output_type - ) - # assemble the tabs and help modal + + # Add static TEST PNGs (with default dedupe/exclusions) + tab3_content += render_img_section( + "Test Visualizations", test_viz_dir, output_type + ) + tabbed_html = build_tabbed_html(tab1_content, tab2_content, tab3_content) modal_html = get_metrics_help_modal() html += tabbed_html + modal_html + get_html_closing() diff -r 9e912fce264c -r b0d893d04d4c utils.py --- a/utils.py Wed Aug 27 21:02:48 2025 +0000 +++ b/utils.py Mon Sep 08 22:38:35 2025 +0000 @@ -3,205 +3,301 @@ def get_html_template(): + """ + Returns the opening HTML, (with CSS/JS), and opens + .container. + Includes: + - Base styling for layout and tables + - Sortable table headers with 3-state arrows (none ⇅, asc ↑, desc ↓) + - A scroll helper class (.scroll-rows-30) that approximates ~30 visible rows + - A guarded script so initializing runs only once even if injected twice + """ return """ - - - - Galaxy-Ludwig Report - + /* Tabs + Help button (used by build_tabbed_html) */ + .tabs { + display: flex; + align-items: center; + border-bottom: 2px solid #ccc; + margin-bottom: 1rem; + gap: 6px; + flex-wrap: wrap; + } + .tab { + padding: 10px 20px; + cursor: pointer; + border: 1px solid #ccc; + border-bottom: none; + background: #f9f9f9; + margin-right: 5px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + } + .tab.active { + background: white; + font-weight: bold; + } + .help-btn { + margin-left: auto; + padding: 6px 12px; + font-size: 0.9rem; + border: 1px solid #4CAF50; + border-radius: 4px; + background: #4CAF50; + color: white; + cursor: pointer; + } + .tab-content { + display: none; + padding: 20px; + border: 1px solid #ccc; + border-top: none; + background: #fff; + } + .tab-content.active { + display: block; + } - - - - -
- """ + // Run after DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initPerfSummarySorting); + } else { + initPerfSummarySorting(); + } + })(); + + + +
+""" def get_html_closing(): + """Closes .container, body, and html.""" return """ -
- - - """ +
+ + +""" -def encode_image_to_base64(image_path): +def encode_image_to_base64(image_path: str) -> str: """Convert an image file to a base64 encoded string.""" with open(image_path, "rb") as img_file: return base64.b64encode(img_file.read()).decode("utf-8") -def json_to_nested_html_table(json_data, depth=0): +def json_to_nested_html_table(json_data, depth: int = 0) -> str: """ - Convert JSON object to an HTML nested table. - - Parameters: - json_data (dict or list): The JSON data to convert. - depth (int): Current depth level for indentation. - - Returns: - str: HTML string for the nested table. + Convert a JSON-able object to an HTML nested table. + Renders dicts as two-column tables (key/value) and lists as index/value rows. """ - # Base case: if JSON is a simple key-value pair dictionary + # Base case: flat dict (no nested dict/list values) if isinstance(json_data, dict) and all( not isinstance(v, (dict, list)) for v in json_data.values() ): - # Render a flat table rows = [ f"{key}{value}" for key, value in json_data.items() ] return f"{''.join(rows)}
" - # Base case: if JSON is a list of simple values + # Base case: list of simple values if isinstance(json_data, list) and all( not isinstance(v, (dict, list)) for v in json_data ): @@ -211,36 +307,34 @@ ] return f"{''.join(rows)}
" - # Recursive case: if JSON contains nested structures + # Recursive cases if isinstance(json_data, dict): rows = [ - f"{key}" - f"{json_to_nested_html_table(value, depth + 1)}" + ( + f"{key}" + f"{json_to_nested_html_table(value, depth + 1)}" + ) for key, value in json_data.items() ] return f"{''.join(rows)}
" if isinstance(json_data, list): rows = [ - f"[{i}]" - f"{json_to_nested_html_table(value, depth + 1)}" + ( + f"[{i}]" + f"{json_to_nested_html_table(value, depth + 1)}" + ) for i, value in enumerate(json_data) ] return f"{''.join(rows)}
" - # Base case: simple value + # Primitive return f"{json_data}" -def json_to_html_table(json_data): +def json_to_html_table(json_data) -> str: """ - Convert JSON to a vertically oriented HTML table. - - Parameters: - json_data (str or dict): JSON string or dictionary. - - Returns: - str: HTML table representation. + Convert JSON (dict or string) into a vertically oriented HTML table. """ if isinstance(json_data, str): json_data = json.loads(json_data) @@ -248,56 +342,19 @@ def build_tabbed_html(metrics_html: str, train_val_html: str, test_html: str) -> str: + """ + Build a 3-tab interface: + - Config and Results Summary + - Train/Validation Results + - Test Results + Includes a persistent "Help" button that toggles the metrics modal. + """ return f""" - -
Config and Results Summary
Train/Validation Results
Test Results
- - +
@@ -311,17 +368,26 @@
""" def get_metrics_help_modal() -> str: + """ + Returns a ready-to-use modal with a comprehensive metrics guide and + the small script that wires the "Help" button to open/close the modal. + """ modal_html = ( '" ) - modal_css = ( - "" - ) + modal_js = ( "" ) - return modal_css + modal_html + modal_js + return modal_html + modal_js