diff --git a/conda-scimap.yaml b/conda-scimap.yaml new file mode 100644 index 0000000..58c4dd0 --- /dev/null +++ b/conda-scimap.yaml @@ -0,0 +1,43 @@ +# Setup: +# mamba env create -f conda-scimap.yaml +# mamba activate spatialcells-scimap +# pip install --no-deps . +# pip install --no-deps scimap==2.3.6 smart_open + +# scimap pins numba 0.58 / llvmlite 0.41 / scipy 1.12 / pandas <2.2 / numpy <2, +# which strictly forces the rest of the stack to certain older versions captured here. + +name: spatialcells-scimap + +channels: + - conda-forge + +dependencies: + - python >=3.10,<3.12 + - pip + - ipykernel + - nbconvert + - nbclient + + # Core runtime (pinned to scimap-compatible versions) + - shapely >=2.0 + - numpy <2 + - pandas <2.2 + - scipy =1.12.* + - scikit-learn >=1.6 + - matplotlib <3.9 + - seaborn + - tqdm + - leidenalg + - scanpy <1.11 + - anndata <0.11 + + # Optional: for joint analyses with other packages (as was done in the tutorials) + - squidpy =1.4.* + - plotly + - numba =0.58.* + - llvmlite =0.41.* + - dask >=2023.11,<2024 + - dask-image <2024 + - zarr >=2.11,<3 + - setuptools <81 diff --git a/conda.yaml b/conda.yaml index d0bc246..81b4016 100644 --- a/conda.yaml +++ b/conda.yaml @@ -1,19 +1,35 @@ -name: spatial-cells-env +# Setup: +# mamba env create -f conda.yaml +# mamba activate spatialcells +# pip install --no-deps . + +# Use conda-scimap.yaml instead if you need scimap in the same env as well. + +name: spatialcells channels: - conda-forge - - defaults dependencies: - - python>=3.7, <3.11 + - python >=3.10,<3.13 + - pip - ipykernel - - matplotlib>=3.7.0 - - pandas>=2.0.3 - - seaborn>=0.12.2 - - shapely>=2.0 + - nbconvert + - nbclient + + # Core runtime + - shapely >=2.0 + - numpy + - pandas + - scipy + - scikit-learn + - matplotlib + - seaborn - tqdm - - pip - - pip: - - anndata==0.9.2 - - scanpy==1.9.4 - \ No newline at end of file + - leidenalg + - scanpy + - anndata + + # Optional: for joint analyses with other packages (as was done in the tutorials) + - squidpy + - plotly diff --git a/spatialcells/__init__.py b/spatialcells/__init__.py index 248828e..10eb3a5 100644 --- a/spatialcells/__init__.py +++ b/spatialcells/__init__.py @@ -1,8 +1,8 @@ -from pkg_resources import get_distribution, DistributionNotFound +from importlib.metadata import version, PackageNotFoundError try: - __version__ = get_distribution("spatialcells").version -except DistributionNotFound: + __version__ = version("spatialcells") +except PackageNotFoundError: __version__ = "(local)" diff --git a/spatialcells/measurements/_getSlidingWindowsComposition.py b/spatialcells/measurements/_getSlidingWindowsComposition.py index 7e63bf7..31764c4 100644 --- a/spatialcells/measurements/_getSlidingWindowsComposition.py +++ b/spatialcells/measurements/_getSlidingWindowsComposition.py @@ -1,3 +1,4 @@ +import anndata as ad import numpy as np import pandas as pd @@ -26,33 +27,55 @@ def getSlidingWindowsComposition( :returns: A dataframe containing the cell type composition of the region in each window """ if region_subset is None: - cells_roi = adata + obs = adata.obs else: - cells_roi = adata[adata.obs[region_col].isin(region_subset)] - cells_roi_maxx = int(cells_roi.obs["X_centroid"].max()) - cells_roi_maxy = int(cells_roi.obs["Y_centroid"].max()) - cells_roi_minx = int(cells_roi.obs["X_centroid"].min()) - cells_roi_miny = int(cells_roi.obs["Y_centroid"].min()) - all_windows_comp_df = [] - for x in range(cells_roi_minx, cells_roi_maxx + window_size, step_size): - for y in range(cells_roi_miny, cells_roi_maxy + window_size, step_size): - cells_roi_window = cells_roi[ - (cells_roi.obs["X_centroid"] >= x) - & (cells_roi.obs["X_centroid"] < x + window_size) - & (cells_roi.obs["Y_centroid"] >= y) - & (cells_roi.obs["Y_centroid"] < y + window_size) - ] - if cells_roi_window.shape[0] > min_cells: - cells_roi_window_composition = getRegionComposition( - cells_roi_window, phenotype_col - ) - cells_roi_window_composition["X_start"] = x - cells_roi_window_composition["Y_start"] = y - cells_roi_window_composition["window_size"] = window_size - cells_roi_window_composition["step_size"] = step_size - all_windows_comp_df.append(cells_roi_window_composition) - all_windows_comp_df = pd.concat(all_windows_comp_df) - return all_windows_comp_df + obs = adata.obs[adata.obs[region_col].isin(region_subset)] + obs = obs.dropna(subset=["X_centroid", "Y_centroid"]) + if obs.empty: + return pd.DataFrame() + + minx = int(obs["X_centroid"].min()) + miny = int(obs["Y_centroid"].min()) + maxx = int(obs["X_centroid"].max()) + maxy = int(obs["Y_centroid"].max()) + + def composition_for(window_obs, x, y): + comp = getRegionComposition( + ad.AnnData(obs=window_obs), phenotype_col, regioncol=region_col + ) + comp["X_start"] = x + comp["Y_start"] = y + return comp + + rows = [] + if window_size == step_size: + # speed up: each cell falls into exactly one window, so bucket once + # and group, rather than iterating all (x, y) pairs + x_bin = ((obs["X_centroid"] - minx) // step_size * step_size + minx).astype(int) + y_bin = ((obs["Y_centroid"] - miny) // step_size * step_size + miny).astype(int) + for (x, y), window_obs in obs.assign(_X_bin=x_bin, _Y_bin=y_bin).groupby( + ["_X_bin", "_Y_bin"], observed=True + ): + if window_obs.shape[0] > min_cells: + rows.append(composition_for(window_obs, int(x), int(y))) + else: + for x in range(minx, maxx + window_size, step_size): + for y in range(miny, maxy + window_size, step_size): + window_obs = obs[ + (obs["X_centroid"] >= x) + & (obs["X_centroid"] < x + window_size) + & (obs["Y_centroid"] >= y) + & (obs["Y_centroid"] < y + window_size) + ] + if window_obs.shape[0] > min_cells: + rows.append(composition_for(window_obs, x, y)) + + if not rows: + return pd.DataFrame() + out = pd.concat(rows) + out["window_size"] = window_size + out["step_size"] = step_size + return out def get_comp_mask(df, pheno_col, pheno_vals, step_size): diff --git a/spatialcells/spatial/_getBoundary.py b/spatialcells/spatial/_getBoundary.py index 6fdb705..b02ec53 100644 --- a/spatialcells/spatial/_getBoundary.py +++ b/spatialcells/spatial/_getBoundary.py @@ -1,3 +1,4 @@ +import numpy as np import shapely from shapely.geometry import Polygon, MultiPolygon from shapely.validation import make_valid @@ -29,19 +30,22 @@ def getBoundary(anndata, communitycolumn, communityIndexList, alpha=100, debug=F xy = anndata.obs[anndata.obs[communitycolumn].isin(communityIndexList)][ ["X_centroid", "Y_centroid"] ].to_numpy() + # slightly nudge the input points so they lie strictly inside. + eps = max(float(np.ptp(xy, axis=0).max()), 1.0) * 1e-9 edge_components = getAlphaShapes(xy, alpha) polygons = [Polygon(edge[:, 0, :]).buffer(0) for edge in edge_components] boundary = MultiPolygon(polygons) - # make sure the boundary is valid and points of interest are inside - boundary = make_valid(boundary).buffer(1) + boundary = make_valid(boundary).buffer(eps) if boundary.geom_type == "Polygon": boundary = MultiPolygon([boundary]) + new_boundary = [] for poly in boundary.geoms: holes = poly.interiors hole_polygons = [Polygon(hole) for hole in holes] - # prevent holes from touching the boundary - hole_limit = Polygon(poly.exterior).buffer(-1) + # shrink the limit by eps avoid the edge case of holes (internal polygons) + # being exactly tangential to the exterior, leading to an invalid polygon. + hole_limit = Polygon(poly.exterior).buffer(-eps) hole_multipoly = shapely.unary_union(hole_polygons) & hole_limit if hole_multipoly.geom_type == "Polygon": hole_multipoly = MultiPolygon([hole_multipoly]) @@ -49,6 +53,8 @@ def getBoundary(anndata, communitycolumn, communityIndexList, alpha=100, debug=F new_poly = Polygon(poly.exterior, new_holes_polygons) new_boundary.append(new_poly) boundary = make_valid(MultiPolygon(new_boundary)) + if boundary.geom_type == "Polygon": + boundary = MultiPolygon([boundary]) if debug: return boundary, polygons, edge_components diff --git a/spatialcells/spatial/_utils.py b/spatialcells/spatial/_utils.py index 3e130c4..f433fed 100644 --- a/spatialcells/spatial/_utils.py +++ b/spatialcells/spatial/_utils.py @@ -6,6 +6,50 @@ from collections import defaultdict +def getAlphaShapes(points, alpha, debug=False): + """ + Compute the alpha shape of a set of points. + + :param points: np.array of shape (n,2) points. + :param alpha: alpha value. + :returns: set of (i,j) point pairs representing edges of the alpha-shape. + """ + assert points.shape[0] > 3, "Need at least four points" + + # triangulate all points + tri = Delaunay(points) + + pa = points[tri.simplices[:, 0]] + pb = points[tri.simplices[:, 1]] + pc = points[tri.simplices[:, 2]] + a = np.sqrt((pa[:, 0] - pb[:, 0]) ** 2 + (pa[:, 1] - pb[:, 1]) ** 2) + b = np.sqrt((pb[:, 0] - pc[:, 0]) ** 2 + (pb[:, 1] - pc[:, 1]) ** 2) + c = np.sqrt((pc[:, 0] - pa[:, 0]) ** 2 + (pc[:, 1] - pa[:, 1]) ** 2) + s = (a + b + c) / 2.0 + area = np.sqrt(s * (s - a) * (s - b) * (s - c)) + # Computing radius of triangle circumcircle + # www.mathalino.com/reviewer/derivation-of-formulas/derivation-of-formula-for-radius-of-circumcircle + circum_r = a * b * c / (4.0 * area + 1e-10) + verts = tri.simplices[circum_r < alpha] + + # get edges that only appear once + edges = _getUniqueEdges( + np.concatenate([verts[:, [0, 1]], verts[:, [1, 2]], verts[:, [0, 2]]], axis=0) + ) + # order all edges + edge_component_indices = _getOrderedEdgeComponents(edges) + if debug: + print("edge_component_indices:", len(edge_component_indices)) + + # points + edge_components = [] + for component in edge_component_indices: + edge_components.append(points[component]) + if debug: + print(component.shape) + return edge_components + + def _bfsGetShortestRing(edge_dict, start_point): """ Given a set of edges, return the shortest ring starting from start_point. @@ -118,50 +162,6 @@ def _pruneTouchingComponents(edge_dict): return new_edge_dict, components -def getAlphaShapes(points, alpha, debug=False): - """ - Compute the alpha shape of a set of points. - - :param points: np.array of shape (n,2) points. - :param alpha: alpha value. - :returns: set of (i,j) point pairs representing edges of the alpha-shape. - """ - assert points.shape[0] > 3, "Need at least four points" - - # triangulate all points - tri = Delaunay(points) - - pa = points[tri.simplices[:, 0]] - pb = points[tri.simplices[:, 1]] - pc = points[tri.simplices[:, 2]] - a = np.sqrt((pa[:, 0] - pb[:, 0]) ** 2 + (pa[:, 1] - pb[:, 1]) ** 2) - b = np.sqrt((pb[:, 0] - pc[:, 0]) ** 2 + (pb[:, 1] - pc[:, 1]) ** 2) - c = np.sqrt((pc[:, 0] - pa[:, 0]) ** 2 + (pc[:, 1] - pa[:, 1]) ** 2) - s = (a + b + c) / 2.0 - area = np.sqrt(s * (s - a) * (s - b) * (s - c)) - # Computing radius of triangle circumcircle - # www.mathalino.com/reviewer/derivation-of-formulas/derivation-of-formula-for-radius-of-circumcircle - circum_r = a * b * c / (4.0 * area + 1e-10) - verts = tri.simplices[circum_r < alpha] - - # get edges that only appear once - edges = _getUniqueEdges( - np.concatenate([verts[:, [0, 1]], verts[:, [1, 2]], verts[:, [0, 2]]], axis=0) - ) - # order all edges - edge_component_indices = _getOrderedEdgeComponents(edges) - if debug: - print("edge_component_indices:", len(edge_component_indices)) - - # points - edge_components = [] - for component in edge_component_indices: - edge_components.append(points[component]) - if debug: - print(component.shape) - return edge_components - - def getComponents(boundary, keep_holes=True): """ Get the components of a boundary defined by a MultiPolygon. @@ -222,226 +222,4 @@ def pruneSmallComponents( holes_to_keep.append(hole) if geom.area >= min_area and len(geom.exterior.coords) - 1 >= min_edges: polygons.append(Polygon(geom.exterior.coords, holes_to_keep)) - return MultiPolygon(polygons) - - -# def getEdgesOnBoundaries(boundaries): -# """ """ -# points = [] -# for boundary_set in boundaries: -# for compt in boundary_set: -# points.append(compt) -# return np.concatenate(points) - - -# def groupRemoveEdgeComponents(edge_components, nedges_min, nedges_out_min): -# edge_components.sort(key=lambda x: len(x), reverse=True) -# new_edge_components = [] -# for comp in edge_components: -# is_in_comp = -1 -# for i, prev_comp in enumerate(new_edge_components): -# # print(comp[0][0]) -# if isInRegion(comp[0][0], prev_comp[0]): -# is_in_comp = i -# # print(i, comp.shape) -# break -# if is_in_comp != -1 and len(comp) >= nedges_min: -# new_edge_components[is_in_comp].append(comp) -# elif is_in_comp == -1 and (len(comp) >= nedges_out_min): -# new_edge_components.append([comp]) -# return new_edge_components - -# def isInRegion(point, boundary, debug=False): -# """ -# Check whether "point" is within the boundary. -# """ - -# count = 0 -# larger_y2_count = 0 -# smaller_y2_count = 0 - -# # point = np.around(point, decimals=3) -# point = np.array(point) - -# # print(point, point.shape, boundary, len(boundary)) -# for pi, pj in boundary: -# # pi = np.around(pi, decimals=3) -# # pj = np.around(pj, decimals=3) - -# # if point is on the edge points, return True -# if (point[0] == pi[0] and point[1] == pi[1]) or ( -# point[0] == pj[0] and point[1] == pj[1] -# ): -# return True - -# # one of x values must be bigger than the point.x -# # ignore other edges -# if pi[0] >= point[0] or pj[0] >= point[0]: -# cond1 = (pi[1] >= point[1]) and (pj[1] <= point[1]) -# cond2 = (pi[1] <= point[1]) and (pj[1] >= point[1]) -# if cond1 or cond2: -# # if debug: print("dealing", pi, pj) - -# # if point and edge are on a horizantal line -# if (point[1] == pi[1]) and (point[1] == pj[1]): -# count += ret - -# # handles edge case of touching line segments / segments on a straight line -# # if either x == point.x, let this point be x1. -# elif pi[1] == point[1] or pj[1] == point[1]: -# if debug: -# print("touching") -# if (pi[1] + pj[1]) / 2 > point[1]: # x2 larger -# larger_y2_count += 1 -# elif (pi[1] + pj[1]) / 2 < point[1]: # x2 smaller -# smaller_y2_count += 1 - -# # # check if line segment intersects with point, (0, point.y). if so count += 1 -# # # only check if the line segment is on the left side of the point and point between the two y values -# # if (pi[1] < point[1]) != (pj[1] < point[1]) and (pi[0] < point[0] or pj[0] < point[0]): -# else: -# ret = isIntersect(point, (max(pi[0], pj[0]), point[1]), pi, pj) -# if debug: -# print("isIntersect", ret, pi, pj) -# # if debug:print(point, (0, point[1])) -# # if debug:print(pi, pj) -# count += ret - -# # if debug: print("before:", count) -# count += (larger_y2_count % 2) and (smaller_y2_count % 2) -# if debug: -# print( -# "count:", -# count, -# "larger_y2_count", -# larger_y2_count, -# "smaller_y2_count", -# smaller_y2_count, -# ) - -# return (count % 2) != 0 - - -# def getPolygons(boundaries): -# polygons = [] -# for boundary_set in boundaries: -# external_boundaries = set(np.arange(len(boundary_set))) -# internal_boundaries = defaultdict(list) -# for i in range(len(boundary_set)): -# for j in range(i + 1, len(boundary_set)): -# pointi = boundary_set[i][:, 0, :] -# pointj = boundary_set[j][:, 0, :] -# polygoni = Polygon(pointi) -# polygonj = Polygon(pointj) -# if polygoni.contains(polygonj): -# internal_boundaries[i].append(pointj) -# external_boundaries.discard(j) -# elif polygonj.contains(polygoni): -# internal_boundaries[j].append(pointi) -# external_boundaries.discard(i) -# for external_boundary_idx in external_boundaries: -# outer_points = boundary_set[external_boundary_idx][:, 0, :] -# inner_points = internal_boundaries[external_boundary_idx] -# polygon = Polygon(outer_points, inner_points) -# polygons.append(polygon) -# return polygons - - -# def isCounterClockwise(A, B, C): -# # if ABC is counterclockwise, then slope of AB less than AC -# return (C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0]) - - -# def isIntersect(p1, p2, p3, p4): -# """ -# Checkswhether the line segment p1-p2 intersects with p3-p4, -# assuming no colinear points -# """ -# return isCounterClockwise(p1, p3, p4) != isCounterClockwise( -# p2, p3, p4 -# ) and isCounterClockwise(p1, p2, p3) != isCounterClockwise(p1, p2, p4) - - -# def PointsInCircum(eachPoint, r, n=100): -# """ -# Return n points within r distance from eachPoint -# """ -# return [ -# ( -# eachPoint[0] + math.cos(2 * math.pi / n * x) * r, -# eachPoint[1] + math.sin(2 * math.pi / n * x) * r, -# ) -# for x in range(0, n + 1) -# ] - - -# def bufferPoints(inPoints, stretchCoef, n=100): -# """ -# Return n*len(inPoints) points that are within r distance of each point. -# """ -# newPoints = [] -# for eachPoint in inPoints: -# newPoints += PointsInCircum(eachPoint, stretchCoef, n) -# # newPoints.append(np.array(PointsInCircum(eachPoint, stretchCoef, n))) -# newPoints = np.array(newPoints) - -# return newPoints - - -# def hasEdge(point, step, polygons): -# grid_edges = Polygon( -# [ -# (point[0], point[1]), -# (point[0] + step, point[1]), -# (point[0], point[1] + step), -# (point[0] + step, point[1] + step), -# ] -# ).boundary -# for polygon in polygons: -# if polygon.boundary.intersects(grid_edges): -# return True -# return False - - -# def getBufferedBoundary(boundaries, offset=200, minsize=20): -# """ """ - -# buffered_boundaries = [] -# outer_multipolygon = Polygon() -# inner_multipolygon = Polygon() -# for boundary_set in boundaries: -# for i, boundary in enumerate(boundary_set): -# boundary_points = boundary[:, 0, :] -# boundary_polygon = Polygon(boundary_points) -# if i == 0: -# outer_multipolygon = outer_multipolygon | boundary_polygon.buffer( -# offset -# ) -# else: -# inner_multipolygon = inner_multipolygon | boundary_polygon.buffer( -# -offset -# ) - -# s_boundary_polygons = outer_multipolygon - inner_multipolygon - -# if isinstance(s_boundary_polygons, MultiPolygon): -# s_boundary_polygons = s_boundary_polygons.geoms -# else: -# s_boundary_polygons = [s_boundary_polygons] - -# for s_boundary_polygon in s_boundary_polygons: -# for ring in [s_boundary_polygon.exterior] + list(s_boundary_polygon.interiors): -# buffered_points = np.array(ring.coords) -# if buffered_points.shape[0] < minsize: -# continue -# buffered_edges = np.stack( -# [np.roll(buffered_points, -1, axis=0), buffered_points], axis=1 -# ) -# buffered_boundaries.append(buffered_edges) - -# # comp_polygons = getPolygons([buffered_boundaries]) - -# # return [buffered_boundaries], comp_polygons - -# buffered_boundaries_edge = getEdgesOnBoundaries([buffered_boundaries]) -# return buffered_boundaries_edge, [buffered_boundaries] + return MultiPolygon(polygons) \ No newline at end of file diff --git a/tutorials/1_tutorial_measurements.ipynb b/tutorials/1_tutorial_measurements.ipynb index 97eebc5..c0eb4f4 100644 --- a/tutorials/1_tutorial_measurements.ipynb +++ b/tutorials/1_tutorial_measurements.ipynb @@ -59,7 +59,7 @@ "metadata": {}, "outputs": [], "source": [ - "adata = ad.read_h5ad(\"../../data/MEL1_adata.h5ad\")" + "adata = ad.read_h5ad(\"../data/MEL1_adata.h5ad\")" ] }, { @@ -629,7 +629,8 @@ " adata, [metric_col, pheno], regions=[\"Tumor\"]\n", ").sort_values(by=metric_col, ascending=False)\n", "\n", - "sns.barplot(data=df, y=metric_col, x=\"composition\", hue=pheno, log=True, ax=ax2)\n", + "sns.barplot(data=df, y=metric_col, x=\"composition\", hue=pheno, ax=ax2)\n", + "ax2.set_xscale(\"log\")\n", "ax2.legend(bbox_to_anchor=(1.02, 1), loc=\"upper left\", borderaxespad=0)\n", "plt.tight_layout()\n", "plt.show()" @@ -682,7 +683,8 @@ "ax1.invert_yaxis()\n", "ax1.legend(loc=\"upper right\", title=metric_col)\n", "\n", - "sns.barplot(data=df, y=metric_col, x=\"composition\", hue=pheno, log=True, ax=ax2)\n", + "sns.barplot(data=df, y=metric_col, x=\"composition\", hue=pheno, ax=ax2)\n", + "ax2.set_xscale(\"log\")\n", "ax2.legend(bbox_to_anchor=(1.02, 1), loc=\"upper left\", borderaxespad=0)\n", "\n", "plt.tight_layout()\n", @@ -735,7 +737,8 @@ " adata, [metric_col, pheno], regions=[\"Tumor\"]\n", ").sort_values(by=metric_col, ascending=False)\n", "\n", - "sns.barplot(data=df, y=metric_col, x=\"composition\", hue=pheno, log=True, ax=ax2)\n", + "sns.barplot(data=df, y=metric_col, x=\"composition\", hue=pheno, ax=ax2)\n", + "ax2.set_xscale(\"log\")\n", "ax2.legend(bbox_to_anchor=(1.02, 1), loc=\"upper left\", borderaxespad=0)\n", "plt.tight_layout()\n", "plt.show()" @@ -787,7 +790,8 @@ " adata, [metric_col, pheno], regions=[\"Tumor\"]\n", ").sort_values(by=metric_col, ascending=False)\n", "\n", - "sns.barplot(data=df, y=metric_col, x=\"composition\", hue=pheno, log=True, ax=ax2)\n", + "sns.barplot(data=df, y=metric_col, x=\"composition\", hue=pheno, ax=ax2)\n", + "ax2.set_xscale(\"log\")\n", "ax2.legend(bbox_to_anchor=(1.02, 1), loc=\"upper left\", borderaxespad=0)\n", "plt.tight_layout()\n", "plt.show()" @@ -1025,7 +1029,8 @@ " adata, [metric_col, pheno], regions=[\"Tumor\"]\n", ").sort_values(by=metric_col, ascending=False)\n", "\n", - "sns.barplot(data=df, y=metric_col, x=\"composition\", hue=pheno, log=True, ax=ax2)\n", + "sns.barplot(data=df, y=metric_col, x=\"composition\", hue=pheno, ax=ax2)\n", + "ax2.set_xscale(\"log\")\n", "ax2.legend(bbox_to_anchor=(1.02, 1), loc=\"upper left\", borderaxespad=0)\n", "plt.tight_layout()\n", "plt.show()" @@ -1080,7 +1085,8 @@ " adata, [metric_col, pheno], regions=[\"Tumor\"]\n", ").sort_values(by=metric_col, ascending=False)\n", "\n", - "sns.barplot(data=df, y=metric_col, x=\"composition\", hue=pheno, log=True, ax=ax2)\n", + "sns.barplot(data=df, y=metric_col, x=\"composition\", hue=pheno, ax=ax2)\n", + "ax2.set_xscale(\"log\")\n", "ax2.legend(bbox_to_anchor=(1.02, 1), loc=\"upper left\", borderaxespad=0)\n", "plt.tight_layout()\n", "plt.show()" @@ -1296,7 +1302,7 @@ " \n", " \n", "\n", - "

1605 rows × 7 columns

\n", + "

1605 rows \u00d7 7 columns

\n", "" ], "text/plain": [ diff --git a/tutorials/2_tutorial_tumor_proliferation.ipynb b/tutorials/2_tutorial_tumor_proliferation.ipynb index b46f64e..2293c08 100644 --- a/tutorials/2_tutorial_tumor_proliferation.ipynb +++ b/tutorials/2_tutorial_tumor_proliferation.ipynb @@ -56,7 +56,7 @@ } ], "source": [ - "adata = ad.read_h5ad(\"../../data/MEL1_adata.h5ad\")\n", + "adata = ad.read_h5ad(\"../data/MEL1_adata.h5ad\")\n", "spc.prep.setGate(adata, \"SOX10_cellRingMask\", 7.9, debug=True)\n", "spc.prep.setGate(adata, \"KERATIN_cellRingMask\", 6.4, debug=True)" ] @@ -161,7 +161,7 @@ { "data": { "text/plain": [ - "" ] @@ -937,7 +937,7 @@ " \n", " \n", "\n", - "

1997 rows × 10 columns

\n", + "

1997 rows \u00d7 10 columns

\n", "" ], "text/plain": [ diff --git a/tutorials/3_tutorial_immune_infiltration_overlap.ipynb b/tutorials/3_tutorial_immune_infiltration_overlap.ipynb index 55f6bbf..fc86aee 100644 --- a/tutorials/3_tutorial_immune_infiltration_overlap.ipynb +++ b/tutorials/3_tutorial_immune_infiltration_overlap.ipynb @@ -59,7 +59,7 @@ } ], "source": [ - "adata = ad.read_h5ad(\"../../data/MEL1_adata.h5ad\")\n", + "adata = ad.read_h5ad(\"../data/MEL1_adata.h5ad\")\n", "spc.prep.setGate(adata, \"KERATIN_cellRingMask\", 6.4, debug=True)\n", "spc.prep.setGate(adata, \"SOX10_cellRingMask\", 7.9, debug=True)\n", "spc.prep.setGate(adata, \"CD3D_cellRingMask\", 7, debug=True)" diff --git a/tutorials/4_tutorial_macro_micro_regions.ipynb b/tutorials/4_tutorial_macro_micro_regions.ipynb index 3cabf9c..6832e66 100644 --- a/tutorials/4_tutorial_macro_micro_regions.ipynb +++ b/tutorials/4_tutorial_macro_micro_regions.ipynb @@ -44,7 +44,7 @@ "metadata": {}, "outputs": [], "source": [ - "adata = ad.read_h5ad(\"../../data/MEL1_adata.h5ad\")\n", + "adata = ad.read_h5ad(\"../data/MEL1_adata.h5ad\")\n", "adata.obs[\"id\"] = adata.obs_names" ] }, @@ -3077,7 +3077,7 @@ " \n", " \n", "\n", - "

5 rows × 53 columns

\n", + "

5 rows \u00d7 53 columns

\n", "" ], "text/plain": [ @@ -3490,7 +3490,7 @@ " \n", " \n", "\n", - "

378 rows × 5 columns

\n", + "

378 rows \u00d7 5 columns

\n", "" ], "text/plain": [ diff --git a/tutorials/5_tutorial_import_export_omero.ipynb b/tutorials/5_tutorial_import_export_omero.ipynb index 9c17ac9..6317e9f 100644 --- a/tutorials/5_tutorial_import_export_omero.ipynb +++ b/tutorials/5_tutorial_import_export_omero.ipynb @@ -49,7 +49,7 @@ "outputs": [], "source": [ "# Load data\n", - "adata = ad.read(\"../../data/Z147_1_750.h5ad\")" + "adata = ad.read_h5ad(\"../data/Z147_1_750.h5ad\")" ] }, { @@ -311,7 +311,7 @@ { "data": { "text/plain": [ - "AnnData object with n_obs × n_vars = 1110585 × 30\n", + "AnnData object with n_obs \u00d7 n_vars = 1110585 \u00d7 30\n", " obs: 'X_centroid', 'Y_centroid', 'column_centroid', 'row_centroid', 'Area', 'MajorAxisLength', 'MinorAxisLength', 'Eccentricity', 'Solidity', 'Extent', 'Orientation', 'imageid', 'phenotype', 'kmeans', 'kmeans_renamed', 'pickseq_roi', 'pickseq_roi_minimal', 'dermis_roi', 'epidermis_roi', 'general_roi', 'Kmeans_r', 'der_epider_roi', 'phenotype_proliferation', 'phenograph', 'spatial_expression_phenograph', 'spatial_expression_kmeans', 'phenograph_raw', 'phenograph_raw_minimal', 'phenotype_1', 'phenotype_1_tumor_kmeans', 'phenotype_1_tumor_kmeans_renamed', 'phenotype_final', 'spatial_expression_consolidated', 'MC', 'spatial_count_kmeans', 'phenotype_2', 'phenotype_2_tumor_kmeans', 'phenotype_2_tumor_kmeans_renamed', 'phenotype_large_cohort', 'phenotype_large_cohort_RJP', 'spatial_lda_kmeans', 'phenotype_2_tumor_kmeans_old', 'tumor_expression_trimmed', 'tumor_expression_TC', 'IM_roi', 'TC_ROI_IM_large', 'TC_ROI_IM_small', 'TC_ROI_IM_elarge', 'manuscript_roi', 'SOX10_positive', 'tumor_region', 'epi_region', 'distance_from_epidermis', 'distance_from_epidermis_binned'\n", " uns: 'all_markers', \"dendrogram_['kmeans']\", \"dendrogram_['phenograph']\", \"dendrogram_['phenograph_raw']\", \"dendrogram_['phenograph_raw_minimal']\", \"dendrogram_['phenotype']\", \"dendrogram_['phenotype_2_tumor_kmeans']\", \"dendrogram_['phenotype_2_tumor_kmeans_scaled']\", \"dendrogram_['spatial_expression_consolidated']\", \"dendrogram_['spatial_expression_kmeans']\", \"dendrogram_['spatial_expression_phenograph']\", 'interaction_all', 'rank_genes_groups', 'spatial_count_radius', 'spatial_distance', 'spatial_expression_radius', 'spatial_interaction', 'spatial_interaction_1', 'spatial_interaction_motif_analysis'" ] diff --git a/tutorials/6_tutorial_visium_cell2location.ipynb b/tutorials/6_tutorial_visium_cell2location.ipynb index 1c627bd..b56e86f 100644 --- a/tutorials/6_tutorial_visium_cell2location.ipynb +++ b/tutorials/6_tutorial_visium_cell2location.ipynb @@ -55,7 +55,7 @@ { "data": { "text/plain": [ - "AnnData object with n_obs × n_vars = 4035 × 10217\n", + "AnnData object with n_obs \u00d7 n_vars = 4035 \u00d7 10217\n", " obs: 'in_tissue', 'array_row', 'array_col', 'sample', '_indices', '_scvi_batch', '_scvi_labels'\n", " var: 'feature_types', 'genome', 'SYMBOL', 'MT_gene'\n", " uns: '_scvi_manager_uuid', '_scvi_uuid', 'mod', 'spatial'\n", @@ -68,7 +68,7 @@ } ], "source": [ - "adata = ad.read_h5ad(\"../../data/sp_1.h5ad\")\n", + "adata = ad.read_h5ad(\"../data/sp_1.h5ad\")\n", "adata" ] }, @@ -77,7 +77,7 @@ "id": "3b078b56", "metadata": {}, "source": [ - "We use 5% quantile of the posterior distribution, representing the value of cell abundance that the model has high confidence in (aka ‘at least this amount is present’).\n", + "We use 5% quantile of the posterior distribution, representing the value of cell abundance that the model has high confidence in (aka \u2018at least this amount is present\u2019).\n", "\n", "https://cell2location.readthedocs.io/en/latest/notebooks/cell2location_tutorial.html#Visualising-cell-abundance-in-spatial-coordinates" ] @@ -799,7 +799,7 @@ " \n", " \n", "\n", - "

2 rows × 34 columns

\n", + "

2 rows \u00d7 34 columns

\n", "" ], "text/plain": [ @@ -966,7 +966,7 @@ " \n", " \n", "\n", - "

2 rows × 34 columns

\n", + "

2 rows \u00d7 34 columns

\n", "" ], "text/plain": [ @@ -1650,7 +1650,7 @@ " \n", " \n", "\n", - "

4 rows × 34 columns

\n", + "

4 rows \u00d7 34 columns

\n", "" ], "text/plain": [ diff --git a/tutorials/8_tutorial_scimap_joint_analysis.ipynb b/tutorials/8_tutorial_scimap_joint_analysis.ipynb index b572bbc..fdb8d4d 100644 --- a/tutorials/8_tutorial_scimap_joint_analysis.ipynb +++ b/tutorials/8_tutorial_scimap_joint_analysis.ipynb @@ -51,7 +51,7 @@ "metadata": {}, "outputs": [], "source": [ - "adata = ad.read_h5ad(\"../../data/MEL1_adata.h5ad\")" + "adata = ad.read_h5ad(\"../data/MEL1_adata.h5ad\")" ] }, { @@ -606,7 +606,7 @@ { "data": { "text/plain": [ - "AnnData object with n_obs × n_vars = 632296 × 6\n", + "AnnData object with n_obs \u00d7 n_vars = 632296 \u00d7 6\n", " obs: 'X_centroid', 'Y_centroid', 'phenotype_large_cohort', 'SOX10_cellRingMask_positive', 'MITF_cellRingMask_positive', 'KERATIN_cellRingMask_positive', 'pheno', 'COI_community', 'region', 'CD3D_cellRingMask_positive'\n", " uns: 'all_markers'" ] @@ -630,7 +630,7 @@ { "data": { "text/plain": [ - "AnnData object with n_obs × n_vars = 356569 × 6\n", + "AnnData object with n_obs \u00d7 n_vars = 356569 \u00d7 6\n", " obs: 'X_centroid', 'Y_centroid', 'phenotype_large_cohort', 'SOX10_cellRingMask_positive', 'MITF_cellRingMask_positive', 'KERATIN_cellRingMask_positive', 'pheno', 'COI_community', 'region', 'CD3D_cellRingMask_positive'\n", " uns: 'all_markers'" ] @@ -654,7 +654,7 @@ { "data": { "text/plain": [ - "AnnData object with n_obs × n_vars = 10000 × 6\n", + "AnnData object with n_obs \u00d7 n_vars = 10000 \u00d7 6\n", " obs: 'X_centroid', 'Y_centroid', 'phenotype_large_cohort', 'SOX10_cellRingMask_positive', 'MITF_cellRingMask_positive', 'KERATIN_cellRingMask_positive', 'pheno', 'COI_community', 'region', 'CD3D_cellRingMask_positive'\n", " uns: 'all_markers', 'neighbors'\n", " obsp: 'distances', 'connectivities'" diff --git a/tutorials/MEL1_part1_animation_umap2xy_scimap.gif b/tutorials/MEL1_part1_animation_umap2xy_scimap.gif index ae91b03..654272f 100644 Binary files a/tutorials/MEL1_part1_animation_umap2xy_scimap.gif and b/tutorials/MEL1_part1_animation_umap2xy_scimap.gif differ diff --git a/tutorials/MEL1_part2_animation_umap2xy_scimap.gif b/tutorials/MEL1_part2_animation_umap2xy_scimap.gif index bd5d53e..824960c 100644 Binary files a/tutorials/MEL1_part2_animation_umap2xy_scimap.gif and b/tutorials/MEL1_part2_animation_umap2xy_scimap.gif differ