Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
# CHANGELOG


## v0.9.3 (2026-03-18)

### Bug Fixes

- Refactor hierarchical_heatmap_template and align test with template changes
([`459aa95`](https://github.com/FNLCR-DMAP/SCSAWorkflow/commit/459aa95100922f16d1ebd58205a874882bc3f421))

- Update template to return (clustergrid, mean_intensity) tuple for in-memory mode - Fix param names
in test: Feature_s_, Standard_Scale_ - Remove unused params: Method, Metric - Add all template
params to test for full coverage - Add in-memory mode test (save_results_flag=False) - Add figure
title and CSV output assertions


## v0.9.2 (2026-03-03)

### Continuous Integration

- **version**: Automatic development release
([`97ead81`](https://github.com/FNLCR-DMAP/SCSAWorkflow/commit/97ead819ef09c101a6cc59af7e98f979548abcb6))


## v0.9.1 (2026-02-27)

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='spac',
version="0.9.2",
version="0.9.3",
description=(
'SPatial Analysis for single-Cell analysis (SPAC)'
'is a Scalable Python package for single-cell spatial protein data '
Expand Down
2 changes: 1 addition & 1 deletion src/spac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
functions.extend(module_functions)

# Define the package version before using it in __all__
__version__ = "0.9.2"
__version__ = "0.9.3"

# Define a __all__ list to specify which functions should be considered public
__all__ = functions
18 changes: 11 additions & 7 deletions src/spac/templates/hierarchical_heatmap_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,20 @@ def run_from_json(
json_path : str, Path, or dict
Path to JSON file, JSON string, or parameter dictionary
save_results_flag : bool, optional
Whether to save results to file. If False, returns the figure and
dataframe directly for in-memory workflows. Default is True.
Whether to save results to file. If False, returns a tuple of
(clustergrid_figure, mean_intensity_dataframe) for in-memory
workflows (e.g., Shiny server). Default is True.
show_plot : bool, optional
Whether to display the plot. Default is True.
output_dir : str or Path, optional
Directory for outputs. If None, uses params['Output_Directory'] or '.'

Returns
-------
dict or DataFrame
dict or tuple
If save_results_flag=True: Dictionary of saved file paths
If save_results_flag=False: The mean intensity dataframe
If save_results_flag=False: Tuple of (figure, mean_intensity_df)
where figure is the matplotlib Figure from the ClusterGrid
"""
# Parse parameters from JSON
params = parse_params(json_path)
Expand Down Expand Up @@ -185,9 +187,11 @@ def run_from_json(
print("Hierarchical Heatmap completed successfully.")
return saved_files
else:
# Return the dataframe directly for in-memory workflows
print("Returning mean intensity dataframe (not saving to file)")
return mean_intensity
# Return the ClusterGrid and dataframe for in-memory workflows
# Returns ClusterGrid (not .fig) so consumers can access
# .ax_heatmap for post-processing (e.g. label rotation).
print("Returning (clustergrid, mean_intensity_df) for in-memory use")
return clustergrid, mean_intensity


# CLI interface
Expand Down
94 changes: 82 additions & 12 deletions tests/templates/test_hierarchical_heatmap_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
"""
Real (non-mocked) unit test for the Hierarchical Heatmap template.

Validates template I/O behaviour only.
Snowball seed test — validates template I/O behaviour only:
• Expected output files are produced on disk
• Filenames follow the convention
• Output artifacts are non-empty

No mocking. Uses real data, real filesystem, and tempfile.
"""

Expand All @@ -15,7 +19,7 @@
from pathlib import Path

import matplotlib
matplotlib.use("Agg")
matplotlib.use("Agg") # Headless backend for CI

import anndata as ad
import numpy as np
Expand Down Expand Up @@ -53,14 +57,24 @@ def setUp(self) -> None:
"Upstream_Analysis": self.in_file,
"Annotation": "cell_type",
"Table_to_Visualize": "Original",
"Features_to_Visualize": ["All"],
"Standard_Scale": "None",
"Method": "average",
"Metric": "euclidean",
"Feature_s_": ["All"],
"Standard_Scale_": "None",
"Z_Score": "None",
"Feature_Dendrogram": True,
"Annotation_Dendrogram": True,
"Figure_Title": "Test Hierarchical Heatmap",
"Figure_Width": 6,
"Figure_Height": 4,
"Figure_DPI": 72,
"Font_Size": 8,
"Matrix_Plot_Ratio": 0.8,
"Swap_Axes": False,
"Rotate_Label_": False,
"Horizontal_Dendrogram_Display_Ratio": 0.2,
"Vertical_Dendrogram_Display_Ratio": 0.2,
"Value_Min": "None",
"Value_Max": "None",
"Color_Map": "seismic",
"Output_Directory": self.tmp_dir.name,
"outputs": {
"figures": {"type": "directory", "name": "figures_dir"},
Expand All @@ -82,24 +96,80 @@ def test_hierarchical_heatmap_produces_expected_outputs(self) -> None:
Validates:
1. saved_files dict has 'figures' and 'dataframe' keys
2. Figures directory contains non-empty PNG(s)
3. Summary CSV exists
3. Summary CSV exists and is non-empty
4. Figure title matches the parameter
"""
# -- Act (save_results_flag=True): write outputs to disk -------
saved_files = run_from_json(
self.json_file,
save_results_flag=True,
show_plot=False,
output_dir=self.tmp_dir.name,
)

self.assertIsInstance(saved_files, dict)
self.assertIn("figures", saved_files)
# -- Act (save_results_flag=False): get objects in memory ------
clustergrid, mean_intensity_df = run_from_json(
self.json_file,
save_results_flag=False,
show_plot=False,
)

# -- Assert: return type ---------------------------------------
self.assertIsInstance(
saved_files, dict,
f"Expected dict from run_from_json, got {type(saved_files)}"
)

# -- Assert: figures directory contains at least one PNG -------
self.assertIn("figures", saved_files,
"Missing 'figures' key in saved_files")
figure_paths = saved_files["figures"]
self.assertGreaterEqual(len(figure_paths), 1)
self.assertGreaterEqual(
len(figure_paths), 1, "No figure files were saved"
)

for fig_path in figure_paths:
fig_file = Path(fig_path)
self.assertTrue(fig_file.exists())
self.assertGreater(fig_file.stat().st_size, 0)
self.assertTrue(
fig_file.exists(), f"Figure not found: {fig_path}"
)
self.assertGreater(
fig_file.stat().st_size, 0,
f"Figure file is empty: {fig_path}"
)
self.assertEqual(
fig_file.suffix, ".png",
f"Expected .png extension, got {fig_file.suffix}"
)

# -- Assert: figure has the correct title ----------------------
axes_title = clustergrid.ax_heatmap.get_title()
self.assertEqual(
axes_title, "Test Hierarchical Heatmap",
f"Expected 'Test Hierarchical Heatmap', got '{axes_title}'"
)

# -- Assert: in-memory mean_intensity_df is a DataFrame --------
self.assertIsInstance(
mean_intensity_df, pd.DataFrame,
f"Expected DataFrame, got {type(mean_intensity_df)}"
)
self.assertIn(
"cell_type", mean_intensity_df.columns,
"Annotation column 'cell_type' missing from mean_intensity_df"
)

# -- Assert: summary CSV exists and is non-empty ---------------
self.assertIn("dataframe", saved_files,
"Missing 'dataframe' key in saved_files")
csv_path = Path(saved_files["dataframe"])
self.assertTrue(
csv_path.exists(), f"Summary CSV not found: {csv_path}"
)
self.assertGreater(
csv_path.stat().st_size, 0,
f"Summary CSV is empty: {csv_path}"
)


if __name__ == "__main__":
Expand Down
Loading