| name | niche-detection |
| title | Spatial Niche Detection |
| slug | niche-detection |
| description | Identify spatial cellular niches using Harmonics hierarchical model. Detects microenvironments based on cell type composition and assigns niche labels to cells. |
| filter_requirements | {"modalities":["gene"],"data_levels":["cell"]} |
| prerequisites | ["Cell type annotations in target slice (adata.obs['celltype'])"] |
| default_skill | true |
Spatial Niche Detection Using Harmonics Model
Identify and characterize spatial cellular niches (microenvironments) in your spatial transcriptomics data. This skill uses the Harmonics hierarchical model to detect tissue niches based on cell type composition and spatial proximity patterns.
Workflow
Copy and execute this complete workflow (all 7 stages):
from niche_analysis_lib.model import Harmonics_Model
slice_id = 0
slice_obj = session.get_slice(slice_id)
adata = slice_obj.adata.copy()
slice_name = f"slice{slice_id}"
assert 'x' in adata.obs.columns and 'y' in adata.obs.columns, "Missing spatial coordinates"
assert 'celltype' in adata.obs.columns, "Missing celltype annotations"
adata.obsm['spatial'] = adata.obs[['x', 'y']].to_numpy()
adata_list = [adata]
slice_name_list = [slice_name]
print(f"✓ Stage 1: Data prepared ({adata.n_obs} cells, {adata.obs['celltype'].nunique()} types)")
model = Harmonics_Model(
adata_list,
slice_name_list,
concat_label='slice_name',
seed=1234,
parallel=True,
verbose=True
)
print(f"✓ Stage 2: Model initialized")
model.preprocess(
ct_key='celltype',
spatial_key='spatial',
method='joint',
n_step=3,
n_neighbors=20,
cut_percentage=99
)
print(f"✓ Stage 3: Preprocessing complete")
model.initialize_clusters(
dim_reduction=True,
explained_var=None,
n_components=None,
n_components_max=100,
standardize=True,
method='kmeans',
Qmax=20
)
print(f"✓ Stage 4: Clusters initialized")
model.hier_dist_match(
assign_metric='jsd',
weighted_merge=True,
max_iters=100,
tol=1e-4,
test_kmeans=False
)
print(f"✓ Stage 5: Hierarchical refinement complete")
adata_list, _ = model.select_solution(
n_niche=None,
niche_key='niche_label',
auto=True,
metric='jsd',
threshold=0.1,
return_adata=True,
plot=True,
save=False
)
print(f"✓ Stage 6: Solution selected")
adata_result = adata_list[0]
slice_obj.adata.obs['niche_label'] = adata_result.obs['niche_label'].values
n_niches = slice_obj.adata.obs['niche_label'].nunique()
print(f"✓ Stage 7: Niche detection complete!")
print(f"✓ Found {n_niches} distinct niches")
print(f"✓ Added 'niche_label' column to slice_obj.adata.obs")
print(f"\nNiche distribution:")
print(slice_obj.adata.obs['niche_label'].value_counts().sort_index())
Visualization
Visualize Niche Spatial Distribution
import matplotlib.pyplot as plt
import seaborn as sns
n_niches = adata.obs['niche_label'].nunique()
palette = sns.color_palette('tab10', n_niches)
fig, ax = plt.subplots(figsize=(12, 10))
for niche_id in sorted(adata.obs['niche_label'].unique()):
mask = adata.obs['niche_label'] == niche_id
ax.scatter(
adata.obs.loc[mask, 'x'],
adata.obs.loc[mask, 'y'],
c=[palette[int(niche_id)]],
s=1,
alpha=0.8,
label=f'Niche {niche_id}'
)
ax.set_xlabel('X coordinate')
ax.set_ylabel('Y coordinate')
ax.set_title('Spatial Niche Distribution')
ax.legend(markerscale=5, loc='upper right', framealpha=0.9)
plt.tight_layout()
plt.show()