Functions relying on transform break with partitioned, dask-backed elements (dask 2025) #1064

Description

@ppm1337

Description

After upgrading to spatialdata 0.7.0 and dask 2025, plotting of points and other elements with dask dataframe as the underlying data structure appears to be broken.

The issue originates from the calls to transform that render_* functions perform:

transform(sdata["transcripts"], to_coordinate_system="global")

Results in ValueError: The number of items in 'lengths' does not match the number of partitions. if sdata["transcripts"].npartitions is > 1.

MRE

Since I am working with Xenium data, I used the xenium_rep1_io data for my MRE listed in your docs.

importspatialdataassdimportspatialdata_plotsdata=sd.read_zarr("temp/xenium/example_data/xenium_v1_human_lung/data.zarr")
feat=sdata["table"].var_names.tolist()[0]
(
sdata
.pl.render_images("he_image")
.pl.render_points(
"transcripts",
table_name="table",
color="feature_name",
groups=feat,
palette="red",
)
.pl.show()
)

Yields ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

Expand for stacktrace
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[41], line 11
1 feat = sdata["table"].var_names.tolist()[0]
2 (
3 sdata.pl.render_images("he_image")
4 .pl.render_points(
5 "transcripts",
6 table_name="table",
7 color="feature_name",
8 groups=feat,
9 palette="red",
10 )
---> 11 .pl.show()
12 )

File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata_plot/pl/basic.py:1163, in PlotAccessor.show(self, coordinate_systems, legend_fontsize, legend_fontweight, legend_loc, legend_fontoutline, na_in_legend, colorbar, colorbar_params, wspace, hspace, ncols, frameon, figsize, dpi, fig, title, share_extent, pad_extent, ax, return_ax, save)
1160 ax.set_title(t)
1161 ax.set_aspect("equal")
-> 1163 extent = get_extent(
1164 sdata,
1165 coordinate_system=cs,
1166 has_images=has_images and wants_images,
1167 has_labels=has_labels and wants_labels,
1168 has_points=has_points and wants_points,
1169 has_shapes=has_shapes and wants_shapes,
1170 elements=wanted_elements,
1171 )
1172 cs_x_min, cs_x_max = extent["x"]
1173 cs_y_min, cs_y_max = extent["y"]

File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
908 if not args:
909 raise TypeError(f'{funcname} requires at least '
910 '1 positional argument')
--> 912 return dispatch(args[0].class)(*args, **kw)

File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:213, in _(e, coordinate_system, exact, has_images, has_labels, has_points, has_shapes, elements)
211 if coordinate_system in coordinate_systems:
212 if isinstance(element_obj, DaskDataFrame | GeoDataFrame):
--> 213 extent = get_extent(element_obj, coordinate_system=coordinate_system, exact=exact)
214 else:
215 extent = get_extent(element_obj, coordinate_system=coordinate_system)

File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
908 if not args:
909 raise TypeError(f'{funcname} requires at least '
910 '1 positional argument')
--> 912 return dispatch(args[0].class)(*args, **kw)

File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:291, in _(e, coordinate_system, exact)
285 extent = _get_extent_of_points(e)
286 return _compute_extent_in_coordinate_system(
287 element=e,
288 coordinate_system=coordinate_system,
289 extent=extent,
290 )
--> 291 transformed = transform(e, to_coordinate_system=coordinate_system)
292 return _get_extent_of_points(transformed)

File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
908 if not args:
909 raise TypeError(f'{funcname} requires at least '
910 '1 positional argument')
--> 912 return dispatch(args[0].class)(*args, **kw)

File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/operations/transform.py:443, in _(data, transformation, maintain_positioning, to_coordinate_system)
441 arrays = []
442 for ax in axes:
--> 443 arrays.append(data[ax].to_dask_array(lengths=True).reshape(-1, 1))
444 xdata = DataArray(da.concatenate(arrays, axis=1), coords={"points": range(len(data)), "dim": list(axes)})
445 xtransformed = transformation._transform_coordinates(xdata)

File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:1422, in FrameBase.to_dask_array(self, lengths, meta, optimize, **optimize_kwargs)
1418 lengths = tuple(self.map_partitions(len).compute())
1420 arr = self.values
-> 1422 chunks = self._validate_chunks(arr, lengths)
1423 arr._chunks = chunks
1425 if meta is not None:

File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:2471, in FrameBase._validate_chunks(self, arr, lengths)
2468 lengths = tuple(lengths)
2470 if len(lengths) != self.npartitions:
-> 2471 raise ValueError(
2472 "The number of items in 'lengths' does not match the number of "
2473 f"partitions. {len(lengths)} != {self.npartitions}"
2474 )
2476 if self.ndim == 1:
2477 chunks = normalize_chunks((lengths,))

ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

Workaround

print(sdata["transcripts"].npartitions)
# 4sdata["transcripts"] =sd.models.PointsModel.parse(
sdata["transcripts"].repartition(npartitions=1).reset_index(drop=True),
transformations={"global": sd.transformations.get_transformation(sdata["transcripts"])},
)
print(sdata["transcripts"].npartitions)
# 1# works without error
(
sdata
.pl.render_images("he_image")
.pl.render_points(
"transcripts",
table_name="table",
color="feature_name",
groups=feat,
palette="red",
)
.pl.show()
)

Additional context

# workssd.get_extent(sdata["transcripts"], coordinate_system="global", exact=False)
# raises ValueErrorsd.get_extent(sdata["transcripts"], coordinate_system="global", exact=True)
# originating fromsd.transform(sdata["transcripts"], to_coordinate_system="global")

The call to reset_index(drop=True) is necessary in the MRE to avoid ValueError: cannot reindex on an axis with duplicate labels.

This issue cannot be reproduced by partitioning the blobs dataset:

fromspatialdata.datasetsimportblobssdata=blobs()
print(sdata["blobs_points"].npartitions)
# 1sdata["blobs_points"] =sd.models.PointsModel.parse(
sdata["blobs_points"].repartition(npartitions=4),
transformations={"global": sd.transformations.get_transformation(sdata["blobs_points"])},
)
print(sdata["blobs_points"].npartitions)
# 4# workssd.transform(sdata["blobs_points"], to_coordinate_system="global")

This error occurs with

  • spatialdata 0.7.0 + dask 2025.12, dask 2025.9 or dask 2025.2.0

This error does not occur with

  • spatialdata 0.6.1 + dask 2024.11.2

Session Info

Click to view session information
-----
dask 2025.12.0
session_info v1.0.1
spatialdata 0.7.0
spatialdata_plot 0.2.14
-----
Click to view modules imported as dependencies
PIL 12.1.0
anndata 0.12.9
annsel 0.1.2
anyio NA
argcomplete NA
arrow 1.4.0
asttokens NA
attr 25.4.0
attrs 25.4.0
babel 2.18.0
certifi 2026.01.04
cffi 2.0.0
charset_normalizer 3.4.4
cloudpickle 3.1.2
comm 0.2.3
cuda 12.9.4
cycler 0.12.1
cython_runtime NA
dask_image NA
datashader 0.18.2
dateutil 2.9.0.post0
debugpy 1.8.20
decorator 5.2.1
defusedxml 0.7.1
donfig 0.8.1.post1
executing 2.2.1
fast_array_utils NA
fastjsonschema NA
fqdn NA
fsspec 2026.2.0
geopandas 1.1.2
google_crc32c NA
h5py 3.15.1
idna 3.11
ipykernel 7.1.0
ipywidgets 8.1.8
isoduration NA
jaraco NA
jedi 0.19.2
jinja2 3.1.6
joblib 1.5.3
json5 0.13.0
jsonpointer 3.0.0
jsonschema 4.26.0
jsonschema_specifications NA
jupyter_events 0.12.0
jupyter_server 2.17.0
jupyterlab_server 2.28.0
kiwisolver 1.4.9
lark 1.3.1
lazy_loader 0.4
legacy_api_wrap NA
llvmlite 0.46.0
locket NA
markupsafe 3.0.3
matplotlib 3.10.8
matplotlib_inline 0.2.1
matplotlib_scalebar 0.9.0
more_itertools 10.8.0
mpl_toolkits NA
msgpack 1.1.2
multipledispatch 0.6.0
multiscale_spatial_image 2.0.3
narwhals 2.16.0
natsort 8.4.0
nbformat 5.10.4
networkx 3.6.1
numba 0.63.1
numcodecs 0.16.5
numexpr 2.14.1
numpy 2.3.5
ome_zarr NA
packaging 26.0
pandas 2.3.3
parso 0.8.5
partd 1.4.2
pathlib_abc NA
pkg_resources NA
platformdirs 4.5.1
pooch v1.9.0
prometheus_client NA
prompt_toolkit 3.0.52
psutil 7.2.2
pure_eval 0.2.3
pyarrow 22.0.0
pycparser 3.00
pyct 0.6.0
pydev_ipython NA
pydevconsole NA
pydevd 3.2.3
pydevd_file_utils NA
pydevd_plugins NA
pydevd_tracing NA
pygments 2.19.2
pyparsing 3.3.2
pyproj 3.7.2
pythonjsonlogger NA
pytz 2025.2
referencing NA
requests 2.32.5
rfc3339_validator 0.1.4
rfc3986_validator 0.1.1
rfc3987_syntax NA
rich NA
rpds NA
scanpy 1.12
scipy 1.16.0
send2trash NA
shapely 2.1.2
six 1.17.0
skimage 0.26.0
sklearn 1.8.0
spatial_image 1.2.3
stack_data 0.6.3
testing NA
threadpoolctl 3.6.0
tlz 1.1.0
toolz 1.1.0
tornado 6.5.4
tqdm 4.67.3
traitlets 5.14.3
typing_extensions NA
upath 0.3.9
uri_template NA
urllib3 2.6.3
vscode NA
wcwidth 0.5.3
webcolors NA
websocket 1.9.0
xarray 2026.1.0
xarray_dataclass 3.0.0
xarray_schema 0.0.3
xrspatial 0.5.2
yaml 6.0.3
zarr 3.1.5
zmq 27.1.0
zoneinfo NA
-----
IPython 9.10.0
jupyter_client 8.8.0
jupyter_core 5.9.1
jupyterlab 4.5.3
notebook 7.5.3
-----
Python 3.12.12 | packaged by conda-forge | (main, Jan 26 2026, 23:51:32) [GCC 14.3.0]
Linux-6.8.0-90-generic-x86_64-with-glibc2.39
-----
Session information updated at 2026-02-06 13:16

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
       blocks
      (function() {
      function addCopyButtons() {
      document.querySelectorAll('pre code').forEach(function(codeBlock) {
      if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
      codeBlock.parentElement.setAttribute('data-copy-added', 'true');
      var btn = document.createElement('button');
      btn.textContent = 'Copy';
      btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
      btn.onmouseover = function() { this.style.opacity = '1'; };
      btn.onmouseout = function() { this.style.opacity = '0.7'; };
      btn.onclick = function() {
      navigator.clipboard.writeText(codeBlock.textContent).then(function() {
      btn.textContent = 'Copied!';
      setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
      });
      };
      codeBlock.parentElement.style.position = 'relative';
      codeBlock.parentElement.appendChild(btn);
      });
      }
      addCopyButtons();
      // Re-run on dynamic content
      var observer = new MutationObserver(addCopyButtons);
      observer.observe(document.body, { childList: true, subtree: true });
      })();
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      Functions relying on transform break with partitioned, dask-backed elements (dask 2025) #1064

      Description

      @ppm1337

      Description

      After upgrading to spatialdata 0.7.0 and dask 2025, plotting of points and other elements with dask dataframe as the underlying data structure appears to be broken.

      The issue originates from the calls to transform that render_* functions perform:

      transform(sdata["transcripts"], to_coordinate_system="global")

      Results in ValueError: The number of items in 'lengths' does not match the number of partitions. if sdata["transcripts"].npartitions is > 1.

      MRE

      Since I am working with Xenium data, I used the xenium_rep1_io data for my MRE listed in your docs.

      importspatialdataassdimportspatialdata_plotsdata=sd.read_zarr("temp/xenium/example_data/xenium_v1_human_lung/data.zarr")
      feat=sdata["table"].var_names.tolist()[0]
      (
      sdata
      .pl.render_images("he_image")
      .pl.render_points(
      "transcripts",
      table_name="table",
      color="feature_name",
      groups=feat,
      palette="red",
      )
      .pl.show()
      )

      Yields ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

      Expand for stacktrace
      ---------------------------------------------------------------------------
      ValueError Traceback (most recent call last)
      Cell In[41], line 11
      1 feat = sdata["table"].var_names.tolist()[0]
      2 (
      3 sdata.pl.render_images("he_image")
      4 .pl.render_points(
      5 "transcripts",
      6 table_name="table",
      7 color="feature_name",
      8 groups=feat,
      9 palette="red",
      10 )
      ---> 11 .pl.show()
      12 )
      

      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata_plot/pl/basic.py:1163, in PlotAccessor.show(self, coordinate_systems, legend_fontsize, legend_fontweight, legend_loc, legend_fontoutline, na_in_legend, colorbar, colorbar_params, wspace, hspace, ncols, frameon, figsize, dpi, fig, title, share_extent, pad_extent, ax, return_ax, save)
      1160 ax.set_title(t)
      1161 ax.set_aspect("equal")
      -> 1163 extent = get_extent(
      1164 sdata,
      1165 coordinate_system=cs,
      1166 has_images=has_images and wants_images,
      1167 has_labels=has_labels and wants_labels,
      1168 has_points=has_points and wants_points,
      1169 has_shapes=has_shapes and wants_shapes,
      1170 elements=wanted_elements,
      1171 )
      1172 cs_x_min, cs_x_max = extent["x"]
      1173 cs_y_min, cs_y_max = extent["y"]

      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
      908 if not args:
      909 raise TypeError(f'{funcname} requires at least '
      910 '1 positional argument')
      --> 912 return dispatch(args[0].class)(*args, **kw)

      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:213, in _(e, coordinate_system, exact, has_images, has_labels, has_points, has_shapes, elements)
      211 if coordinate_system in coordinate_systems:
      212 if isinstance(element_obj, DaskDataFrame | GeoDataFrame):
      --> 213 extent = get_extent(element_obj, coordinate_system=coordinate_system, exact=exact)
      214 else:
      215 extent = get_extent(element_obj, coordinate_system=coordinate_system)

      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
      908 if not args:
      909 raise TypeError(f'{funcname} requires at least '
      910 '1 positional argument')
      --> 912 return dispatch(args[0].class)(*args, **kw)

      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:291, in _(e, coordinate_system, exact)
      285 extent = _get_extent_of_points(e)
      286 return _compute_extent_in_coordinate_system(
      287 element=e,
      288 coordinate_system=coordinate_system,
      289 extent=extent,
      290 )
      --> 291 transformed = transform(e, to_coordinate_system=coordinate_system)
      292 return _get_extent_of_points(transformed)

      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
      908 if not args:
      909 raise TypeError(f'{funcname} requires at least '
      910 '1 positional argument')
      --> 912 return dispatch(args[0].class)(*args, **kw)

      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/operations/transform.py:443, in _(data, transformation, maintain_positioning, to_coordinate_system)
      441 arrays = []
      442 for ax in axes:
      --> 443 arrays.append(data[ax].to_dask_array(lengths=True).reshape(-1, 1))
      444 xdata = DataArray(da.concatenate(arrays, axis=1), coords={"points": range(len(data)), "dim": list(axes)})
      445 xtransformed = transformation._transform_coordinates(xdata)

      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:1422, in FrameBase.to_dask_array(self, lengths, meta, optimize, **optimize_kwargs)
      1418 lengths = tuple(self.map_partitions(len).compute())
      1420 arr = self.values
      -> 1422 chunks = self._validate_chunks(arr, lengths)
      1423 arr._chunks = chunks
      1425 if meta is not None:

      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:2471, in FrameBase._validate_chunks(self, arr, lengths)
      2468 lengths = tuple(lengths)
      2470 if len(lengths) != self.npartitions:
      -> 2471 raise ValueError(
      2472 "The number of items in 'lengths' does not match the number of "
      2473 f"partitions. {len(lengths)} != {self.npartitions}"
      2474 )
      2476 if self.ndim == 1:
      2477 chunks = normalize_chunks((lengths,))

      ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

      Workaround

      print(sdata["transcripts"].npartitions)
      # 4sdata["transcripts"] =sd.models.PointsModel.parse(
      sdata["transcripts"].repartition(npartitions=1).reset_index(drop=True),
      transformations={"global": sd.transformations.get_transformation(sdata["transcripts"])},
      )
      print(sdata["transcripts"].npartitions)
      # 1# works without error
      (
      sdata
      .pl.render_images("he_image")
      .pl.render_points(
      "transcripts",
      table_name="table",
      color="feature_name",
      groups=feat,
      palette="red",
      )
      .pl.show()
      )

      Additional context

      # workssd.get_extent(sdata["transcripts"], coordinate_system="global", exact=False)
      # raises ValueErrorsd.get_extent(sdata["transcripts"], coordinate_system="global", exact=True)
      # originating fromsd.transform(sdata["transcripts"], to_coordinate_system="global")

      The call to reset_index(drop=True) is necessary in the MRE to avoid ValueError: cannot reindex on an axis with duplicate labels.

      This issue cannot be reproduced by partitioning the blobs dataset:

      fromspatialdata.datasetsimportblobssdata=blobs()
      print(sdata["blobs_points"].npartitions)
      # 1sdata["blobs_points"] =sd.models.PointsModel.parse(
      sdata["blobs_points"].repartition(npartitions=4),
      transformations={"global": sd.transformations.get_transformation(sdata["blobs_points"])},
      )
      print(sdata["blobs_points"].npartitions)
      # 4# workssd.transform(sdata["blobs_points"], to_coordinate_system="global")

      This error occurs with

      • spatialdata 0.7.0 + dask 2025.12, dask 2025.9 or dask 2025.2.0

      This error does not occur with

      • spatialdata 0.6.1 + dask 2024.11.2

      Session Info

      Click to view session information
      -----
      dask 2025.12.0
      session_info v1.0.1
      spatialdata 0.7.0
      spatialdata_plot 0.2.14
      -----
      
      Click to view modules imported as dependencies
      PIL 12.1.0
      anndata 0.12.9
      annsel 0.1.2
      anyio NA
      argcomplete NA
      arrow 1.4.0
      asttokens NA
      attr 25.4.0
      attrs 25.4.0
      babel 2.18.0
      certifi 2026.01.04
      cffi 2.0.0
      charset_normalizer 3.4.4
      cloudpickle 3.1.2
      comm 0.2.3
      cuda 12.9.4
      cycler 0.12.1
      cython_runtime NA
      dask_image NA
      datashader 0.18.2
      dateutil 2.9.0.post0
      debugpy 1.8.20
      decorator 5.2.1
      defusedxml 0.7.1
      donfig 0.8.1.post1
      executing 2.2.1
      fast_array_utils NA
      fastjsonschema NA
      fqdn NA
      fsspec 2026.2.0
      geopandas 1.1.2
      google_crc32c NA
      h5py 3.15.1
      idna 3.11
      ipykernel 7.1.0
      ipywidgets 8.1.8
      isoduration NA
      jaraco NA
      jedi 0.19.2
      jinja2 3.1.6
      joblib 1.5.3
      json5 0.13.0
      jsonpointer 3.0.0
      jsonschema 4.26.0
      jsonschema_specifications NA
      jupyter_events 0.12.0
      jupyter_server 2.17.0
      jupyterlab_server 2.28.0
      kiwisolver 1.4.9
      lark 1.3.1
      lazy_loader 0.4
      legacy_api_wrap NA
      llvmlite 0.46.0
      locket NA
      markupsafe 3.0.3
      matplotlib 3.10.8
      matplotlib_inline 0.2.1
      matplotlib_scalebar 0.9.0
      more_itertools 10.8.0
      mpl_toolkits NA
      msgpack 1.1.2
      multipledispatch 0.6.0
      multiscale_spatial_image 2.0.3
      narwhals 2.16.0
      natsort 8.4.0
      nbformat 5.10.4
      networkx 3.6.1
      numba 0.63.1
      numcodecs 0.16.5
      numexpr 2.14.1
      numpy 2.3.5
      ome_zarr NA
      packaging 26.0
      pandas 2.3.3
      parso 0.8.5
      partd 1.4.2
      pathlib_abc NA
      pkg_resources NA
      platformdirs 4.5.1
      pooch v1.9.0
      prometheus_client NA
      prompt_toolkit 3.0.52
      psutil 7.2.2
      pure_eval 0.2.3
      pyarrow 22.0.0
      pycparser 3.00
      pyct 0.6.0
      pydev_ipython NA
      pydevconsole NA
      pydevd 3.2.3
      pydevd_file_utils NA
      pydevd_plugins NA
      pydevd_tracing NA
      pygments 2.19.2
      pyparsing 3.3.2
      pyproj 3.7.2
      pythonjsonlogger NA
      pytz 2025.2
      referencing NA
      requests 2.32.5
      rfc3339_validator 0.1.4
      rfc3986_validator 0.1.1
      rfc3987_syntax NA
      rich NA
      rpds NA
      scanpy 1.12
      scipy 1.16.0
      send2trash NA
      shapely 2.1.2
      six 1.17.0
      skimage 0.26.0
      sklearn 1.8.0
      spatial_image 1.2.3
      stack_data 0.6.3
      testing NA
      threadpoolctl 3.6.0
      tlz 1.1.0
      toolz 1.1.0
      tornado 6.5.4
      tqdm 4.67.3
      traitlets 5.14.3
      typing_extensions NA
      upath 0.3.9
      uri_template NA
      urllib3 2.6.3
      vscode NA
      wcwidth 0.5.3
      webcolors NA
      websocket 1.9.0
      xarray 2026.1.0
      xarray_dataclass 3.0.0
      xarray_schema 0.0.3
      xrspatial 0.5.2
      yaml 6.0.3
      zarr 3.1.5
      zmq 27.1.0
      zoneinfo NA
      
      -----
      IPython 9.10.0
      jupyter_client 8.8.0
      jupyter_core 5.9.1
      jupyterlab 4.5.3
      notebook 7.5.3
      -----
      Python 3.12.12 | packaged by conda-forge | (main, Jan 26 2026, 23:51:32) [GCC 14.3.0]
      Linux-6.8.0-90-generic-x86_64-with-glibc2.39
      -----
      Session information updated at 2026-02-06 13:16
      

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        No labels
        No labels

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          Functions relying on transform break with partitioned, dask-backed elements (dask 2025) #1064

          Description

          @ppm1337

          Description

          After upgrading to spatialdata 0.7.0 and dask 2025, plotting of points and other elements with dask dataframe as the underlying data structure appears to be broken.

          The issue originates from the calls to transform that render_* functions perform:

          transform(sdata["transcripts"], to_coordinate_system="global")

          Results in ValueError: The number of items in 'lengths' does not match the number of partitions. if sdata["transcripts"].npartitions is > 1.

          MRE

          Since I am working with Xenium data, I used the xenium_rep1_io data for my MRE listed in your docs.

          importspatialdataassdimportspatialdata_plotsdata=sd.read_zarr("temp/xenium/example_data/xenium_v1_human_lung/data.zarr")
          feat=sdata["table"].var_names.tolist()[0]
          (
          sdata
          .pl.render_images("he_image")
          .pl.render_points(
          "transcripts",
          table_name="table",
          color="feature_name",
          groups=feat,
          palette="red",
          )
          .pl.show()
          )

          Yields ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

          Expand for stacktrace
          ---------------------------------------------------------------------------
          ValueError Traceback (most recent call last)
          Cell In[41], line 11
          1 feat = sdata["table"].var_names.tolist()[0]
          2 (
          3 sdata.pl.render_images("he_image")
          4 .pl.render_points(
          5 "transcripts",
          6 table_name="table",
          7 color="feature_name",
          8 groups=feat,
          9 palette="red",
          10 )
          ---> 11 .pl.show()
          12 )
          

          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata_plot/pl/basic.py:1163, in PlotAccessor.show(self, coordinate_systems, legend_fontsize, legend_fontweight, legend_loc, legend_fontoutline, na_in_legend, colorbar, colorbar_params, wspace, hspace, ncols, frameon, figsize, dpi, fig, title, share_extent, pad_extent, ax, return_ax, save)
          1160 ax.set_title(t)
          1161 ax.set_aspect("equal")
          -> 1163 extent = get_extent(
          1164 sdata,
          1165 coordinate_system=cs,
          1166 has_images=has_images and wants_images,
          1167 has_labels=has_labels and wants_labels,
          1168 has_points=has_points and wants_points,
          1169 has_shapes=has_shapes and wants_shapes,
          1170 elements=wanted_elements,
          1171 )
          1172 cs_x_min, cs_x_max = extent["x"]
          1173 cs_y_min, cs_y_max = extent["y"]

          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
          908 if not args:
          909 raise TypeError(f'{funcname} requires at least '
          910 '1 positional argument')
          --> 912 return dispatch(args[0].class)(*args, **kw)

          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:213, in _(e, coordinate_system, exact, has_images, has_labels, has_points, has_shapes, elements)
          211 if coordinate_system in coordinate_systems:
          212 if isinstance(element_obj, DaskDataFrame | GeoDataFrame):
          --> 213 extent = get_extent(element_obj, coordinate_system=coordinate_system, exact=exact)
          214 else:
          215 extent = get_extent(element_obj, coordinate_system=coordinate_system)

          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
          908 if not args:
          909 raise TypeError(f'{funcname} requires at least '
          910 '1 positional argument')
          --> 912 return dispatch(args[0].class)(*args, **kw)

          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:291, in _(e, coordinate_system, exact)
          285 extent = _get_extent_of_points(e)
          286 return _compute_extent_in_coordinate_system(
          287 element=e,
          288 coordinate_system=coordinate_system,
          289 extent=extent,
          290 )
          --> 291 transformed = transform(e, to_coordinate_system=coordinate_system)
          292 return _get_extent_of_points(transformed)

          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
          908 if not args:
          909 raise TypeError(f'{funcname} requires at least '
          910 '1 positional argument')
          --> 912 return dispatch(args[0].class)(*args, **kw)

          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/operations/transform.py:443, in _(data, transformation, maintain_positioning, to_coordinate_system)
          441 arrays = []
          442 for ax in axes:
          --> 443 arrays.append(data[ax].to_dask_array(lengths=True).reshape(-1, 1))
          444 xdata = DataArray(da.concatenate(arrays, axis=1), coords={"points": range(len(data)), "dim": list(axes)})
          445 xtransformed = transformation._transform_coordinates(xdata)

          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:1422, in FrameBase.to_dask_array(self, lengths, meta, optimize, **optimize_kwargs)
          1418 lengths = tuple(self.map_partitions(len).compute())
          1420 arr = self.values
          -> 1422 chunks = self._validate_chunks(arr, lengths)
          1423 arr._chunks = chunks
          1425 if meta is not None:

          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:2471, in FrameBase._validate_chunks(self, arr, lengths)
          2468 lengths = tuple(lengths)
          2470 if len(lengths) != self.npartitions:
          -> 2471 raise ValueError(
          2472 "The number of items in 'lengths' does not match the number of "
          2473 f"partitions. {len(lengths)} != {self.npartitions}"
          2474 )
          2476 if self.ndim == 1:
          2477 chunks = normalize_chunks((lengths,))

          ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

          Workaround

          print(sdata["transcripts"].npartitions)
          # 4sdata["transcripts"] =sd.models.PointsModel.parse(
          sdata["transcripts"].repartition(npartitions=1).reset_index(drop=True),
          transformations={"global": sd.transformations.get_transformation(sdata["transcripts"])},
          )
          print(sdata["transcripts"].npartitions)
          # 1# works without error
          (
          sdata
          .pl.render_images("he_image")
          .pl.render_points(
          "transcripts",
          table_name="table",
          color="feature_name",
          groups=feat,
          palette="red",
          )
          .pl.show()
          )

          Additional context

          # workssd.get_extent(sdata["transcripts"], coordinate_system="global", exact=False)
          # raises ValueErrorsd.get_extent(sdata["transcripts"], coordinate_system="global", exact=True)
          # originating fromsd.transform(sdata["transcripts"], to_coordinate_system="global")

          The call to reset_index(drop=True) is necessary in the MRE to avoid ValueError: cannot reindex on an axis with duplicate labels.

          This issue cannot be reproduced by partitioning the blobs dataset:

          fromspatialdata.datasetsimportblobssdata=blobs()
          print(sdata["blobs_points"].npartitions)
          # 1sdata["blobs_points"] =sd.models.PointsModel.parse(
          sdata["blobs_points"].repartition(npartitions=4),
          transformations={"global": sd.transformations.get_transformation(sdata["blobs_points"])},
          )
          print(sdata["blobs_points"].npartitions)
          # 4# workssd.transform(sdata["blobs_points"], to_coordinate_system="global")

          This error occurs with

          • spatialdata 0.7.0 + dask 2025.12, dask 2025.9 or dask 2025.2.0

          This error does not occur with

          • spatialdata 0.6.1 + dask 2024.11.2

          Session Info

          Click to view session information
          -----
          dask 2025.12.0
          session_info v1.0.1
          spatialdata 0.7.0
          spatialdata_plot 0.2.14
          -----
          
          Click to view modules imported as dependencies
          PIL 12.1.0
          anndata 0.12.9
          annsel 0.1.2
          anyio NA
          argcomplete NA
          arrow 1.4.0
          asttokens NA
          attr 25.4.0
          attrs 25.4.0
          babel 2.18.0
          certifi 2026.01.04
          cffi 2.0.0
          charset_normalizer 3.4.4
          cloudpickle 3.1.2
          comm 0.2.3
          cuda 12.9.4
          cycler 0.12.1
          cython_runtime NA
          dask_image NA
          datashader 0.18.2
          dateutil 2.9.0.post0
          debugpy 1.8.20
          decorator 5.2.1
          defusedxml 0.7.1
          donfig 0.8.1.post1
          executing 2.2.1
          fast_array_utils NA
          fastjsonschema NA
          fqdn NA
          fsspec 2026.2.0
          geopandas 1.1.2
          google_crc32c NA
          h5py 3.15.1
          idna 3.11
          ipykernel 7.1.0
          ipywidgets 8.1.8
          isoduration NA
          jaraco NA
          jedi 0.19.2
          jinja2 3.1.6
          joblib 1.5.3
          json5 0.13.0
          jsonpointer 3.0.0
          jsonschema 4.26.0
          jsonschema_specifications NA
          jupyter_events 0.12.0
          jupyter_server 2.17.0
          jupyterlab_server 2.28.0
          kiwisolver 1.4.9
          lark 1.3.1
          lazy_loader 0.4
          legacy_api_wrap NA
          llvmlite 0.46.0
          locket NA
          markupsafe 3.0.3
          matplotlib 3.10.8
          matplotlib_inline 0.2.1
          matplotlib_scalebar 0.9.0
          more_itertools 10.8.0
          mpl_toolkits NA
          msgpack 1.1.2
          multipledispatch 0.6.0
          multiscale_spatial_image 2.0.3
          narwhals 2.16.0
          natsort 8.4.0
          nbformat 5.10.4
          networkx 3.6.1
          numba 0.63.1
          numcodecs 0.16.5
          numexpr 2.14.1
          numpy 2.3.5
          ome_zarr NA
          packaging 26.0
          pandas 2.3.3
          parso 0.8.5
          partd 1.4.2
          pathlib_abc NA
          pkg_resources NA
          platformdirs 4.5.1
          pooch v1.9.0
          prometheus_client NA
          prompt_toolkit 3.0.52
          psutil 7.2.2
          pure_eval 0.2.3
          pyarrow 22.0.0
          pycparser 3.00
          pyct 0.6.0
          pydev_ipython NA
          pydevconsole NA
          pydevd 3.2.3
          pydevd_file_utils NA
          pydevd_plugins NA
          pydevd_tracing NA
          pygments 2.19.2
          pyparsing 3.3.2
          pyproj 3.7.2
          pythonjsonlogger NA
          pytz 2025.2
          referencing NA
          requests 2.32.5
          rfc3339_validator 0.1.4
          rfc3986_validator 0.1.1
          rfc3987_syntax NA
          rich NA
          rpds NA
          scanpy 1.12
          scipy 1.16.0
          send2trash NA
          shapely 2.1.2
          six 1.17.0
          skimage 0.26.0
          sklearn 1.8.0
          spatial_image 1.2.3
          stack_data 0.6.3
          testing NA
          threadpoolctl 3.6.0
          tlz 1.1.0
          toolz 1.1.0
          tornado 6.5.4
          tqdm 4.67.3
          traitlets 5.14.3
          typing_extensions NA
          upath 0.3.9
          uri_template NA
          urllib3 2.6.3
          vscode NA
          wcwidth 0.5.3
          webcolors NA
          websocket 1.9.0
          xarray 2026.1.0
          xarray_dataclass 3.0.0
          xarray_schema 0.0.3
          xrspatial 0.5.2
          yaml 6.0.3
          zarr 3.1.5
          zmq 27.1.0
          zoneinfo NA
          
          -----
          IPython 9.10.0
          jupyter_client 8.8.0
          jupyter_core 5.9.1
          jupyterlab 4.5.3
          notebook 7.5.3
          -----
          Python 3.12.12 | packaged by conda-forge | (main, Jan 26 2026, 23:51:32) [GCC 14.3.0]
          Linux-6.8.0-90-generic-x86_64-with-glibc2.39
          -----
          Session information updated at 2026-02-06 13:16
          

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            No labels
            No labels

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Functions relying on transform break with partitioned, dask-backed elements (dask 2025) #1064

              Description

              @ppm1337

              Description

              After upgrading to spatialdata 0.7.0 and dask 2025, plotting of points and other elements with dask dataframe as the underlying data structure appears to be broken.

              The issue originates from the calls to transform that render_* functions perform:

              transform(sdata["transcripts"], to_coordinate_system="global")

              Results in ValueError: The number of items in 'lengths' does not match the number of partitions. if sdata["transcripts"].npartitions is > 1.

              MRE

              Since I am working with Xenium data, I used the xenium_rep1_io data for my MRE listed in your docs.

              importspatialdataassdimportspatialdata_plotsdata=sd.read_zarr("temp/xenium/example_data/xenium_v1_human_lung/data.zarr")
              feat=sdata["table"].var_names.tolist()[0]
              (
              sdata
              .pl.render_images("he_image")
              .pl.render_points(
              "transcripts",
              table_name="table",
              color="feature_name",
              groups=feat,
              palette="red",
              )
              .pl.show()
              )

              Yields ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

              Expand for stacktrace
              ---------------------------------------------------------------------------
              ValueError Traceback (most recent call last)
              Cell In[41], line 11
              1 feat = sdata["table"].var_names.tolist()[0]
              2 (
              3 sdata.pl.render_images("he_image")
              4 .pl.render_points(
              5 "transcripts",
              6 table_name="table",
              7 color="feature_name",
              8 groups=feat,
              9 palette="red",
              10 )
              ---> 11 .pl.show()
              12 )
              

              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata_plot/pl/basic.py:1163, in PlotAccessor.show(self, coordinate_systems, legend_fontsize, legend_fontweight, legend_loc, legend_fontoutline, na_in_legend, colorbar, colorbar_params, wspace, hspace, ncols, frameon, figsize, dpi, fig, title, share_extent, pad_extent, ax, return_ax, save)
              1160 ax.set_title(t)
              1161 ax.set_aspect("equal")
              -> 1163 extent = get_extent(
              1164 sdata,
              1165 coordinate_system=cs,
              1166 has_images=has_images and wants_images,
              1167 has_labels=has_labels and wants_labels,
              1168 has_points=has_points and wants_points,
              1169 has_shapes=has_shapes and wants_shapes,
              1170 elements=wanted_elements,
              1171 )
              1172 cs_x_min, cs_x_max = extent["x"]
              1173 cs_y_min, cs_y_max = extent["y"]

              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
              908 if not args:
              909 raise TypeError(f'{funcname} requires at least '
              910 '1 positional argument')
              --> 912 return dispatch(args[0].class)(*args, **kw)

              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:213, in _(e, coordinate_system, exact, has_images, has_labels, has_points, has_shapes, elements)
              211 if coordinate_system in coordinate_systems:
              212 if isinstance(element_obj, DaskDataFrame | GeoDataFrame):
              --> 213 extent = get_extent(element_obj, coordinate_system=coordinate_system, exact=exact)
              214 else:
              215 extent = get_extent(element_obj, coordinate_system=coordinate_system)

              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
              908 if not args:
              909 raise TypeError(f'{funcname} requires at least '
              910 '1 positional argument')
              --> 912 return dispatch(args[0].class)(*args, **kw)

              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:291, in _(e, coordinate_system, exact)
              285 extent = _get_extent_of_points(e)
              286 return _compute_extent_in_coordinate_system(
              287 element=e,
              288 coordinate_system=coordinate_system,
              289 extent=extent,
              290 )
              --> 291 transformed = transform(e, to_coordinate_system=coordinate_system)
              292 return _get_extent_of_points(transformed)

              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
              908 if not args:
              909 raise TypeError(f'{funcname} requires at least '
              910 '1 positional argument')
              --> 912 return dispatch(args[0].class)(*args, **kw)

              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/operations/transform.py:443, in _(data, transformation, maintain_positioning, to_coordinate_system)
              441 arrays = []
              442 for ax in axes:
              --> 443 arrays.append(data[ax].to_dask_array(lengths=True).reshape(-1, 1))
              444 xdata = DataArray(da.concatenate(arrays, axis=1), coords={"points": range(len(data)), "dim": list(axes)})
              445 xtransformed = transformation._transform_coordinates(xdata)

              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:1422, in FrameBase.to_dask_array(self, lengths, meta, optimize, **optimize_kwargs)
              1418 lengths = tuple(self.map_partitions(len).compute())
              1420 arr = self.values
              -> 1422 chunks = self._validate_chunks(arr, lengths)
              1423 arr._chunks = chunks
              1425 if meta is not None:

              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:2471, in FrameBase._validate_chunks(self, arr, lengths)
              2468 lengths = tuple(lengths)
              2470 if len(lengths) != self.npartitions:
              -> 2471 raise ValueError(
              2472 "The number of items in 'lengths' does not match the number of "
              2473 f"partitions. {len(lengths)} != {self.npartitions}"
              2474 )
              2476 if self.ndim == 1:
              2477 chunks = normalize_chunks((lengths,))

              ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

              Workaround

              print(sdata["transcripts"].npartitions)
              # 4sdata["transcripts"] =sd.models.PointsModel.parse(
              sdata["transcripts"].repartition(npartitions=1).reset_index(drop=True),
              transformations={"global": sd.transformations.get_transformation(sdata["transcripts"])},
              )
              print(sdata["transcripts"].npartitions)
              # 1# works without error
              (
              sdata
              .pl.render_images("he_image")
              .pl.render_points(
              "transcripts",
              table_name="table",
              color="feature_name",
              groups=feat,
              palette="red",
              )
              .pl.show()
              )

              Additional context

              # workssd.get_extent(sdata["transcripts"], coordinate_system="global", exact=False)
              # raises ValueErrorsd.get_extent(sdata["transcripts"], coordinate_system="global", exact=True)
              # originating fromsd.transform(sdata["transcripts"], to_coordinate_system="global")

              The call to reset_index(drop=True) is necessary in the MRE to avoid ValueError: cannot reindex on an axis with duplicate labels.

              This issue cannot be reproduced by partitioning the blobs dataset:

              fromspatialdata.datasetsimportblobssdata=blobs()
              print(sdata["blobs_points"].npartitions)
              # 1sdata["blobs_points"] =sd.models.PointsModel.parse(
              sdata["blobs_points"].repartition(npartitions=4),
              transformations={"global": sd.transformations.get_transformation(sdata["blobs_points"])},
              )
              print(sdata["blobs_points"].npartitions)
              # 4# workssd.transform(sdata["blobs_points"], to_coordinate_system="global")

              This error occurs with

              • spatialdata 0.7.0 + dask 2025.12, dask 2025.9 or dask 2025.2.0

              This error does not occur with

              • spatialdata 0.6.1 + dask 2024.11.2

              Session Info

              Click to view session information
              -----
              dask 2025.12.0
              session_info v1.0.1
              spatialdata 0.7.0
              spatialdata_plot 0.2.14
              -----
              
              Click to view modules imported as dependencies
              PIL 12.1.0
              anndata 0.12.9
              annsel 0.1.2
              anyio NA
              argcomplete NA
              arrow 1.4.0
              asttokens NA
              attr 25.4.0
              attrs 25.4.0
              babel 2.18.0
              certifi 2026.01.04
              cffi 2.0.0
              charset_normalizer 3.4.4
              cloudpickle 3.1.2
              comm 0.2.3
              cuda 12.9.4
              cycler 0.12.1
              cython_runtime NA
              dask_image NA
              datashader 0.18.2
              dateutil 2.9.0.post0
              debugpy 1.8.20
              decorator 5.2.1
              defusedxml 0.7.1
              donfig 0.8.1.post1
              executing 2.2.1
              fast_array_utils NA
              fastjsonschema NA
              fqdn NA
              fsspec 2026.2.0
              geopandas 1.1.2
              google_crc32c NA
              h5py 3.15.1
              idna 3.11
              ipykernel 7.1.0
              ipywidgets 8.1.8
              isoduration NA
              jaraco NA
              jedi 0.19.2
              jinja2 3.1.6
              joblib 1.5.3
              json5 0.13.0
              jsonpointer 3.0.0
              jsonschema 4.26.0
              jsonschema_specifications NA
              jupyter_events 0.12.0
              jupyter_server 2.17.0
              jupyterlab_server 2.28.0
              kiwisolver 1.4.9
              lark 1.3.1
              lazy_loader 0.4
              legacy_api_wrap NA
              llvmlite 0.46.0
              locket NA
              markupsafe 3.0.3
              matplotlib 3.10.8
              matplotlib_inline 0.2.1
              matplotlib_scalebar 0.9.0
              more_itertools 10.8.0
              mpl_toolkits NA
              msgpack 1.1.2
              multipledispatch 0.6.0
              multiscale_spatial_image 2.0.3
              narwhals 2.16.0
              natsort 8.4.0
              nbformat 5.10.4
              networkx 3.6.1
              numba 0.63.1
              numcodecs 0.16.5
              numexpr 2.14.1
              numpy 2.3.5
              ome_zarr NA
              packaging 26.0
              pandas 2.3.3
              parso 0.8.5
              partd 1.4.2
              pathlib_abc NA
              pkg_resources NA
              platformdirs 4.5.1
              pooch v1.9.0
              prometheus_client NA
              prompt_toolkit 3.0.52
              psutil 7.2.2
              pure_eval 0.2.3
              pyarrow 22.0.0
              pycparser 3.00
              pyct 0.6.0
              pydev_ipython NA
              pydevconsole NA
              pydevd 3.2.3
              pydevd_file_utils NA
              pydevd_plugins NA
              pydevd_tracing NA
              pygments 2.19.2
              pyparsing 3.3.2
              pyproj 3.7.2
              pythonjsonlogger NA
              pytz 2025.2
              referencing NA
              requests 2.32.5
              rfc3339_validator 0.1.4
              rfc3986_validator 0.1.1
              rfc3987_syntax NA
              rich NA
              rpds NA
              scanpy 1.12
              scipy 1.16.0
              send2trash NA
              shapely 2.1.2
              six 1.17.0
              skimage 0.26.0
              sklearn 1.8.0
              spatial_image 1.2.3
              stack_data 0.6.3
              testing NA
              threadpoolctl 3.6.0
              tlz 1.1.0
              toolz 1.1.0
              tornado 6.5.4
              tqdm 4.67.3
              traitlets 5.14.3
              typing_extensions NA
              upath 0.3.9
              uri_template NA
              urllib3 2.6.3
              vscode NA
              wcwidth 0.5.3
              webcolors NA
              websocket 1.9.0
              xarray 2026.1.0
              xarray_dataclass 3.0.0
              xarray_schema 0.0.3
              xrspatial 0.5.2
              yaml 6.0.3
              zarr 3.1.5
              zmq 27.1.0
              zoneinfo NA
              
              -----
              IPython 9.10.0
              jupyter_client 8.8.0
              jupyter_core 5.9.1
              jupyterlab 4.5.3
              notebook 7.5.3
              -----
              Python 3.12.12 | packaged by conda-forge | (main, Jan 26 2026, 23:51:32) [GCC 14.3.0]
              Linux-6.8.0-90-generic-x86_64-with-glibc2.39
              -----
              Session information updated at 2026-02-06 13:16
              

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                No labels
                No labels

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  Functions relying on transform break with partitioned, dask-backed elements (dask 2025) #1064

                  Description

                  @ppm1337

                  Description

                  After upgrading to spatialdata 0.7.0 and dask 2025, plotting of points and other elements with dask dataframe as the underlying data structure appears to be broken.

                  The issue originates from the calls to transform that render_* functions perform:

                  transform(sdata["transcripts"], to_coordinate_system="global")

                  Results in ValueError: The number of items in 'lengths' does not match the number of partitions. if sdata["transcripts"].npartitions is > 1.

                  MRE

                  Since I am working with Xenium data, I used the xenium_rep1_io data for my MRE listed in your docs.

                  importspatialdataassdimportspatialdata_plotsdata=sd.read_zarr("temp/xenium/example_data/xenium_v1_human_lung/data.zarr")
                  feat=sdata["table"].var_names.tolist()[0]
                  (
                  sdata
                  .pl.render_images("he_image")
                  .pl.render_points(
                  "transcripts",
                  table_name="table",
                  color="feature_name",
                  groups=feat,
                  palette="red",
                  )
                  .pl.show()
                  )

                  Yields ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

                  Expand for stacktrace
                  ---------------------------------------------------------------------------
                  ValueError Traceback (most recent call last)
                  Cell In[41], line 11
                  1 feat = sdata["table"].var_names.tolist()[0]
                  2 (
                  3 sdata.pl.render_images("he_image")
                  4 .pl.render_points(
                  5 "transcripts",
                  6 table_name="table",
                  7 color="feature_name",
                  8 groups=feat,
                  9 palette="red",
                  10 )
                  ---> 11 .pl.show()
                  12 )
                  

                  File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata_plot/pl/basic.py:1163, in PlotAccessor.show(self, coordinate_systems, legend_fontsize, legend_fontweight, legend_loc, legend_fontoutline, na_in_legend, colorbar, colorbar_params, wspace, hspace, ncols, frameon, figsize, dpi, fig, title, share_extent, pad_extent, ax, return_ax, save)
                  1160 ax.set_title(t)
                  1161 ax.set_aspect("equal")
                  -> 1163 extent = get_extent(
                  1164 sdata,
                  1165 coordinate_system=cs,
                  1166 has_images=has_images and wants_images,
                  1167 has_labels=has_labels and wants_labels,
                  1168 has_points=has_points and wants_points,
                  1169 has_shapes=has_shapes and wants_shapes,
                  1170 elements=wanted_elements,
                  1171 )
                  1172 cs_x_min, cs_x_max = extent["x"]
                  1173 cs_y_min, cs_y_max = extent["y"]

                  File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
                  908 if not args:
                  909 raise TypeError(f'{funcname} requires at least '
                  910 '1 positional argument')
                  --> 912 return dispatch(args[0].class)(*args, **kw)

                  File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:213, in _(e, coordinate_system, exact, has_images, has_labels, has_points, has_shapes, elements)
                  211 if coordinate_system in coordinate_systems:
                  212 if isinstance(element_obj, DaskDataFrame | GeoDataFrame):
                  --> 213 extent = get_extent(element_obj, coordinate_system=coordinate_system, exact=exact)
                  214 else:
                  215 extent = get_extent(element_obj, coordinate_system=coordinate_system)

                  File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
                  908 if not args:
                  909 raise TypeError(f'{funcname} requires at least '
                  910 '1 positional argument')
                  --> 912 return dispatch(args[0].class)(*args, **kw)

                  File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:291, in _(e, coordinate_system, exact)
                  285 extent = _get_extent_of_points(e)
                  286 return _compute_extent_in_coordinate_system(
                  287 element=e,
                  288 coordinate_system=coordinate_system,
                  289 extent=extent,
                  290 )
                  --> 291 transformed = transform(e, to_coordinate_system=coordinate_system)
                  292 return _get_extent_of_points(transformed)

                  File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
                  908 if not args:
                  909 raise TypeError(f'{funcname} requires at least '
                  910 '1 positional argument')
                  --> 912 return dispatch(args[0].class)(*args, **kw)

                  File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/operations/transform.py:443, in _(data, transformation, maintain_positioning, to_coordinate_system)
                  441 arrays = []
                  442 for ax in axes:
                  --> 443 arrays.append(data[ax].to_dask_array(lengths=True).reshape(-1, 1))
                  444 xdata = DataArray(da.concatenate(arrays, axis=1), coords={"points": range(len(data)), "dim": list(axes)})
                  445 xtransformed = transformation._transform_coordinates(xdata)

                  File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:1422, in FrameBase.to_dask_array(self, lengths, meta, optimize, **optimize_kwargs)
                  1418 lengths = tuple(self.map_partitions(len).compute())
                  1420 arr = self.values
                  -> 1422 chunks = self._validate_chunks(arr, lengths)
                  1423 arr._chunks = chunks
                  1425 if meta is not None:

                  File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:2471, in FrameBase._validate_chunks(self, arr, lengths)
                  2468 lengths = tuple(lengths)
                  2470 if len(lengths) != self.npartitions:
                  -> 2471 raise ValueError(
                  2472 "The number of items in 'lengths' does not match the number of "
                  2473 f"partitions. {len(lengths)} != {self.npartitions}"
                  2474 )
                  2476 if self.ndim == 1:
                  2477 chunks = normalize_chunks((lengths,))

                  ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

                  Workaround

                  print(sdata["transcripts"].npartitions)
                  # 4sdata["transcripts"] =sd.models.PointsModel.parse(
                  sdata["transcripts"].repartition(npartitions=1).reset_index(drop=True),
                  transformations={"global": sd.transformations.get_transformation(sdata["transcripts"])},
                  )
                  print(sdata["transcripts"].npartitions)
                  # 1# works without error
                  (
                  sdata
                  .pl.render_images("he_image")
                  .pl.render_points(
                  "transcripts",
                  table_name="table",
                  color="feature_name",
                  groups=feat,
                  palette="red",
                  )
                  .pl.show()
                  )

                  Additional context

                  # workssd.get_extent(sdata["transcripts"], coordinate_system="global", exact=False)
                  # raises ValueErrorsd.get_extent(sdata["transcripts"], coordinate_system="global", exact=True)
                  # originating fromsd.transform(sdata["transcripts"], to_coordinate_system="global")

                  The call to reset_index(drop=True) is necessary in the MRE to avoid ValueError: cannot reindex on an axis with duplicate labels.

                  This issue cannot be reproduced by partitioning the blobs dataset:

                  fromspatialdata.datasetsimportblobssdata=blobs()
                  print(sdata["blobs_points"].npartitions)
                  # 1sdata["blobs_points"] =sd.models.PointsModel.parse(
                  sdata["blobs_points"].repartition(npartitions=4),
                  transformations={"global": sd.transformations.get_transformation(sdata["blobs_points"])},
                  )
                  print(sdata["blobs_points"].npartitions)
                  # 4# workssd.transform(sdata["blobs_points"], to_coordinate_system="global")

                  This error occurs with

                  • spatialdata 0.7.0 + dask 2025.12, dask 2025.9 or dask 2025.2.0

                  This error does not occur with

                  • spatialdata 0.6.1 + dask 2024.11.2

                  Session Info

                  Click to view session information
                  -----
                  dask 2025.12.0
                  session_info v1.0.1
                  spatialdata 0.7.0
                  spatialdata_plot 0.2.14
                  -----
                  
                  Click to view modules imported as dependencies
                  PIL 12.1.0
                  anndata 0.12.9
                  annsel 0.1.2
                  anyio NA
                  argcomplete NA
                  arrow 1.4.0
                  asttokens NA
                  attr 25.4.0
                  attrs 25.4.0
                  babel 2.18.0
                  certifi 2026.01.04
                  cffi 2.0.0
                  charset_normalizer 3.4.4
                  cloudpickle 3.1.2
                  comm 0.2.3
                  cuda 12.9.4
                  cycler 0.12.1
                  cython_runtime NA
                  dask_image NA
                  datashader 0.18.2
                  dateutil 2.9.0.post0
                  debugpy 1.8.20
                  decorator 5.2.1
                  defusedxml 0.7.1
                  donfig 0.8.1.post1
                  executing 2.2.1
                  fast_array_utils NA
                  fastjsonschema NA
                  fqdn NA
                  fsspec 2026.2.0
                  geopandas 1.1.2
                  google_crc32c NA
                  h5py 3.15.1
                  idna 3.11
                  ipykernel 7.1.0
                  ipywidgets 8.1.8
                  isoduration NA
                  jaraco NA
                  jedi 0.19.2
                  jinja2 3.1.6
                  joblib 1.5.3
                  json5 0.13.0
                  jsonpointer 3.0.0
                  jsonschema 4.26.0
                  jsonschema_specifications NA
                  jupyter_events 0.12.0
                  jupyter_server 2.17.0
                  jupyterlab_server 2.28.0
                  kiwisolver 1.4.9
                  lark 1.3.1
                  lazy_loader 0.4
                  legacy_api_wrap NA
                  llvmlite 0.46.0
                  locket NA
                  markupsafe 3.0.3
                  matplotlib 3.10.8
                  matplotlib_inline 0.2.1
                  matplotlib_scalebar 0.9.0
                  more_itertools 10.8.0
                  mpl_toolkits NA
                  msgpack 1.1.2
                  multipledispatch 0.6.0
                  multiscale_spatial_image 2.0.3
                  narwhals 2.16.0
                  natsort 8.4.0
                  nbformat 5.10.4
                  networkx 3.6.1
                  numba 0.63.1
                  numcodecs 0.16.5
                  numexpr 2.14.1
                  numpy 2.3.5
                  ome_zarr NA
                  packaging 26.0
                  pandas 2.3.3
                  parso 0.8.5
                  partd 1.4.2
                  pathlib_abc NA
                  pkg_resources NA
                  platformdirs 4.5.1
                  pooch v1.9.0
                  prometheus_client NA
                  prompt_toolkit 3.0.52
                  psutil 7.2.2
                  pure_eval 0.2.3
                  pyarrow 22.0.0
                  pycparser 3.00
                  pyct 0.6.0
                  pydev_ipython NA
                  pydevconsole NA
                  pydevd 3.2.3
                  pydevd_file_utils NA
                  pydevd_plugins NA
                  pydevd_tracing NA
                  pygments 2.19.2
                  pyparsing 3.3.2
                  pyproj 3.7.2
                  pythonjsonlogger NA
                  pytz 2025.2
                  referencing NA
                  requests 2.32.5
                  rfc3339_validator 0.1.4
                  rfc3986_validator 0.1.1
                  rfc3987_syntax NA
                  rich NA
                  rpds NA
                  scanpy 1.12
                  scipy 1.16.0
                  send2trash NA
                  shapely 2.1.2
                  six 1.17.0
                  skimage 0.26.0
                  sklearn 1.8.0
                  spatial_image 1.2.3
                  stack_data 0.6.3
                  testing NA
                  threadpoolctl 3.6.0
                  tlz 1.1.0
                  toolz 1.1.0
                  tornado 6.5.4
                  tqdm 4.67.3
                  traitlets 5.14.3
                  typing_extensions NA
                  upath 0.3.9
                  uri_template NA
                  urllib3 2.6.3
                  vscode NA
                  wcwidth 0.5.3
                  webcolors NA
                  websocket 1.9.0
                  xarray 2026.1.0
                  xarray_dataclass 3.0.0
                  xarray_schema 0.0.3
                  xrspatial 0.5.2
                  yaml 6.0.3
                  zarr 3.1.5
                  zmq 27.1.0
                  zoneinfo NA
                  
                  -----
                  IPython 9.10.0
                  jupyter_client 8.8.0
                  jupyter_core 5.9.1
                  jupyterlab 4.5.3
                  notebook 7.5.3
                  -----
                  Python 3.12.12 | packaged by conda-forge | (main, Jan 26 2026, 23:51:32) [GCC 14.3.0]
                  Linux-6.8.0-90-generic-x86_64-with-glibc2.39
                  -----
                  Session information updated at 2026-02-06 13:16
                  

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    No labels
                    No labels

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      Functions relying on transform break with partitioned, dask-backed elements (dask 2025) #1064

                      Description

                      @ppm1337

                      Description

                      After upgrading to spatialdata 0.7.0 and dask 2025, plotting of points and other elements with dask dataframe as the underlying data structure appears to be broken.

                      The issue originates from the calls to transform that render_* functions perform:

                      transform(sdata["transcripts"], to_coordinate_system="global")

                      Results in ValueError: The number of items in 'lengths' does not match the number of partitions. if sdata["transcripts"].npartitions is > 1.

                      MRE

                      Since I am working with Xenium data, I used the xenium_rep1_io data for my MRE listed in your docs.

                      importspatialdataassdimportspatialdata_plotsdata=sd.read_zarr("temp/xenium/example_data/xenium_v1_human_lung/data.zarr")
                      feat=sdata["table"].var_names.tolist()[0]
                      (
                      sdata
                      .pl.render_images("he_image")
                      .pl.render_points(
                      "transcripts",
                      table_name="table",
                      color="feature_name",
                      groups=feat,
                      palette="red",
                      )
                      .pl.show()
                      )

                      Yields ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

                      Expand for stacktrace
                      ---------------------------------------------------------------------------
                      ValueError Traceback (most recent call last)
                      Cell In[41], line 11
                      1 feat = sdata["table"].var_names.tolist()[0]
                      2 (
                      3 sdata.pl.render_images("he_image")
                      4 .pl.render_points(
                      5 "transcripts",
                      6 table_name="table",
                      7 color="feature_name",
                      8 groups=feat,
                      9 palette="red",
                      10 )
                      ---> 11 .pl.show()
                      12 )
                      

                      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata_plot/pl/basic.py:1163, in PlotAccessor.show(self, coordinate_systems, legend_fontsize, legend_fontweight, legend_loc, legend_fontoutline, na_in_legend, colorbar, colorbar_params, wspace, hspace, ncols, frameon, figsize, dpi, fig, title, share_extent, pad_extent, ax, return_ax, save)
                      1160 ax.set_title(t)
                      1161 ax.set_aspect("equal")
                      -> 1163 extent = get_extent(
                      1164 sdata,
                      1165 coordinate_system=cs,
                      1166 has_images=has_images and wants_images,
                      1167 has_labels=has_labels and wants_labels,
                      1168 has_points=has_points and wants_points,
                      1169 has_shapes=has_shapes and wants_shapes,
                      1170 elements=wanted_elements,
                      1171 )
                      1172 cs_x_min, cs_x_max = extent["x"]
                      1173 cs_y_min, cs_y_max = extent["y"]

                      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
                      908 if not args:
                      909 raise TypeError(f'{funcname} requires at least '
                      910 '1 positional argument')
                      --> 912 return dispatch(args[0].class)(*args, **kw)

                      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:213, in _(e, coordinate_system, exact, has_images, has_labels, has_points, has_shapes, elements)
                      211 if coordinate_system in coordinate_systems:
                      212 if isinstance(element_obj, DaskDataFrame | GeoDataFrame):
                      --> 213 extent = get_extent(element_obj, coordinate_system=coordinate_system, exact=exact)
                      214 else:
                      215 extent = get_extent(element_obj, coordinate_system=coordinate_system)

                      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
                      908 if not args:
                      909 raise TypeError(f'{funcname} requires at least '
                      910 '1 positional argument')
                      --> 912 return dispatch(args[0].class)(*args, **kw)

                      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:291, in _(e, coordinate_system, exact)
                      285 extent = _get_extent_of_points(e)
                      286 return _compute_extent_in_coordinate_system(
                      287 element=e,
                      288 coordinate_system=coordinate_system,
                      289 extent=extent,
                      290 )
                      --> 291 transformed = transform(e, to_coordinate_system=coordinate_system)
                      292 return _get_extent_of_points(transformed)

                      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
                      908 if not args:
                      909 raise TypeError(f'{funcname} requires at least '
                      910 '1 positional argument')
                      --> 912 return dispatch(args[0].class)(*args, **kw)

                      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/operations/transform.py:443, in _(data, transformation, maintain_positioning, to_coordinate_system)
                      441 arrays = []
                      442 for ax in axes:
                      --> 443 arrays.append(data[ax].to_dask_array(lengths=True).reshape(-1, 1))
                      444 xdata = DataArray(da.concatenate(arrays, axis=1), coords={"points": range(len(data)), "dim": list(axes)})
                      445 xtransformed = transformation._transform_coordinates(xdata)

                      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:1422, in FrameBase.to_dask_array(self, lengths, meta, optimize, **optimize_kwargs)
                      1418 lengths = tuple(self.map_partitions(len).compute())
                      1420 arr = self.values
                      -> 1422 chunks = self._validate_chunks(arr, lengths)
                      1423 arr._chunks = chunks
                      1425 if meta is not None:

                      File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:2471, in FrameBase._validate_chunks(self, arr, lengths)
                      2468 lengths = tuple(lengths)
                      2470 if len(lengths) != self.npartitions:
                      -> 2471 raise ValueError(
                      2472 "The number of items in 'lengths' does not match the number of "
                      2473 f"partitions. {len(lengths)} != {self.npartitions}"
                      2474 )
                      2476 if self.ndim == 1:
                      2477 chunks = normalize_chunks((lengths,))

                      ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

                      Workaround

                      print(sdata["transcripts"].npartitions)
                      # 4sdata["transcripts"] =sd.models.PointsModel.parse(
                      sdata["transcripts"].repartition(npartitions=1).reset_index(drop=True),
                      transformations={"global": sd.transformations.get_transformation(sdata["transcripts"])},
                      )
                      print(sdata["transcripts"].npartitions)
                      # 1# works without error
                      (
                      sdata
                      .pl.render_images("he_image")
                      .pl.render_points(
                      "transcripts",
                      table_name="table",
                      color="feature_name",
                      groups=feat,
                      palette="red",
                      )
                      .pl.show()
                      )

                      Additional context

                      # workssd.get_extent(sdata["transcripts"], coordinate_system="global", exact=False)
                      # raises ValueErrorsd.get_extent(sdata["transcripts"], coordinate_system="global", exact=True)
                      # originating fromsd.transform(sdata["transcripts"], to_coordinate_system="global")

                      The call to reset_index(drop=True) is necessary in the MRE to avoid ValueError: cannot reindex on an axis with duplicate labels.

                      This issue cannot be reproduced by partitioning the blobs dataset:

                      fromspatialdata.datasetsimportblobssdata=blobs()
                      print(sdata["blobs_points"].npartitions)
                      # 1sdata["blobs_points"] =sd.models.PointsModel.parse(
                      sdata["blobs_points"].repartition(npartitions=4),
                      transformations={"global": sd.transformations.get_transformation(sdata["blobs_points"])},
                      )
                      print(sdata["blobs_points"].npartitions)
                      # 4# workssd.transform(sdata["blobs_points"], to_coordinate_system="global")

                      This error occurs with

                      • spatialdata 0.7.0 + dask 2025.12, dask 2025.9 or dask 2025.2.0

                      This error does not occur with

                      • spatialdata 0.6.1 + dask 2024.11.2

                      Session Info

                      Click to view session information
                      -----
                      dask 2025.12.0
                      session_info v1.0.1
                      spatialdata 0.7.0
                      spatialdata_plot 0.2.14
                      -----
                      
                      Click to view modules imported as dependencies
                      PIL 12.1.0
                      anndata 0.12.9
                      annsel 0.1.2
                      anyio NA
                      argcomplete NA
                      arrow 1.4.0
                      asttokens NA
                      attr 25.4.0
                      attrs 25.4.0
                      babel 2.18.0
                      certifi 2026.01.04
                      cffi 2.0.0
                      charset_normalizer 3.4.4
                      cloudpickle 3.1.2
                      comm 0.2.3
                      cuda 12.9.4
                      cycler 0.12.1
                      cython_runtime NA
                      dask_image NA
                      datashader 0.18.2
                      dateutil 2.9.0.post0
                      debugpy 1.8.20
                      decorator 5.2.1
                      defusedxml 0.7.1
                      donfig 0.8.1.post1
                      executing 2.2.1
                      fast_array_utils NA
                      fastjsonschema NA
                      fqdn NA
                      fsspec 2026.2.0
                      geopandas 1.1.2
                      google_crc32c NA
                      h5py 3.15.1
                      idna 3.11
                      ipykernel 7.1.0
                      ipywidgets 8.1.8
                      isoduration NA
                      jaraco NA
                      jedi 0.19.2
                      jinja2 3.1.6
                      joblib 1.5.3
                      json5 0.13.0
                      jsonpointer 3.0.0
                      jsonschema 4.26.0
                      jsonschema_specifications NA
                      jupyter_events 0.12.0
                      jupyter_server 2.17.0
                      jupyterlab_server 2.28.0
                      kiwisolver 1.4.9
                      lark 1.3.1
                      lazy_loader 0.4
                      legacy_api_wrap NA
                      llvmlite 0.46.0
                      locket NA
                      markupsafe 3.0.3
                      matplotlib 3.10.8
                      matplotlib_inline 0.2.1
                      matplotlib_scalebar 0.9.0
                      more_itertools 10.8.0
                      mpl_toolkits NA
                      msgpack 1.1.2
                      multipledispatch 0.6.0
                      multiscale_spatial_image 2.0.3
                      narwhals 2.16.0
                      natsort 8.4.0
                      nbformat 5.10.4
                      networkx 3.6.1
                      numba 0.63.1
                      numcodecs 0.16.5
                      numexpr 2.14.1
                      numpy 2.3.5
                      ome_zarr NA
                      packaging 26.0
                      pandas 2.3.3
                      parso 0.8.5
                      partd 1.4.2
                      pathlib_abc NA
                      pkg_resources NA
                      platformdirs 4.5.1
                      pooch v1.9.0
                      prometheus_client NA
                      prompt_toolkit 3.0.52
                      psutil 7.2.2
                      pure_eval 0.2.3
                      pyarrow 22.0.0
                      pycparser 3.00
                      pyct 0.6.0
                      pydev_ipython NA
                      pydevconsole NA
                      pydevd 3.2.3
                      pydevd_file_utils NA
                      pydevd_plugins NA
                      pydevd_tracing NA
                      pygments 2.19.2
                      pyparsing 3.3.2
                      pyproj 3.7.2
                      pythonjsonlogger NA
                      pytz 2025.2
                      referencing NA
                      requests 2.32.5
                      rfc3339_validator 0.1.4
                      rfc3986_validator 0.1.1
                      rfc3987_syntax NA
                      rich NA
                      rpds NA
                      scanpy 1.12
                      scipy 1.16.0
                      send2trash NA
                      shapely 2.1.2
                      six 1.17.0
                      skimage 0.26.0
                      sklearn 1.8.0
                      spatial_image 1.2.3
                      stack_data 0.6.3
                      testing NA
                      threadpoolctl 3.6.0
                      tlz 1.1.0
                      toolz 1.1.0
                      tornado 6.5.4
                      tqdm 4.67.3
                      traitlets 5.14.3
                      typing_extensions NA
                      upath 0.3.9
                      uri_template NA
                      urllib3 2.6.3
                      vscode NA
                      wcwidth 0.5.3
                      webcolors NA
                      websocket 1.9.0
                      xarray 2026.1.0
                      xarray_dataclass 3.0.0
                      xarray_schema 0.0.3
                      xrspatial 0.5.2
                      yaml 6.0.3
                      zarr 3.1.5
                      zmq 27.1.0
                      zoneinfo NA
                      
                      -----
                      IPython 9.10.0
                      jupyter_client 8.8.0
                      jupyter_core 5.9.1
                      jupyterlab 4.5.3
                      notebook 7.5.3
                      -----
                      Python 3.12.12 | packaged by conda-forge | (main, Jan 26 2026, 23:51:32) [GCC 14.3.0]
                      Linux-6.8.0-90-generic-x86_64-with-glibc2.39
                      -----
                      Session information updated at 2026-02-06 13:16
                      

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        No labels
                        No labels

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          Functions relying on transform break with partitioned, dask-backed elements (dask 2025) #1064

                          Description

                          @ppm1337

                          Description

                          After upgrading to spatialdata 0.7.0 and dask 2025, plotting of points and other elements with dask dataframe as the underlying data structure appears to be broken.

                          The issue originates from the calls to transform that render_* functions perform:

                          transform(sdata["transcripts"], to_coordinate_system="global")

                          Results in ValueError: The number of items in 'lengths' does not match the number of partitions. if sdata["transcripts"].npartitions is > 1.

                          MRE

                          Since I am working with Xenium data, I used the xenium_rep1_io data for my MRE listed in your docs.

                          importspatialdataassdimportspatialdata_plotsdata=sd.read_zarr("temp/xenium/example_data/xenium_v1_human_lung/data.zarr")
                          feat=sdata["table"].var_names.tolist()[0]
                          (
                          sdata
                          .pl.render_images("he_image")
                          .pl.render_points(
                          "transcripts",
                          table_name="table",
                          color="feature_name",
                          groups=feat,
                          palette="red",
                          )
                          .pl.show()
                          )

                          Yields ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

                          Expand for stacktrace
                          ---------------------------------------------------------------------------
                          ValueError Traceback (most recent call last)
                          Cell In[41], line 11
                          1 feat = sdata["table"].var_names.tolist()[0]
                          2 (
                          3 sdata.pl.render_images("he_image")
                          4 .pl.render_points(
                          5 "transcripts",
                          6 table_name="table",
                          7 color="feature_name",
                          8 groups=feat,
                          9 palette="red",
                          10 )
                          ---> 11 .pl.show()
                          12 )
                          

                          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata_plot/pl/basic.py:1163, in PlotAccessor.show(self, coordinate_systems, legend_fontsize, legend_fontweight, legend_loc, legend_fontoutline, na_in_legend, colorbar, colorbar_params, wspace, hspace, ncols, frameon, figsize, dpi, fig, title, share_extent, pad_extent, ax, return_ax, save)
                          1160 ax.set_title(t)
                          1161 ax.set_aspect("equal")
                          -> 1163 extent = get_extent(
                          1164 sdata,
                          1165 coordinate_system=cs,
                          1166 has_images=has_images and wants_images,
                          1167 has_labels=has_labels and wants_labels,
                          1168 has_points=has_points and wants_points,
                          1169 has_shapes=has_shapes and wants_shapes,
                          1170 elements=wanted_elements,
                          1171 )
                          1172 cs_x_min, cs_x_max = extent["x"]
                          1173 cs_y_min, cs_y_max = extent["y"]

                          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
                          908 if not args:
                          909 raise TypeError(f'{funcname} requires at least '
                          910 '1 positional argument')
                          --> 912 return dispatch(args[0].class)(*args, **kw)

                          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:213, in _(e, coordinate_system, exact, has_images, has_labels, has_points, has_shapes, elements)
                          211 if coordinate_system in coordinate_systems:
                          212 if isinstance(element_obj, DaskDataFrame | GeoDataFrame):
                          --> 213 extent = get_extent(element_obj, coordinate_system=coordinate_system, exact=exact)
                          214 else:
                          215 extent = get_extent(element_obj, coordinate_system=coordinate_system)

                          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
                          908 if not args:
                          909 raise TypeError(f'{funcname} requires at least '
                          910 '1 positional argument')
                          --> 912 return dispatch(args[0].class)(*args, **kw)

                          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:291, in _(e, coordinate_system, exact)
                          285 extent = _get_extent_of_points(e)
                          286 return _compute_extent_in_coordinate_system(
                          287 element=e,
                          288 coordinate_system=coordinate_system,
                          289 extent=extent,
                          290 )
                          --> 291 transformed = transform(e, to_coordinate_system=coordinate_system)
                          292 return _get_extent_of_points(transformed)

                          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
                          908 if not args:
                          909 raise TypeError(f'{funcname} requires at least '
                          910 '1 positional argument')
                          --> 912 return dispatch(args[0].class)(*args, **kw)

                          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/operations/transform.py:443, in _(data, transformation, maintain_positioning, to_coordinate_system)
                          441 arrays = []
                          442 for ax in axes:
                          --> 443 arrays.append(data[ax].to_dask_array(lengths=True).reshape(-1, 1))
                          444 xdata = DataArray(da.concatenate(arrays, axis=1), coords={"points": range(len(data)), "dim": list(axes)})
                          445 xtransformed = transformation._transform_coordinates(xdata)

                          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:1422, in FrameBase.to_dask_array(self, lengths, meta, optimize, **optimize_kwargs)
                          1418 lengths = tuple(self.map_partitions(len).compute())
                          1420 arr = self.values
                          -> 1422 chunks = self._validate_chunks(arr, lengths)
                          1423 arr._chunks = chunks
                          1425 if meta is not None:

                          File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:2471, in FrameBase._validate_chunks(self, arr, lengths)
                          2468 lengths = tuple(lengths)
                          2470 if len(lengths) != self.npartitions:
                          -> 2471 raise ValueError(
                          2472 "The number of items in 'lengths' does not match the number of "
                          2473 f"partitions. {len(lengths)} != {self.npartitions}"
                          2474 )
                          2476 if self.ndim == 1:
                          2477 chunks = normalize_chunks((lengths,))

                          ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

                          Workaround

                          print(sdata["transcripts"].npartitions)
                          # 4sdata["transcripts"] =sd.models.PointsModel.parse(
                          sdata["transcripts"].repartition(npartitions=1).reset_index(drop=True),
                          transformations={"global": sd.transformations.get_transformation(sdata["transcripts"])},
                          )
                          print(sdata["transcripts"].npartitions)
                          # 1# works without error
                          (
                          sdata
                          .pl.render_images("he_image")
                          .pl.render_points(
                          "transcripts",
                          table_name="table",
                          color="feature_name",
                          groups=feat,
                          palette="red",
                          )
                          .pl.show()
                          )

                          Additional context

                          # workssd.get_extent(sdata["transcripts"], coordinate_system="global", exact=False)
                          # raises ValueErrorsd.get_extent(sdata["transcripts"], coordinate_system="global", exact=True)
                          # originating fromsd.transform(sdata["transcripts"], to_coordinate_system="global")

                          The call to reset_index(drop=True) is necessary in the MRE to avoid ValueError: cannot reindex on an axis with duplicate labels.

                          This issue cannot be reproduced by partitioning the blobs dataset:

                          fromspatialdata.datasetsimportblobssdata=blobs()
                          print(sdata["blobs_points"].npartitions)
                          # 1sdata["blobs_points"] =sd.models.PointsModel.parse(
                          sdata["blobs_points"].repartition(npartitions=4),
                          transformations={"global": sd.transformations.get_transformation(sdata["blobs_points"])},
                          )
                          print(sdata["blobs_points"].npartitions)
                          # 4# workssd.transform(sdata["blobs_points"], to_coordinate_system="global")

                          This error occurs with

                          • spatialdata 0.7.0 + dask 2025.12, dask 2025.9 or dask 2025.2.0

                          This error does not occur with

                          • spatialdata 0.6.1 + dask 2024.11.2

                          Session Info

                          Click to view session information
                          -----
                          dask 2025.12.0
                          session_info v1.0.1
                          spatialdata 0.7.0
                          spatialdata_plot 0.2.14
                          -----
                          
                          Click to view modules imported as dependencies
                          PIL 12.1.0
                          anndata 0.12.9
                          annsel 0.1.2
                          anyio NA
                          argcomplete NA
                          arrow 1.4.0
                          asttokens NA
                          attr 25.4.0
                          attrs 25.4.0
                          babel 2.18.0
                          certifi 2026.01.04
                          cffi 2.0.0
                          charset_normalizer 3.4.4
                          cloudpickle 3.1.2
                          comm 0.2.3
                          cuda 12.9.4
                          cycler 0.12.1
                          cython_runtime NA
                          dask_image NA
                          datashader 0.18.2
                          dateutil 2.9.0.post0
                          debugpy 1.8.20
                          decorator 5.2.1
                          defusedxml 0.7.1
                          donfig 0.8.1.post1
                          executing 2.2.1
                          fast_array_utils NA
                          fastjsonschema NA
                          fqdn NA
                          fsspec 2026.2.0
                          geopandas 1.1.2
                          google_crc32c NA
                          h5py 3.15.1
                          idna 3.11
                          ipykernel 7.1.0
                          ipywidgets 8.1.8
                          isoduration NA
                          jaraco NA
                          jedi 0.19.2
                          jinja2 3.1.6
                          joblib 1.5.3
                          json5 0.13.0
                          jsonpointer 3.0.0
                          jsonschema 4.26.0
                          jsonschema_specifications NA
                          jupyter_events 0.12.0
                          jupyter_server 2.17.0
                          jupyterlab_server 2.28.0
                          kiwisolver 1.4.9
                          lark 1.3.1
                          lazy_loader 0.4
                          legacy_api_wrap NA
                          llvmlite 0.46.0
                          locket NA
                          markupsafe 3.0.3
                          matplotlib 3.10.8
                          matplotlib_inline 0.2.1
                          matplotlib_scalebar 0.9.0
                          more_itertools 10.8.0
                          mpl_toolkits NA
                          msgpack 1.1.2
                          multipledispatch 0.6.0
                          multiscale_spatial_image 2.0.3
                          narwhals 2.16.0
                          natsort 8.4.0
                          nbformat 5.10.4
                          networkx 3.6.1
                          numba 0.63.1
                          numcodecs 0.16.5
                          numexpr 2.14.1
                          numpy 2.3.5
                          ome_zarr NA
                          packaging 26.0
                          pandas 2.3.3
                          parso 0.8.5
                          partd 1.4.2
                          pathlib_abc NA
                          pkg_resources NA
                          platformdirs 4.5.1
                          pooch v1.9.0
                          prometheus_client NA
                          prompt_toolkit 3.0.52
                          psutil 7.2.2
                          pure_eval 0.2.3
                          pyarrow 22.0.0
                          pycparser 3.00
                          pyct 0.6.0
                          pydev_ipython NA
                          pydevconsole NA
                          pydevd 3.2.3
                          pydevd_file_utils NA
                          pydevd_plugins NA
                          pydevd_tracing NA
                          pygments 2.19.2
                          pyparsing 3.3.2
                          pyproj 3.7.2
                          pythonjsonlogger NA
                          pytz 2025.2
                          referencing NA
                          requests 2.32.5
                          rfc3339_validator 0.1.4
                          rfc3986_validator 0.1.1
                          rfc3987_syntax NA
                          rich NA
                          rpds NA
                          scanpy 1.12
                          scipy 1.16.0
                          send2trash NA
                          shapely 2.1.2
                          six 1.17.0
                          skimage 0.26.0
                          sklearn 1.8.0
                          spatial_image 1.2.3
                          stack_data 0.6.3
                          testing NA
                          threadpoolctl 3.6.0
                          tlz 1.1.0
                          toolz 1.1.0
                          tornado 6.5.4
                          tqdm 4.67.3
                          traitlets 5.14.3
                          typing_extensions NA
                          upath 0.3.9
                          uri_template NA
                          urllib3 2.6.3
                          vscode NA
                          wcwidth 0.5.3
                          webcolors NA
                          websocket 1.9.0
                          xarray 2026.1.0
                          xarray_dataclass 3.0.0
                          xarray_schema 0.0.3
                          xrspatial 0.5.2
                          yaml 6.0.3
                          zarr 3.1.5
                          zmq 27.1.0
                          zoneinfo NA
                          
                          -----
                          IPython 9.10.0
                          jupyter_client 8.8.0
                          jupyter_core 5.9.1
                          jupyterlab 4.5.3
                          notebook 7.5.3
                          -----
                          Python 3.12.12 | packaged by conda-forge | (main, Jan 26 2026, 23:51:32) [GCC 14.3.0]
                          Linux-6.8.0-90-generic-x86_64-with-glibc2.39
                          -----
                          Session information updated at 2026-02-06 13:16
                          

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            No labels
                            No labels

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              Functions relying on transform break with partitioned, dask-backed elements (dask 2025) #1064

                              Description

                              @ppm1337

                              Description

                              After upgrading to spatialdata 0.7.0 and dask 2025, plotting of points and other elements with dask dataframe as the underlying data structure appears to be broken.

                              The issue originates from the calls to transform that render_* functions perform:

                              transform(sdata["transcripts"], to_coordinate_system="global")

                              Results in ValueError: The number of items in 'lengths' does not match the number of partitions. if sdata["transcripts"].npartitions is > 1.

                              MRE

                              Since I am working with Xenium data, I used the xenium_rep1_io data for my MRE listed in your docs.

                              importspatialdataassdimportspatialdata_plotsdata=sd.read_zarr("temp/xenium/example_data/xenium_v1_human_lung/data.zarr")
                              feat=sdata["table"].var_names.tolist()[0]
                              (
                              sdata
                              .pl.render_images("he_image")
                              .pl.render_points(
                              "transcripts",
                              table_name="table",
                              color="feature_name",
                              groups=feat,
                              palette="red",
                              )
                              .pl.show()
                              )

                              Yields ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

                              Expand for stacktrace
                              ---------------------------------------------------------------------------
                              ValueError Traceback (most recent call last)
                              Cell In[41], line 11
                              1 feat = sdata["table"].var_names.tolist()[0]
                              2 (
                              3 sdata.pl.render_images("he_image")
                              4 .pl.render_points(
                              5 "transcripts",
                              6 table_name="table",
                              7 color="feature_name",
                              8 groups=feat,
                              9 palette="red",
                              10 )
                              ---> 11 .pl.show()
                              12 )
                              

                              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata_plot/pl/basic.py:1163, in PlotAccessor.show(self, coordinate_systems, legend_fontsize, legend_fontweight, legend_loc, legend_fontoutline, na_in_legend, colorbar, colorbar_params, wspace, hspace, ncols, frameon, figsize, dpi, fig, title, share_extent, pad_extent, ax, return_ax, save)
                              1160 ax.set_title(t)
                              1161 ax.set_aspect("equal")
                              -> 1163 extent = get_extent(
                              1164 sdata,
                              1165 coordinate_system=cs,
                              1166 has_images=has_images and wants_images,
                              1167 has_labels=has_labels and wants_labels,
                              1168 has_points=has_points and wants_points,
                              1169 has_shapes=has_shapes and wants_shapes,
                              1170 elements=wanted_elements,
                              1171 )
                              1172 cs_x_min, cs_x_max = extent["x"]
                              1173 cs_y_min, cs_y_max = extent["y"]

                              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
                              908 if not args:
                              909 raise TypeError(f'{funcname} requires at least '
                              910 '1 positional argument')
                              --> 912 return dispatch(args[0].class)(*args, **kw)

                              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:213, in _(e, coordinate_system, exact, has_images, has_labels, has_points, has_shapes, elements)
                              211 if coordinate_system in coordinate_systems:
                              212 if isinstance(element_obj, DaskDataFrame | GeoDataFrame):
                              --> 213 extent = get_extent(element_obj, coordinate_system=coordinate_system, exact=exact)
                              214 else:
                              215 extent = get_extent(element_obj, coordinate_system=coordinate_system)

                              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
                              908 if not args:
                              909 raise TypeError(f'{funcname} requires at least '
                              910 '1 positional argument')
                              --> 912 return dispatch(args[0].class)(*args, **kw)

                              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/data_extent.py:291, in _(e, coordinate_system, exact)
                              285 extent = _get_extent_of_points(e)
                              286 return _compute_extent_in_coordinate_system(
                              287 element=e,
                              288 coordinate_system=coordinate_system,
                              289 extent=extent,
                              290 )
                              --> 291 transformed = transform(e, to_coordinate_system=coordinate_system)
                              292 return _get_extent_of_points(transformed)

                              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/functools.py:912, in singledispatch..wrapper(*args, **kw)
                              908 if not args:
                              909 raise TypeError(f'{funcname} requires at least '
                              910 '1 positional argument')
                              --> 912 return dispatch(args[0].class)(*args, **kw)

                              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/spatialdata/_core/operations/transform.py:443, in _(data, transformation, maintain_positioning, to_coordinate_system)
                              441 arrays = []
                              442 for ax in axes:
                              --> 443 arrays.append(data[ax].to_dask_array(lengths=True).reshape(-1, 1))
                              444 xdata = DataArray(da.concatenate(arrays, axis=1), coords={"points": range(len(data)), "dim": list(axes)})
                              445 xtransformed = transformation._transform_coordinates(xdata)

                              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:1422, in FrameBase.to_dask_array(self, lengths, meta, optimize, **optimize_kwargs)
                              1418 lengths = tuple(self.map_partitions(len).compute())
                              1420 arr = self.values
                              -> 1422 chunks = self._validate_chunks(arr, lengths)
                              1423 arr._chunks = chunks
                              1425 if meta is not None:

                              File ~/projects/ddr_st/ddr_st_xenium/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:2471, in FrameBase._validate_chunks(self, arr, lengths)
                              2468 lengths = tuple(lengths)
                              2470 if len(lengths) != self.npartitions:
                              -> 2471 raise ValueError(
                              2472 "The number of items in 'lengths' does not match the number of "
                              2473 f"partitions. {len(lengths)} != {self.npartitions}"
                              2474 )
                              2476 if self.ndim == 1:
                              2477 chunks = normalize_chunks((lengths,))

                              ValueError: The number of items in 'lengths' does not match the number of partitions. 2 != 4

                              Workaround

                              print(sdata["transcripts"].npartitions)
                              # 4sdata["transcripts"] =sd.models.PointsModel.parse(
                              sdata["transcripts"].repartition(npartitions=1).reset_index(drop=True),
                              transformations={"global": sd.transformations.get_transformation(sdata["transcripts"])},
                              )
                              print(sdata["transcripts"].npartitions)
                              # 1# works without error
                              (
                              sdata
                              .pl.render_images("he_image")
                              .pl.render_points(
                              "transcripts",
                              table_name="table",
                              color="feature_name",
                              groups=feat,
                              palette="red",
                              )
                              .pl.show()
                              )

                              Additional context

                              # workssd.get_extent(sdata["transcripts"], coordinate_system="global", exact=False)
                              # raises ValueErrorsd.get_extent(sdata["transcripts"], coordinate_system="global", exact=True)
                              # originating fromsd.transform(sdata["transcripts"], to_coordinate_system="global")

                              The call to reset_index(drop=True) is necessary in the MRE to avoid ValueError: cannot reindex on an axis with duplicate labels.

                              This issue cannot be reproduced by partitioning the blobs dataset:

                              fromspatialdata.datasetsimportblobssdata=blobs()
                              print(sdata["blobs_points"].npartitions)
                              # 1sdata["blobs_points"] =sd.models.PointsModel.parse(
                              sdata["blobs_points"].repartition(npartitions=4),
                              transformations={"global": sd.transformations.get_transformation(sdata["blobs_points"])},
                              )
                              print(sdata["blobs_points"].npartitions)
                              # 4# workssd.transform(sdata["blobs_points"], to_coordinate_system="global")

                              This error occurs with

                              • spatialdata 0.7.0 + dask 2025.12, dask 2025.9 or dask 2025.2.0

                              This error does not occur with

                              • spatialdata 0.6.1 + dask 2024.11.2

                              Session Info

                              Click to view session information
                              -----
                              dask 2025.12.0
                              session_info v1.0.1
                              spatialdata 0.7.0
                              spatialdata_plot 0.2.14
                              -----
                              
                              Click to view modules imported as dependencies
                              PIL 12.1.0
                              anndata 0.12.9
                              annsel 0.1.2
                              anyio NA
                              argcomplete NA
                              arrow 1.4.0
                              asttokens NA
                              attr 25.4.0
                              attrs 25.4.0
                              babel 2.18.0
                              certifi 2026.01.04
                              cffi 2.0.0
                              charset_normalizer 3.4.4
                              cloudpickle 3.1.2
                              comm 0.2.3
                              cuda 12.9.4
                              cycler 0.12.1
                              cython_runtime NA
                              dask_image NA
                              datashader 0.18.2
                              dateutil 2.9.0.post0
                              debugpy 1.8.20
                              decorator 5.2.1
                              defusedxml 0.7.1
                              donfig 0.8.1.post1
                              executing 2.2.1
                              fast_array_utils NA
                              fastjsonschema NA
                              fqdn NA
                              fsspec 2026.2.0
                              geopandas 1.1.2
                              google_crc32c NA
                              h5py 3.15.1
                              idna 3.11
                              ipykernel 7.1.0
                              ipywidgets 8.1.8
                              isoduration NA
                              jaraco NA
                              jedi 0.19.2
                              jinja2 3.1.6
                              joblib 1.5.3
                              json5 0.13.0
                              jsonpointer 3.0.0
                              jsonschema 4.26.0
                              jsonschema_specifications NA
                              jupyter_events 0.12.0
                              jupyter_server 2.17.0
                              jupyterlab_server 2.28.0
                              kiwisolver 1.4.9
                              lark 1.3.1
                              lazy_loader 0.4
                              legacy_api_wrap NA
                              llvmlite 0.46.0
                              locket NA
                              markupsafe 3.0.3
                              matplotlib 3.10.8
                              matplotlib_inline 0.2.1
                              matplotlib_scalebar 0.9.0
                              more_itertools 10.8.0
                              mpl_toolkits NA
                              msgpack 1.1.2
                              multipledispatch 0.6.0
                              multiscale_spatial_image 2.0.3
                              narwhals 2.16.0
                              natsort 8.4.0
                              nbformat 5.10.4
                              networkx 3.6.1
                              numba 0.63.1
                              numcodecs 0.16.5
                              numexpr 2.14.1
                              numpy 2.3.5
                              ome_zarr NA
                              packaging 26.0
                              pandas 2.3.3
                              parso 0.8.5
                              partd 1.4.2
                              pathlib_abc NA
                              pkg_resources NA
                              platformdirs 4.5.1
                              pooch v1.9.0
                              prometheus_client NA
                              prompt_toolkit 3.0.52
                              psutil 7.2.2
                              pure_eval 0.2.3
                              pyarrow 22.0.0
                              pycparser 3.00
                              pyct 0.6.0
                              pydev_ipython NA
                              pydevconsole NA
                              pydevd 3.2.3
                              pydevd_file_utils NA
                              pydevd_plugins NA
                              pydevd_tracing NA
                              pygments 2.19.2
                              pyparsing 3.3.2
                              pyproj 3.7.2
                              pythonjsonlogger NA
                              pytz 2025.2
                              referencing NA
                              requests 2.32.5
                              rfc3339_validator 0.1.4
                              rfc3986_validator 0.1.1
                              rfc3987_syntax NA
                              rich NA
                              rpds NA
                              scanpy 1.12
                              scipy 1.16.0
                              send2trash NA
                              shapely 2.1.2
                              six 1.17.0
                              skimage 0.26.0
                              sklearn 1.8.0
                              spatial_image 1.2.3
                              stack_data 0.6.3
                              testing NA
                              threadpoolctl 3.6.0
                              tlz 1.1.0
                              toolz 1.1.0
                              tornado 6.5.4
                              tqdm 4.67.3
                              traitlets 5.14.3
                              typing_extensions NA
                              upath 0.3.9
                              uri_template NA
                              urllib3 2.6.3
                              vscode NA
                              wcwidth 0.5.3
                              webcolors NA
                              websocket 1.9.0
                              xarray 2026.1.0
                              xarray_dataclass 3.0.0
                              xarray_schema 0.0.3
                              xrspatial 0.5.2
                              yaml 6.0.3
                              zarr 3.1.5
                              zmq 27.1.0
                              zoneinfo NA
                              
                              -----
                              IPython 9.10.0
                              jupyter_client 8.8.0
                              jupyter_core 5.9.1
                              jupyterlab 4.5.3
                              notebook 7.5.3
                              -----
                              Python 3.12.12 | packaged by conda-forge | (main, Jan 26 2026, 23:51:32) [GCC 14.3.0]
                              Linux-6.8.0-90-generic-x86_64-with-glibc2.39
                              -----
                              Session information updated at 2026-02-06 13:16
                              

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                No labels
                                No labels

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions