[main] Add spatialdata element to segger export - #80
Conversation
|
Example user workflow: |
|
Asking for your feedback on this workflow @MeyerBender, @quentinblampey. This adds segger elements to an existing spatialdata object. Would this be useful for you? |
|
Tagging @alihamraoui since he started working on integrating segger to sopa |
|
Thanks a lot, looks really cool and will be super useful! One thing I don't fully understand is why the Also tagging @LazDaria. |
|
Agree with @MeyerBender I belive keeping all transcripts is essential for downstream QC, so default should contain all, while user can select only to keep assigned transcripts. |
|
Appreciate your inputs. How about negative controls and antisense probes? And low quality probes ( If we keep all the same transcripts, it doesn't necessarily make sense to store a new element. Perhaps just add a column with seggers segmentation to the existing transcripts element? @EliHei2 with the new |
|
I think that would be a good idea, having just one points element should also make things less ambiguous. I would even keep quality probes in there, and just have them as "Unassigned". I can ofc only speak for SegTraQ, but we usually filter out quality probes beforehand, and then only look at the "regular" gene probes to compute the number of unassigned transcripts. What would be important is that the number of transcripts assigned in the points element matches the number in the table exactly (in the old ProSeg version, this wasn't enforced, which led to quite some inconsistencies, see here). |
|
I see, this is useful. We could join the following info with configurable prefix to the existing transcripts then:
How about the cell_boundaries and table? Should we override, or add new elements? -- we don't use spatialdata, so really reffering to your inputs here. thanks @MeyerBender |
|
Are there naming conventions or other aspects to look out for for SOPA integration? @alihamraoui |
|
Sounds like a resonable plan, from my POV the most important ones would be |
1 similar comment
|
Sounds like a resonable plan, from my POV the most important ones would be |
… for legacy segmentation outputs
Runs cell_boundary for each cell in a ProcessPoolExecutor sized to the SLURM-allocated CPU count instead of a single-threaded loop.
spatialdata's xarray_schema dependency imports pkg_resources, which comes from setuptools; not otherwise pulled in by the resolved env.
zip(*results) produced tuples, which geopandas' set_geometry mistakes for a column-name lookup instead of geometry values, dumping every polygon into the error message. Convert to lists. Also leave one CPU free instead of using every allocated core.
- Disable dask's query-planning backend at the top of segger/__init__.py: cudf is imported unconditionally by every CLI subcommand and pulls in dask.dataframe before export code ever runs, so setting the config lazily inside the export path was too late. - _write_to_sdata now adds elements directly to the given --sdata store instead of copytree-ing it into the output directory first; avoids the 'cannot overwrite' error from the raw Xenium sdata's transcripts/ cell_boundaries/table already occupying those element names. - Fail fast: check the target element names don't already exist before doing any of the (expensive) segmentation/boundary work. - Add --spatialdata-element-prefix (default '_segger') to name segger's elements distinctly from the source sdata's own. - -o/--output-directory is now optional, only required for the anndata/transcripts/boundaries elements.
Points and table centroids include z when present; no-op until a reader emits it. Spatialdata writes now fail on a name collision instead of overwriting. Co-authored-by: Elyas Heidari <55977725+EliHei2@users.noreply.github.com> Co-authored-by: enric-bazz <enrico.bazzacco02@outlook.it>
…w one Per PR #80 discussion: transcripts_segger duplicated the sdata transcripts table and dropped unassigned/control transcripts. Now left-joins segger's columns (segger_cell_id, segger_similarity, segger_similarity_threshold, segger_converged, segger_filtered) onto the existing points element in place, with unassigned transcripts left null and a new segger_unused flag for rows segger never saw at all.
Rework transcripts loading around a single load_transcripts() that trusts segger's own 'filtered' flag instead of recomputing similarity thresholds client-side, and rename the sdata element options to explicit --sdata-transcripts-name/--sdata-cell-boundaries-name/--sdata-table-name. Fix two gaps that would have broken the spatialdata path: load_transcripts now returns the full per-transcript frame alongside the assigned subset (needed to append segger's columns onto the sdata transcripts table), and _merge_sdata_transcripts keeps row_index through its select so the join onto the sdata table doesn't fail.
aee0b10 to
a1e223b
Compare
Matches _write_to_sdata's signature style instead of a dict.
…azily in dask PointsModel/ShapesModel.parse default to an Identity transform on 'global' when none is passed, silently dropping whatever scale/affine the base transcripts element carried. _write_to_sdata now reads it via get_transformation() and passes it through to both the rewritten transcripts and the new boundaries. _merge_sdata_transcripts no longer round-trips the base transcripts through pandas/polars/pandas; it merges lazily in dask (broadcast join, cumsum-built row_index) and is only materialized once, right before write_element() overwrites the on-disk store in place (required, since write_element refuses to overwrite a path a live dask graph still reads from). include_all_transcripts now defaults to True.
write_element(overwrite=True) always fails for an element inside its own store (discussion #520). Back up transcripts, detach in-memory handles, then delete-then-write. Also drop the row_index join key from output.
Drop cells below min_counts from the table/anndata, and keep only cells that produced a boundary polygon so the table annotates only real shapes (no dangling SpatialData annotations). Require min_counts >= 3 for spatialdata, since boundaries need >= 3 transcripts.
|
Current usage: This
Other changes
@MeyerBender @EliHei2 do you want to test this? Shouldn't need to rerun segger, just the export should be fine. |
…> export spatialdata) Anticipates #80: segger export spatialdata edits an existing SpatialData Zarr store in place rather than copying into the output directory, so update the flag table and outputs page to match, and add the recommended Xenium workflow to the quickstart.
|
@Tobiaspk it took me a while to get the installation to work, I think the issue is that the current installation instructions install two competing versions of cupy. I did manage to manually make it work in the end, but now I keep running into |
|
@MeyerBender,
|
|
Cool, will have a look, thanks! I fixed the quadtree bug by changing It seems like segger is running now, but I cannot install spatialdata into the same environment. Specifically, I get the following error: I will try to run the segmentation and export in two separate environments now to see if it works that way. |
|
@MeyerBender that's expected, RAPIDS pins dask to an old version, and spatialdata needs I'm running segger (uv) and spatialdata (conda) in separate environments for now. |
|
Thanks @alihamraoui for these details! This will be an issue for containerization, no? |
|
I think so, two images/two venvs, with segger writing to disk and a second container picking it up for the spatialdata step. But I think it'll work fine for nf-core/sopa. Hope the maintainers find a better alternative. |
|
Thanks for looking into this @MeyerBender. Did you try installing it with Unfortunately, |
Re-running export against transcripts that already have segger_* columns caused merge() to silently suffix them with _x/_y instead of overwriting, corrupting the segger_seen/segger_filtered computation.
Added better support for spatialdata in response to #25 #48 #67 #73.
Adds a
spatialdataelement tosegger export. Requires thatsdata.zarralready exists (e.g. created withspatialdata_io.<technology>). Run like this:Output — this edits
sdata.zarrin place:segger_cell_id,segger_similarity,segger_similarity_threshold,segger_converged,segger_filtered,segger_seen) to the existingtranscriptspoints element.cell_boundaries_seggershapes element (cell polygons).table_seggertable element (cell × gene AnnData).Element names are configurable:
--sdata-transcripts-name(defaulttranscripts) — existing points element to append segger's columns to.--sdata-cell-boundaries-name(defaultcell_boundaries_segger)--sdata-table-name(defaulttable_segger)Notes:
FileExistsErrorif the target boundaries/table name already exists, orKeyErrorif the transcripts element is missing.segger_seenmarks the ones it did.--min-counts(default 10) drops cells with fewer assigned transcripts from the table. Must be>= 3for spatialdata, since boundaries need>= 3transcripts to form a polygon. The table is aligned to the boundaries, so it never annotates a missing shape.Implementation notes:
Other changes:
filteredcolumn introduced in [main] Add x/y coordinates and filtered flag to transcripts output #79