Skip to content

[main] Add spatialdata element to segger export - #80

Open
Tobiaspk wants to merge 12 commits into
mainfrom
feature/spatialdata_export
Open

[main] Add spatialdata element to segger export#80
Tobiaspk wants to merge 12 commits into
mainfrom
feature/spatialdata_export

Conversation

@Tobiaspk

@Tobiaspk Tobiaspk commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Added better support for spatialdata in response to #25 #48 #67 #73.

Add spatialdata element to segger export. Requires that sdata.zarr exists somehwere, created for example using spatialdata_io.<technology>. Run like this:

pixi run -e cuda121 segger export spatialdata \
  -s "$PATH_OUTPUT/segger_segmentation.parquet" \
  --sdata "$PATH_INPUT/sdata.zarr"

Output:

  • This will add the following layers to the sdata.zarr:
    • transcripts_segger
    • cell_boundaries_segger
    • table_segger

Notes:

  • If any element already exists, this will throw a FileExistsError
  • The exported transcripts_segger only contain assigned transcripts. In contrast, transcripts in sdata.zarr often contain unassigned and negative control transcripts too
  • Use --spatialdata-element-prefix in segger export for a different prefix. Per default it's _segger.

Other changes:

Example result:
image

@Tobiaspk Tobiaspk changed the title Add spatialdata element to segger export [main] \Add spatialdata element to segger export Aug 25, 2026
@Tobiaspk Tobiaspk changed the title [main] \Add spatialdata element to segger export [main] Add spatialdata element to segger export Aug 25, 2026
tobiaspk and others added 9 commits August 25, 2026 15:12
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.
@Tobiaspk

Copy link
Copy Markdown
Collaborator Author

Example user workflow:

# define inputs and outputs
PATH_INPUT=../../data/inputs/xenium_sample_a
PATH_OUTPUT=../../data/outputs/xenium_sample_a

# save xenium as a spatialdata object (this can take a bit)
conda run -n sc python -c "
import spatialdata_io
sdata = spatialdata_io.xenium('$PATH_INPUT')
sdata.write('$PATH_INPUT/sdata.zarr')
"

# segment with segger
pixi run -e cuda121 segger segment \
  --input-directory "$PATH_INPUT" \
  --output-directory "$PATH_OUTPUT"

# export spatialdata
pixi run -e cuda121 segger export spatialdata \
  -s "$PATH_OUTPUT/segger_segmentation.parquet" \
  --sdata "$PATH_INPUT/sdata.zarr"

@Tobiaspk

Tobiaspk commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Asking for your feedback on this workflow @MeyerBender, @quentinblampey. This adds segger elements to an existing spatialdata object. Would this be useful for you?

@quentinblampey

Copy link
Copy Markdown

Tagging @alihamraoui since he started working on integrating segger to sopa

@MeyerBender

Copy link
Copy Markdown

Thanks a lot, looks really cool and will be super useful! One thing I don't fully understand is why the transcripts_segger points would only contain the assigned transcripts. Since we are working on QC for ST segmentation, it is often useful to have all points in the transcripts, even the unassigned ones. Wouldn't this be what the filtered column in the points df would be for anyway? Or alternatively, could there be a switch allowing users to in-/exclude those transcripts in transcripts_segger?

Also tagging @LazDaria.

@EliHei2

EliHei2 commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

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.

@Tobiaspk

Copy link
Copy Markdown
Collaborator Author

Appreciate your inputs. How about negative controls and antisense probes? And low quality probes (qv < 20)?

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 filtered flag, we could default to all transcripts. Less chance of error for the user. Same question as above though.

@MeyerBender

Copy link
Copy Markdown

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).

@Tobiaspk

Copy link
Copy Markdown
Collaborator Author

I see, this is useful. We could join the following info with configurable prefix to the existing transcripts then:

  • segger_similarity
  • segger_cell_id
  • segger_similarity_threshold
  • segger_converged
  • segger_filtered

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

@Tobiaspk

Copy link
Copy Markdown
Collaborator Author

Are there naming conventions or other aspects to look out for for SOPA integration? @alihamraoui

@MeyerBender

Copy link
Copy Markdown

Sounds like a resonable plan, from my POV the most important ones would be segger_cell_id and segger_filtered. I would set segger_cell_id to None for all cells that are unassigned, regardless of the reason (low QV, control probe, or no convergence). And segger_filtered would then simply be a boolean that indicates if segger even attempted to assign the transcript or filtered it out beforehand, correct?

1 similar comment
@MeyerBender

Copy link
Copy Markdown

Sounds like a resonable plan, from my POV the most important ones would be segger_cell_id and segger_filtered. I would set segger_cell_id to None for all cells that are unassigned, regardless of the reason (low QV, control probe, or no convergence). And segger_filtered would then simply be a boolean that indicates if segger even attempted to assign the transcript or filtered it out beforehand, correct?

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants