Skip to content

refactor: convert experimental parameters to TypedDicts (1/4) - #1279

Draft
selmanozleyen wants to merge 8 commits into
mainfrom
feat/experimental-params-typeddicts
Draft

refactor: convert experimental parameters to TypedDicts (1/4)#1279
selmanozleyen wants to merge 8 commits into
mainfrom
feat/experimental-params-typeddicts

Conversation

@selmanozleyen

@selmanozleyen selmanozleyen commented Aug 28, 2026

Copy link
Copy Markdown
Member

Hi,

I want to bring a clear separation to the kwargs madness we have here.

  • Params: For kw only parameter bags we expect from the user: we use this PRs suggestion.
  • Fits: For configurations obtained by other functions: like StainReference or AlignmentResult we use frozen data classes . Even if they are given as input later to other functions since we don't expect the user to type those it doesn't make sense to unpack. Based on this instinct I also have a follow up PR to stack on refactor: add StainReference transform methods (2/4) #1280
  • Results: If they are simple returns only because we don't want to modify the input we can return a NamedTuple (like current SpatialNeighboursResults)

(about the nesting of functions by groups I am open to discussion, but it's really unreadable if we have a flat tl.*. I would also argue that we want to deprecate .im anyway. So we might as well put everything inside experimental.im.* under tl to make it less confusing if we will do this grouping in the docs.

Intended end result after one more pr in this stack:

Fits

Screenshot 2026-08-31 at 12 54 36 AM

Functions

Screenshot 2026-08-31 at 12 54 24 AM

Results

Screenshot 2026-08-31 at 12 53 57 AM

Params

Screenshot 2026-08-31 at 12 53 42 AM image

@selmanozleyen
selmanozleyen force-pushed the feat/experimental-params-typeddicts branch from cd1fa5b to 36451d4 Compare August 28, 2026 07:52
@selmanozleyen
selmanozleyen requested a review from timtreis August 28, 2026 08:03
@codecov

codecov Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.13043% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.25%. Comparing base (f71f2b9) to head (6dcb8f6).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/squidpy/experimental/im/_detect_tissue.py 92.59% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1279      +/-   ##
==========================================
+ Coverage   78.75%   79.25%   +0.50%     
==========================================
  Files          63       64       +1     
  Lines        9370     9357      -13     
  Branches     1558     1528      -30     
==========================================
+ Hits         7379     7416      +37     
+ Misses       1421     1392      -29     
+ Partials      570      549      -21     
Files with missing lines Coverage Δ
src/squidpy/experimental/im/_stain/_constants.py 100.00% <100.00%> (ø)
...c/squidpy/experimental/im/_stain/_decomposition.py 97.84% <100.00%> (+5.91%) ⬆️
src/squidpy/experimental/im/_stain/_normalize.py 93.75% <ø> (ø)
src/squidpy/experimental/im/_stain/_reinhard.py 100.00% <100.00%> (ø)
src/squidpy/experimental/im/_tiling.py 90.10% <100.00%> (+1.03%) ⬆️
src/squidpy/experimental/tl/_tiling_qc.py 69.88% <100.00%> (-0.68%) ⬇️
src/squidpy/experimental/tl/_tiling_stitch.py 77.72% <100.00%> (+2.42%) ⬆️
src/squidpy/experimental/utils/_params.py 100.00% <100.00%> (ø)
src/squidpy/types.py 100.00% <100.00%> (ø)
src/squidpy/experimental/im/_detect_tissue.py 69.84% <92.59%> (+2.06%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@selmanozleyen
selmanozleyen marked this pull request as ready for review August 28, 2026 08:18
@selmanozleyen
selmanozleyen force-pushed the feat/experimental-params-typeddicts branch from d1001aa to affe9e0 Compare August 28, 2026 10:12
@selmanozleyen
selmanozleyen marked this pull request as draft August 28, 2026 12:50
selmanozleyen added a commit to selmanozleyen/squidpy that referenced this pull request Aug 30, 2026
Brings in scverse#1279 (params as TypedDicts with their defaults declared on the key), scverse#1280
(StainFit and its methods), the enum-to-Literal pass, and the API page restructuring, so
the align work sits on the shape those settle rather than carrying its own copy of it.

Conflicts resolved toward the stack for everything it owns: the params, the defaults
machinery, the enum conversion and the docs templates all come from there, and the three
copies this branch had made of them are dropped.

The alignment surface is what this branch adds on top -- the `stalign_align_*` entry
points, the fit classes they return, and `rasterize_points`/`sample_volume`.

`Stalign*Params` move into `squidpy.types` with the rest. Re-exporting them from
`_align._stalign` deadlocked at import: `types` would have imported the implementation
package whose `__init__` imports `types`. They are declared there now, like every other
params class, and `_stalign` reads them back.

`_matches` in the params test grew a tolerant union arm. It walks a key's declared type to
check the default against it, and `npt.ArrayLike` unions in protocols that are not
`runtime_checkable`, so `isinstance` raised before reaching the `None` arm that actually
matched.
@selmanozleyen
selmanozleyen force-pushed the feat/experimental-params-typeddicts branch from 5ef501c to 5146abb Compare August 30, 2026 19:45
@selmanozleyen
selmanozleyen force-pushed the feat/experimental-params-typeddicts branch 2 times, most recently from 5146abb to 0586b10 Compare August 30, 2026 20:00
The module holds the public parameter bags, and later the result tuples that go with them.
Those are not experimental, so `squidpy.types` is where the whole set belongs -- and
introducing it under `experimental` only to move it a PR later is churn a reviewer should
not have to follow.

Its docs section moves with it: it was rendering as `### Types` nested under
`## Experimental`, so the page filed a top-level module as an experimental one.

Imported by `squidpy/__init__` rather than left to arrive as a side effect: it resolved
before only because `experimental.im._detect_tissue` imports it eagerly, so `sq.types`
would have vanished the moment `experimental` became lazy, while `import squidpy.types`
kept working.
The ``**background_detection_params`` entry landed before ``inplace``, and
``border_margin_px`` ahead of the two parameters it follows, so the rendered
parameter list disagreed with the signature above it.
``any_corner`` is an internal helper in ``_detect_tissue``: not exported, not
importable, and nothing a reader of the public params class can look up. State
the fallback instead of pointing at it.
``validate_qc_params``, ``validate_reinhard_params`` and ``validate_stitch_params``
are internal helpers: not exported, not importable, nothing a reader of the public
params class can look up. The surrounding sentences said what every params class
already says by being one, so they go with them.
@flying-sheep

Copy link
Copy Markdown
Member

Nice, I like the plan and consistency.

What I don’t get is the distinction between spelling kwargs out in the function and having them defined in a TypedDict: are they always shared? Is there a rule about what goes where?

@selmanozleyen

selmanozleyen commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

What I don’t get is the distinction between spelling kwargs out in the function and having them defined in a TypedDict: are they always shared? Is there a rule about what goes where?

  • For stalign there is a clear win with deduplication using the inheritence and overriding. We use Unpack here

  • For the cases like in detect_tissue, we have a bag or kwargs we sont want to bloat the signature with in the docs and the code. Honestly I haven't look into the criteria exactly. It's where @timtreis used frozen dataclasses to not bload the signature basically :D. I just converted them mechanically. We also use Unpack here.

  • for cases like in FelzenszwalbParams, we dont use Unpack. But keeping them as typed dict is better than either taking a frozen dataclass vs Mapping. Bc we used to also let the user give a mapping in case they didnt want to create the class themselves. Plus you can annotate mappings while essentially giving the Mapping flexibility to the users as well

@selmanozleyen

Copy link
Copy Markdown
Member Author

hi @timtreis, so these are what I noticed on the kwargs bags. I think it's useful to rethink them:

  • TilingQCParams is three keys against a 14-parameter signature, so no reason it should exist. If it should, it should encapsulate more of the params.
  • detect_tissue keeps auto_max_pixels, close_holes_smaller_than_frac and mask_smoothing_cycles in the signature while bagging corner_size_pct, those seem equally obscure to me

@selmanozleyen selmanozleyen changed the title refactor: convert experimental parameters to TypedDicts refactor: convert experimental parameters to TypedDicts (1/4) Sep 3, 2026
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.

2 participants