-
Notifications
You must be signed in to change notification settings - Fork 8
Support binary datatype of the columns #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |||||||||||||||||||||
| import geopandas as gpd | ||||||||||||||||||||||
| import polars as pl | ||||||||||||||||||||||
| import pandas as pd | ||||||||||||||||||||||
| import json | ||||||||||||||||||||||
| import warnings | ||||||||||||||||||||||
| import logging | ||||||||||||||||||||||
| import sys | ||||||||||||||||||||||
|
|
@@ -21,7 +22,8 @@ | |||||||||||||||||||||
| MerscopeBoundaryFields, | ||||||||||||||||||||||
| StandardTranscriptFields, | ||||||||||||||||||||||
| StandardBoundaryFields, | ||||||||||||||||||||||
| XeniumTranscriptFields, | ||||||||||||||||||||||
| XeniumTranscriptFields, | ||||||||||||||||||||||
| XeniumTranscriptFieldsV1, | ||||||||||||||||||||||
| XeniumBoundaryFields, | ||||||||||||||||||||||
| CosMxTranscriptFields, | ||||||||||||||||||||||
| CosMxBoundaryFields, | ||||||||||||||||||||||
|
|
@@ -372,16 +374,48 @@ class XeniumPreprocessor(ISTPreprocessor): | |||||||||||||||||||||
| """ | ||||||||||||||||||||||
| Preprocessor for 10x Genomics Xenium datasets. | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| tx_fields = XeniumTranscriptFields() | ||||||||||||||||||||||
| bd_fields = XeniumBoundaryFields() | ||||||||||||||||||||||
| sw_version = lambda version: version[0] > 1 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @staticmethod | ||||||||||||||||||||||
| def _validate_directory(data_dir: Path): | ||||||||||||||||||||||
| def _get_analysis_sw_version(data_dir: Path) -> str: | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| Get 10x xenium analysis software version. Example experiment.xenium file: | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| ..., | ||||||||||||||||||||||
| "analysis_sw_version": "xenium-3.3.1.1" | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| Return: | ||||||||||||||||||||||
| version : list of ints representing major, minor, and patch version numbers (e.g. [3, 3, 1, 1]) | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # get version | ||||||||||||||||||||||
| path_meta = data_dir / "experiment.xenium" | ||||||||||||||||||||||
| with open(path_meta) as f: | ||||||||||||||||||||||
| meta = json.load(f) | ||||||||||||||||||||||
| # version can be xenium-x.y.z or Xenium-x.y.z, ... | ||||||||||||||||||||||
| version = meta["analysis_sw_version"].split("-")[-1].split(".") | ||||||||||||||||||||||
| version = [int(v) for v in version] | ||||||||||||||||||||||
| return version | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @classmethod | ||||||||||||||||||||||
| def _validate_directory(cls, data_dir: Path): | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Apply xenium software version 2 or higher (when cell id "Unassigned" was introduced. Previously -1) | ||||||||||||||||||||||
| version = XeniumPreprocessor._get_analysis_sw_version(data_dir) | ||||||||||||||||||||||
| if not cls.sw_version(version): | ||||||||||||||||||||||
|
Comment on lines
+394
to
+408
|
||||||||||||||||||||||
| raise IOError( | ||||||||||||||||||||||
| f"Xenium analysis software version must be 2.0.0 or higher, " | ||||||||||||||||||||||
| f"but found version {'.'.join(version)}." | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
Comment on lines
+383
to
+412
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Check required files/directories | ||||||||||||||||||||||
| bd_fields = XeniumBoundaryFields() | ||||||||||||||||||||||
| tx_fields = XeniumTranscriptFields() | ||||||||||||||||||||||
| for pat in [ | ||||||||||||||||||||||
| tx_fields.filename, | ||||||||||||||||||||||
| bd_fields.cell_filename, | ||||||||||||||||||||||
| bd_fields.nucleus_filename, | ||||||||||||||||||||||
| cls.tx_fields.filename, | ||||||||||||||||||||||
| cls.bd_fields.cell_filename, | ||||||||||||||||||||||
| cls.bd_fields.nucleus_filename, | ||||||||||||||||||||||
| ]: | ||||||||||||||||||||||
| num_matches = len(list(data_dir.glob(pat))) | ||||||||||||||||||||||
| if not num_matches == 1: | ||||||||||||||||||||||
|
|
@@ -394,7 +428,7 @@ def _validate_directory(data_dir: Path): | |||||||||||||||||||||
| def transcripts(self) -> pl.DataFrame: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Field names | ||||||||||||||||||||||
| raw = XeniumTranscriptFields() | ||||||||||||||||||||||
| raw = self.tx_fields | ||||||||||||||||||||||
| std = StandardTranscriptFields() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
|
|
@@ -405,6 +439,11 @@ def transcripts(self) -> pl.DataFrame: | |||||||||||||||||||||
| ) | ||||||||||||||||||||||
| # Add numeric index at beginning | ||||||||||||||||||||||
| .with_row_index(name=std.row_index) | ||||||||||||||||||||||
| # Cast binary columns to string (Some Xenium parquet stores these as binary) | ||||||||||||||||||||||
| .with_columns( | ||||||||||||||||||||||
| pl.col(raw.feature).cast(pl.Utf8), | ||||||||||||||||||||||
| pl.col(raw.cell_id).cast(pl.Utf8), | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| # Filter data | ||||||||||||||||||||||
| .filter(pl.col(raw.quality) >= 20) | ||||||||||||||||||||||
| .filter(pl.col(raw.feature).str.contains( | ||||||||||||||||||||||
|
|
@@ -437,15 +476,16 @@ def transcripts(self) -> pl.DataFrame: | |||||||||||||||||||||
| .collect() | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @staticmethod | ||||||||||||||||||||||
| @classmethod | ||||||||||||||||||||||
| def _get_boundaries( | ||||||||||||||||||||||
| cls, | ||||||||||||||||||||||
| filepath: Path, | ||||||||||||||||||||||
| boundary_type: str | ||||||||||||||||||||||
| ) -> gpd.GeoDataFrame: | ||||||||||||||||||||||
| # TODO: Add documentation | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Field names | ||||||||||||||||||||||
| raw = XeniumBoundaryFields() | ||||||||||||||||||||||
| raw = cls.bd_fields | ||||||||||||||||||||||
| std = StandardBoundaryFields() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Read in flat vertices and convert to geometries | ||||||||||||||||||||||
|
|
@@ -463,7 +503,7 @@ def _get_boundaries( | |||||||||||||||||||||
| @cached_property | ||||||||||||||||||||||
| def boundaries(self) -> gpd.GeoDataFrame: | ||||||||||||||||||||||
| # TODO: Add documentation | ||||||||||||||||||||||
| raw = XeniumBoundaryFields() | ||||||||||||||||||||||
| raw = self.bd_fields | ||||||||||||||||||||||
| std = StandardBoundaryFields() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Join boundary datasets | ||||||||||||||||||||||
|
|
@@ -496,14 +536,24 @@ def boundaries(self) -> gpd.GeoDataFrame: | |||||||||||||||||||||
| cells.reset_index(drop=False, names=std.id), | ||||||||||||||||||||||
| nuclei.reset_index(drop=False, names=std.id), | ||||||||||||||||||||||
| ]) | ||||||||||||||||||||||
| # Convert index to string type (to join on AnnData) | ||||||||||||||||||||||
| bd.index = bd[std.id] + '_' + bd[std.boundary_type].map({ | ||||||||||||||||||||||
| # cell_id is string in later 10x versions, but int in earlier versions. | ||||||||||||||||||||||
| bd.index = bd[std.id].astype(str) + '_' + bd[std.boundary_type].map({ | ||||||||||||||||||||||
|
Comment on lines
+539
to
+540
|
||||||||||||||||||||||
| # cell_id is string in later 10x versions, but int in earlier versions. | |
| bd.index = bd[std.id].astype(str) + '_' + bd[std.boundary_type].map({ | |
| # cell_id is string in later 10x versions, but int/bytes in earlier versions. | |
| # Normalize the column itself so downstream joins on `cell_id` match | |
| # transcript `cell_id` values, then build the index from the normalized | |
| # column. | |
| bd[std.id] = bd[std.id].map( | |
| lambda value: value.decode() if isinstance(value, bytes) else str(value) | |
| ) | |
| bd.index = bd[std.id] + '_' + bd[std.boundary_type].map({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sw_versionis defined as a plain function attribute (lambda) on the class. When accessed ascls.sw_version(...)it will be bound and receiveclsas an extra first argument, causing aTypeErrorduring platform inference/validation. Make this a@staticmethod(or a normaldefwrapped withstaticmethod) so it accepts onlyversion.