import scomv
import stlearn as st
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import cv2
import pandas as pd
import numpy as np
import anndata
import scanpy as sc
/Users/riuyamas/miniconda3/envs/scomv-pypi-install-2/lib/python3.11/site-packages/numba/core/decorators.py:282: RuntimeWarning: nopython is set for njit and is ignored
  warnings.warn('nopython is set for njit and is ignored', RuntimeWarning)
/Users/riuyamas/miniconda3/envs/scomv-pypi-install-2/lib/python3.11/site-packages/stlearn/tl/cci/het.py:206: NumbaDeprecationWarning: The keyword argument 'nopython=False' was supplied. From Numba 0.59.0 the default is being changed to True and use of 'nopython=False' will raise a warning as the argument will have no effect. See https://numba.readthedocs.io/en/stable/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit for details.
  @jit(parallel=True, nopython=False)
# Download tutorial dataset
#!mkdir tutorial_data
#!wget -P tutorial_data/ https://raw.githubusercontent.com/RyosukeNomural/SpatialCompassV/main/docs/tutorials/tutorial_data/xenium_data/cell_feature_matrix.h5
#!wget -P tutorial_data/ https://raw.githubusercontent.com/RyosukeNomural/SpatialCompassV/main/docs/tutorials/tutorial_data/xenium_data/cells.csv.gz
# Load Xenium data using stlearn
adata = st.ReadXenium(
    feature_cell_matrix_file="tutorial_data/cell_feature_matrix.h5",
    cell_summary_file="tutorial_data/cells.csv.gz",
    library_id="example data",
    image_path=None,
    scale=1,
    spot_diameter_fullres=10
)
Warning: Using default pixel size of 0.2125 microns. Consider providing experiment_xenium_file for accurate pixel size.
# Gridding at 10μm interval using stlearn
N_COL = int((adata.obs.imagecol.max() - adata.obs.imagecol.min()) / 10)
N_ROW = int((adata.obs.imagerow.max() - adata.obs.imagerow.min()) / 10)
grid = st.tl.cci.grid(adata, n_row=N_ROW, n_col=N_COL, n_cpus=10, verbose=False)
from scomv.preparation.skny_calc_distance import calculate_distance
## apply SKNY
grid = calculate_distance(
    grid, pos_marker_ls=['CDH1',"EPCAM"],
)
# Figure 2B
fig, ax = plt.subplots(figsize=(8, 6), dpi=150)
ax.imshow(cv2.cvtColor(grid.uns["marker_median_delineation"], cv2.COLOR_BGR2RGB),
          interpolation="nearest")
ax.axis("off")
plt.show()
../../_images/dbabf6d7b35995f646ca6915588682263042099d722526f6265f40483b2af4ba.png
import plotly.express as px
import plotly.io as pio
import cv2
import numpy as np

# Convert the OpenCV BGR image to RGB for Plotly visualization
img = cv2.cvtColor(grid.uns["marker_median_delineation"], cv2.COLOR_BGR2RGB)

# Minimum coordinates of the original spatial data
x_min = float(adata.obs.imagecol.min())
y_min = float(adata.obs.imagerow.min())

# Grid bin size used when generating the grid image
bin_size = 10

# Create Plotly image figure
fig = px.imshow(img)

h, w = img.shape[:2]

# Convert grid coordinates back to absolute spatial coordinates
xx_abs = (np.arange(w)[None, :] * bin_size + x_min).repeat(h, axis=0)
yy_abs = (np.arange(h)[:, None] * bin_size + y_min).repeat(w, axis=1)

# Round coordinates to the nearest multiple of 10 for display
xx_round10 = np.round(xx_abs / 10) * 10
yy_round10 = np.round(yy_abs / 10) * 10

# Stack the coordinates so they can be accessed in hover data
custom = np.dstack([xx_round10, yy_round10])

# Axis tick configuration
# step = 20 in grid space corresponds to ~200 units in absolute coordinates
step = 20
x_tickvals = list(range(0, w, step))
y_tickvals = list(range(0, h, step))

# Generate axis labels rounded to multiples of 10
x_ticktext = [str(int(round((v * bin_size + x_min) / 10) * 10)) for v in x_tickvals]
y_ticktext = [str(int(round((v * bin_size + y_min) / 10) * 10)) for v in y_tickvals]

fig.update_xaxes(
    tickmode="array",
    tickvals=x_tickvals,
    ticktext=x_ticktext
)

fig.update_yaxes(
    tickmode="array",
    tickvals=y_tickvals,
    ticktext=y_ticktext
)

# Customize hover information to display rounded absolute spatial coordinates
fig.update_traces(
    customdata=custom,
    hovertemplate=
    "imagecol: %{customdata[0]:.0f}<br>"
    "imagerow: %{customdata[1]:.0f}"
    "<extra></extra>"
)

fig.update_layout(
    xaxis_title="imagecol",
    yaxis_title="imagerow",
    height=700
)

# Use the interactive notebook renderer
pio.renderers.default = "notebook_connected"

fig.show()
# annotation each section to obs object
df_shotest = getattr(grid, "shortest")
df_grid = grid.to_df()

# extract grid info
df_grid = pd.merge(
    pd.DataFrame(index=["grid_" + str(i+1) for i in range(N_ROW * N_COL)]),
    df_grid, right_index=True, left_index=True, how="left"
).fillna(np.nan)

# extract section info
df_region = pd.DataFrame(
    np.array(df_shotest["region"]).reshape(N_ROW, N_COL).T.reshape(N_ROW * N_COL),
    index=["grid_" + str(i+1) for i in range(N_ROW * N_COL)], columns=["region"]
)

# marge
df_grid_region = pd.merge(
    df_grid, df_region,
    right_index=True, left_index=True, how="left"
)
df_grid_region = df_grid_region.dropna()

# add to obs
grid.obs = pd.merge(
    grid.obs, df_grid_region[["region"]],
    right_index=True, left_index=True, how="left"
)

# shaping
grid.obs["region_10"] = [str(i*10) for i in grid.obs["region"]]
grid.obs["region_10"] = ["("+str(int(float(i.split(", ")[0][1:])))+", "+str(int(float(i.split(", ")[-1][:-1])))+"]" if i != "nan" else np.nan for i in grid.obs["region_10"]]
# exclude because of small number
grid.obs["region_10"] = grid.obs["region_10"].replace(
    {"(-150, -120]": np.nan}
)
from scomv.preparation.choose_roi import extract_roi, contour_regions
# select ROI
roi = (2400, 3400, 2400, 3800)

subset_grid, filtered_shortest, xy_list = extract_roi(
    grid=grid,
    roi=roi,
    bin_size=10,
    region_col="region_10",
)
g_x_cont, g_y_cont, g_x_inside, g_y_inside = contour_regions(
    filtered_shortest, adata,
    min_x=0, max_x=4000, min_y=0, max_y=4000,
    show=True,
)
../../_images/c9d3fa38c331b8ab17aed3bbf368273d5f77bd134bbd8dc07b3a78cc3d1fa1c0.png
from scomv.preparation.scomv_calc_vector import compute_min_vectors_polar

outline_points = list(zip(g_x_cont, g_y_cont))
inside_points  = list(zip(g_x_inside, g_y_inside))

min_vector_df = compute_min_vectors_polar(
    xy_list=xy_list,
    outline_points=outline_points,
    inside_points=inside_points,
    invert_y=True,
    make_inside_negative=True,
)
# load cell_annotation file
!wget -P tutorial_data/ https://raw.githubusercontent.com/RyosukeNomural/SpatialCompassV/main/docs/tutorials/tutorial_data/Cell_Barcode_Type_Matrices.xlsx
--2026-03-14 12:51:14--  https://raw.githubusercontent.com/RyosukeNomural/SpatialCompassV/main/docs/tutorials/tutorial_data/Cell_Barcode_Type_Matrices.xlsx
gw.east.ncc.go.jp (gw.east.ncc.go.jp) をDNSに問いあわせています... 160.190.222.8
gw.east.ncc.go.jp (gw.east.ncc.go.jp)|160.190.222.8|:8080 に接続しています... 接続しました。
Proxy による接続要求を送信しました、応答を待っています... 200 OK
長さ: 9409803 (9.0M) [application/octet-stream]
`tutorial_data/Cell_Barcode_Type_Matrices.xlsx.1' に保存中

Cell_Barcode_Type_M 100%[===================>]   8.97M  --.-KB/s 時間 0.1s       

2026-03-14 12:51:14 (78.8 MB/s) - `tutorial_data/Cell_Barcode_Type_Matrices.xlsx.1' へ保存完了 [9409803/9409803]
!pip install openpyxl
Collecting openpyxl
  Using cached openpyxl-3.1.5-py2.py3-none-any.whl.metadata (2.5 kB)
Collecting et-xmlfile (from openpyxl)
  Using cached et_xmlfile-2.0.0-py3-none-any.whl.metadata (2.7 kB)
Using cached openpyxl-3.1.5-py2.py3-none-any.whl (250 kB)
Using cached et_xmlfile-2.0.0-py3-none-any.whl (18 kB)
Installing collected packages: et-xmlfile, openpyxl
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2/2 [openpyxl]1/2 [openpyxl]
Successfully installed et-xmlfile-2.0.0 openpyxl-3.1.5
# load cell_annotation file
file_path = "tutorial_data/Cell_Barcode_Type_Matrices.xlsx"
xls = pd.ExcelFile(file_path)
cell_ann_df = pd.read_excel(file_path, sheet_name=xls.sheet_names[3])
print(cell_ann_df.head())

adata_obs = adata.obs
cell_ann_df.index = cell_ann_df.index + 1
adata_obs = adata_obs[["imagecol", "imagerow"]]
adata_obs.index = adata_obs.index.astype(int)


cell_df = pd.concat([adata_obs, cell_ann_df], axis=1)
cell_df = cell_df[["imagecol", "imagerow", "Cluster"]]
cell_df["Cluster"].unique()
#cell_df.to_csv("cell_annotation_df.csv")
   Barcode         Cluster
0        1          DCIS_2
1        2          DCIS_2
2        3       Unlabeled
3        4  Invasive_Tumor
4        5          DCIS_2
array(['DCIS_2', 'Unlabeled', 'Invasive_Tumor', 'Macrophages_1',
       'Stromal', 'DCIS_1', 'Myoepi_ACTA2+', 'CD8+_T_Cells',
       'Endothelial', 'Prolif_Invasive_Tumor', 'T_Cell_&_Tumor_Hybrid',
       'Mast_Cells', 'CD4+_T_Cells', 'B_Cells', 'Macrophages_2',
       'Stromal_&_T_Cell_Hybrid', 'Perivascular-Like', 'LAMP3+_DCs',
       'IRF7+_DCs', 'Myoepi_KRT15+'], dtype=object)
from scomv.cell_pipeline import CellPolarPipeline

# Initialize the pipeline with cell-level data and precomputed minimum-distance vectors
cell_pipe = CellPolarPipeline(
    cell_df=cell_df,
    min_vector_df=min_vector_df
)

# Run the pipeline for a specified ROI (xmin, xmax, ymin, ymax)
# Disable histogram plotting during the run
cell_out = cell_pipe.run(
    roi=(2400, 3400, 2400, 3800),
    plot_hist=False
)

# Distance matrix between cells based on polar-vector representations
dist = cell_pipe.dist_df

# Plot explained variance of PCoA components
cell_pipe.plot_explained_variance(n_components=8)

# Plot a heatmap of similarities (displayed as 1 - distance)
cell_pipe.heatmap(font_size=14, figsize=(8, 8))
../../_images/42d9074db3b91056f900750060f1299fd5435474bcf8bddf28c4e12d9681e84c.png ../../_images/974e246ff3cf2db0b4d457f4e4cbe87c899226d633809d64cc5c110eb9d64350.png
<seaborn.matrix.ClusterGrid at 0x16961df10>
from scomv.cell import compute_cluster_polar_distributions
_ = compute_cluster_polar_distributions(cell_out["cell_df_filtered"], min_vector_df)
../../_images/10d78883e28bae8352eed37cdbd772a525907ab1f2deeb60c73c9bdfe2cab82e.png ../../_images/790c04fd89c67b19f3e10ae40ada4834f3f50cf3e683cc32dddbf656225a9b36.png ../../_images/5e26bbf2be082481d08679d9301ebeaa14c6a0abd9ab119a8b08ce3a72e792d6.png ../../_images/f614eed128d8f5e8ab0546fc7c62f0fcb024588fdfeb87362bf5cefc338bf431.png ../../_images/09d4ccbeed9c7eb9d1558137fbc8c9847dd32a3143c36f4222bbc30f0f2d08b0.png ../../_images/6ff53e5877e1b34712a7032d4a2f8e396b5711e1078bb7d316995d553b5c567d.png ../../_images/58d79dd2cbc522c88054157b4f4e3136e58290d7a20b5b55fb45b3cfbd25046f.png ../../_images/800e326a9f63facb20dffe5d7cd7574b94c2464d5bbe22ad2c924574ee54b698.png ../../_images/8ffec3b7020629851d4a5b0f0eb27a853f9776baea506478525001e1756cf1f3.png ../../_images/e1f9ff001d3f2eebbd35a48b7637f27dbfc832a0352a49f55e7211c7a2980d7c.png ../../_images/05fa0750db629657f7dbe1ca89e2b2aa7ed3721954bd7f125607f08abcfe9687.png
adata_2 = anndata.AnnData(X=np.zeros((len(cell_df), 0)))  # Dummy X
adata_2.obs = cell_df.copy()

# Store the coordinates used as the basis in adata.obsm
adata_2.obsm["spatial"] = cell_df[["imagecol", "imagerow"]].to_numpy()

# draw
sc.pl.embedding(
    adata_2,
    basis="spatial",
    color="Cluster",
    size=80,
    legend_loc=None,
    frameon=True,
    show=False,
)

ax = plt.gca()
ax.set_xlabel("X coordinate", fontsize=15)
ax.set_ylabel("Y coordinate", fontsize=15)
ax.tick_params(axis="both", labelsize=13)

# limit the range
ax.set_xlim(2400, 3400)
ax.set_ylim(2400, 3800)

# Align to the image coordinate system
ax.invert_yaxis()
ax.set_aspect("equal", adjustable="box")

legend = ax.legend(
    *ax.get_legend_handles_labels(),
    loc="center left",
    bbox_to_anchor=(1.02, 0.5),
    frameon=False,
    fontsize=12
)
plt.tight_layout()
plt.show()
/Users/riuyamas/miniconda3/envs/scomv-pypi-install-2/lib/python3.11/site-packages/anndata/_core/anndata.py:859: UserWarning:


AnnData expects .obs.index to contain strings, but got values like:
    [1, 2, 3, 4, 5]

    Inferred to be: integer
../../_images/1ec3b8396b554bc8f5e434dcefadc01a2f1717aa513ce8aaf999de11dc44f7c0.png
sc.pl.embedding(
    adata_2,
    basis="spatial",
    color="Cluster",
    size=80,
    legend_loc="none",
    frameon=True,
    show=False,
)

ax = plt.gca()
ax.set_xlabel("X coordinate", fontsize=15)
ax.set_ylabel("Y coordinate", fontsize=15)
ax.tick_params(axis="both", labelsize=13)
ax.set_xlim(2400, 3400)
ax.set_ylim(2400, 3800)
ax.invert_yaxis()
ax.set_aspect("equal", adjustable="box")

# Retrieve category names and colors stored in adata
cats = adata_2.obs["Cluster"].astype("category").cat.categories
colors = adata_2.uns.get("Cluster_colors")

handles = [mpatches.Patch(color=c, label=str(cat)) for c, cat in zip(colors, cats)]
ax.legend(handles=handles, loc="center left", bbox_to_anchor=(1.02, 0.5), frameon=False, fontsize=12)

plt.subplots_adjust(right=0.80)
plt.tight_layout()
plt.show()
../../_images/f43f0235467850cc5f201745b83415368ca59726f87a6630757adf4deabdc1d6.png
# List of cluster categories
clusters = adata_2.obs["Cluster"].astype("category").cat.categories

# Create a copy of adata and rename DCIS_2 → DCIS
adata_2_renamed = adata_2.copy()
adata_2_renamed.obs["Cluster"] = (
    adata_2_renamed.obs["Cluster"].replace({"DCIS_2": "DCIS"}).astype("category")
)

# Color settings: DCIS in yellow, others keep original colors
orig_cats = adata_2.obs["Cluster"].astype("category").cat.categories
orig_colors = adata_2.uns["Cluster_colors"]
color_dict = dict(zip(orig_cats, orig_colors))

cats = list(adata_2_renamed.obs["Cluster"].cat.categories)
colors = []
for c in cats:
    if c == "DCIS":
        colors.append("#FFD700")
    else:
        orig_name = "DCIS_2" if c == "DCIS" and "DCIS_2" in orig_cats else c
        colors.append(color_dict.get(orig_name, "gray"))

adata_2_renamed.uns["Cluster_colors"] = colors

# Plot "DCIS + each cluster" separately
for cluster in clusters:
    cluster_name = "DCIS" if cluster == "DCIS_2" else cluster

    plt.figure(figsize=(10, 10))
    sc.pl.spatial(
        adata_2_renamed[adata_2_renamed.obs["Cluster"].isin([cluster_name, "DCIS"])],
        img_key="hires",
        color="Cluster",
        size=1.5,
        spot_size=20,
        legend_loc="lower right",
        frameon=True,
        show=False,
    )

    ax = plt.gca()

    # Remove axis titles and labels
    ax.set_title("")
    ax.set_xlabel("")
    ax.set_ylabel("")
    ax.tick_params(axis="both", labelsize=11)

    # Manually construct legend for selected clusters
    handle_map = {cat: col for cat, col in zip(cats, adata_2_renamed.uns["Cluster_colors"])}
    show_labels = [cluster_name, "DCIS"]
    show_labels = list(dict.fromkeys(show_labels))  # Remove duplicates for DCIS plots
    handles = [
        mpatches.Patch(color=handle_map[label], label=label)
        for label in show_labels
    ]
    ax.legend(handles=handles, loc="lower right", frameon=True, fontsize=12)

    ax.set_axis_on()

    # Set ROI limits
    ax.set_xlim(2400, 3400)
    # ax.set_xticks(range(2400, 3401, 200))
    ax.set_ylim(2400, 3800)
    # ax.set_yticks(range(2400, 3801, 200))

    # Align to image coordinate system
    ax.invert_yaxis()
    ax.set_aspect("equal", adjustable="box")

    # Save figure (optional)
    # plt.savefig(f"{root}/cell_location_fig/{cluster_name}_no_tick.png", dpi=300)

    plt.show()
    print(f"Figure: {cluster_name} + DCIS")
/Users/riuyamas/miniconda3/envs/scomv-pypi-install-2/lib/python3.11/site-packages/anndata/_core/anndata.py:183: ImplicitModificationWarning:

Transforming to str index.

/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:7: FutureWarning:

The behavior of Series.replace (and DataFrame.replace) with CategoricalDtype is deprecated. In a future version, replace will only be used for cases that preserve the categories. To change the categories, use ser.cat.rename_categories instead.

/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/f8b8168039f3825062a3d47772e55eb129ca01c24a1bf7f98810809c98d4c54b.png
Figure: B_Cells + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/7994d223128ea34503f3687b8c7f058bf956b92be96839a32f91b0289cd9b3d1.png
Figure: CD4+_T_Cells + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/7c66ba7ddae765a559190f3049beed41238bf2d071faa467f3b53a95e9ebf18e.png
Figure: CD8+_T_Cells + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/7e74734a7bcd7ffc6c3c77b1c78ea642e2eda5b3f7978989cef3018970a4641e.png
Figure: DCIS_1 + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/d797ceb704496fc039498808e0c2d7175b5993b170e05fca520f067320c2a2e2.png
Figure: DCIS + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/3d350d1dc44fdf93eaf7c0c5e81ee6b0e3a95a38d762198d4c1d7e15e801d386.png
Figure: Endothelial + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/6bae660d0ddecd25f406f6177362f1e3c23feb9ad912bf099c8b8d6499909e99.png
Figure: IRF7+_DCs + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/c85a0910f46c59e82d59adbc46f5f2cd9c71b4fe3b75fa4b81c061ddb01409ff.png
Figure: Invasive_Tumor + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/7ac8e37c8d7cc37ef46dc05a61cc319d3193223f8e5087d0407e16720d1d3f02.png
Figure: LAMP3+_DCs + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/f07d6a07bd096295698f894904ef73c5b99deff9045549ab8a056f7ec9c8e7dd.png
Figure: Macrophages_1 + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/99753493304aac0ca29f0724ce645d3a9f341d49bd11b50a06de469928db36ae.png
Figure: Macrophages_2 + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/edac13e90b4e0c0317cbcf1cddda93063743db86304cf8674ef157611bca23d9.png
Figure: Mast_Cells + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/9f72cde51727131b6cfaaaa1fd22f9749769276d06e8fa2b1e55b57d2fb0bb03.png
Figure: Myoepi_ACTA2+ + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/e28a9ce70221499d7f481eaa099f30d401810d71582566028b88bc51577c1c33.png
Figure: Myoepi_KRT15+ + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/723724e4d440aaf230e29f65829bcd082490da78d3e606a8ff4246202a81d7b7.png
Figure: Perivascular-Like + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/f2e4269a819f2a0f9f09a5dd53d19eb2a12f271cc1fbf2df1d6919d081980532.png
Figure: Prolif_Invasive_Tumor + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/fc9993f311e87bb1a5568e4142ff35a7b37eefdb55f2dbf5e4113fa62b4e53b7.png
Figure: Stromal + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/2023c1688d4782e9db3385e3bc9788d763b9bb6edb1e973a992bf3fd1cf2b1de.png
Figure: Stromal_&_T_Cell_Hybrid + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/12f42eb7efdadcaf01d56b4e32cd85fd4865be462372824cea1628eb454bb40f.png
Figure: T_Cell_&_Tumor_Hybrid + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/3277719802.py:31: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/4b4e28b264cbf42a7a8ddcd94eb8bd24e7e7b535c95f2a6bf9b3621f2b70b299.png
Figure: Unlabeled + DCIS
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import scanpy as sc
import anndata

# ----------------------------
# 0) 事前: Clusterの欠損を落とす(任意だが安全)
# ----------------------------
adata_2 = adata_2.copy()
adata_2.obs_names = adata_2.obs_names.astype(str) 
adata_2_clean = adata_2[~adata_2.obs["Cluster"].isna()].copy()

# ----------------------------
# 1) DCIS_1, DCIS_2 → DCIS に統合
# ----------------------------
adata_2_renamed = adata_2_clean.copy()

# 末尾スペースなどの事故を避けたい場合(任意)
adata_2_renamed.obs["Cluster"] = adata_2_renamed.obs["Cluster"].astype(str).str.strip()

adata_2_renamed.obs["Cluster"] = (
    adata_2_renamed.obs["Cluster"]
    .replace({"DCIS_1": "DCIS", "DCIS_2": "DCIS"})
    .astype("category")
)

# ----------------------------
# 2) 色設定: DCISを黄色、他は元の色を引き継ぐ
# ----------------------------
orig = adata_2_clean.copy()
orig.obs["Cluster"] = orig.obs["Cluster"].astype(str).str.strip().astype("category")

orig_cats = list(orig.obs["Cluster"].cat.categories)

# orig_colorsが無い/長さが合わない時の保険(scanpyに自動生成させる)
if "Cluster_colors" not in orig.uns or len(orig.uns["Cluster_colors"]) != len(orig_cats):
    tmp = orig.copy()
    tmp.uns.pop("Cluster_colors", None)
    sc.pl.embedding(tmp, basis="spatial", color="Cluster", show=False)
    plt.close()
    orig_colors = tmp.uns["Cluster_colors"]
else:
    orig_colors = orig.uns["Cluster_colors"]

color_dict = dict(zip(orig_cats, orig_colors))

# 統合後カテゴリ順に色リストを作る
cats = list(adata_2_renamed.obs["Cluster"].cat.categories)
colors = []
for c in cats:
    if c == "DCIS":
        colors.append("#FFD700")  # DCISは黄色固定
    else:
        # 統合されていないクラスタはそのまま対応
        colors.append(color_dict.get(c, "gray"))

adata_2_renamed.uns["Cluster_colors"] = colors

# ----------------------------
# 3) "DCIS + 各クラスタ" を統合後カテゴリで回す
#    (DCIS自身は除外)
# ----------------------------
clusters = [c for c in cats if c != "DCIS"]

for cluster in clusters:
    plt.figure(figsize=(10, 10))

    sub = adata_2_renamed[adata_2_renamed.obs["Cluster"].isin([cluster, "DCIS"])].copy()

    sc.pl.spatial(
        sub,
        img_key="hires",
        color="Cluster",
        size=1.5,
        spot_size=20,
        legend_loc=None,   # いったん消して手動legend
        frameon=True,
        show=False,
    )

    ax = plt.gca()

    # タイトル・軸ラベルを消す
    ax.set_title("")
    ax.set_xlabel("")
    ax.set_ylabel("")
    ax.tick_params(axis="both", labelsize=11)

    # 手動legend(その図に出ている2つだけ)
    handle_map = dict(zip(cats, adata_2_renamed.uns["Cluster_colors"]))
    show_labels = [cluster, "DCIS"]
    handles = [mpatches.Patch(color=handle_map[l], label=l) for l in show_labels]
    ax.legend(handles=handles, loc="lower right", frameon=True, fontsize=12)

    ax.set_axis_on()

    # ROI
    ax.set_xlim(2400, 3400)
    ax.set_ylim(2400, 3800)

        # ROI
    xmin, xmax = 2400, 3400
    ymin, ymax = 2400, 3800
    
    ax.set_xlim(xmin, xmax)
    ax.set_ylim(ymin, ymax)
    
    # 200 px ごとに目盛りを出す例
    ax.set_xticks(np.arange(xmin, xmax + 1, 200))
    ax.set_yticks(np.arange(ymin, ymax + 1, 200))
    
    ax.tick_params(axis="both", labelsize=11)


    # 画像座標系
    ax.invert_yaxis()
    ax.set_aspect("equal", adjustable="box")

    plt.tight_layout()
    plt.show()
    print(f"Figure: {cluster} + DCIS")
/Users/riuyamas/miniconda3/envs/scomv-pypi-install-2/lib/python3.11/site-packages/anndata/_core/anndata.py:183: ImplicitModificationWarning:

Transforming to str index.

/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/1292577244.py:71: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.
<Figure size 1000x1000 with 0 Axes>
../../_images/dc68bf9d466e4c22a57b7b65a3be76eb44978f5c3e25019519ce0488ca8dccf6.png
Figure: B_Cells + DCIS
/var/folders/rp/843_v26x35314skd4rnkz6jm0000gp/T/ipykernel_32438/1292577244.py:71: FutureWarning:

Use `squidpy.pl.spatial_scatter` instead.