thyra 2.0.1__tar.gz → 2.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. {thyra-2.0.1 → thyra-2.0.3}/PKG-INFO +1 -1
  2. {thyra-2.0.1 → thyra-2.0.3}/pyproject.toml +1 -1
  3. {thyra-2.0.1 → thyra-2.0.3}/thyra/__init__.py +1 -1
  4. {thyra-2.0.1 → thyra-2.0.3}/thyra/converters/spatialdata/streaming_converter.py +56 -11
  5. {thyra-2.0.1 → thyra-2.0.3}/LICENSE +0 -0
  6. {thyra-2.0.1 → thyra-2.0.3}/README.md +0 -0
  7. {thyra-2.0.1 → thyra-2.0.3}/thyra/__main__.py +0 -0
  8. {thyra-2.0.1 → thyra-2.0.3}/thyra/alignment/__init__.py +0 -0
  9. {thyra-2.0.1 → thyra-2.0.3}/thyra/alignment/affine.py +0 -0
  10. {thyra-2.0.1 → thyra-2.0.3}/thyra/alignment/teaching_points.py +0 -0
  11. {thyra-2.0.1 → thyra-2.0.3}/thyra/config.py +0 -0
  12. {thyra-2.0.1 → thyra-2.0.3}/thyra/convert.py +0 -0
  13. {thyra-2.0.1 → thyra-2.0.3}/thyra/converters/__init__.py +0 -0
  14. {thyra-2.0.1 → thyra-2.0.3}/thyra/converters/spatialdata/__init__.py +0 -0
  15. {thyra-2.0.1 → thyra-2.0.3}/thyra/converters/spatialdata/_chunking.py +0 -0
  16. {thyra-2.0.1 → thyra-2.0.3}/thyra/converters/spatialdata/base_spatialdata_converter.py +0 -0
  17. {thyra-2.0.1 → thyra-2.0.3}/thyra/converters/spatialdata/converter.py +0 -0
  18. {thyra-2.0.1 → thyra-2.0.3}/thyra/converters/spatialdata/spatialdata_2d_converter.py +0 -0
  19. {thyra-2.0.1 → thyra-2.0.3}/thyra/converters/spatialdata/spatialdata_3d_converter.py +0 -0
  20. {thyra-2.0.1 → thyra-2.0.3}/thyra/core/__init__.py +0 -0
  21. {thyra-2.0.1 → thyra-2.0.3}/thyra/core/base_converter.py +0 -0
  22. {thyra-2.0.1 → thyra-2.0.3}/thyra/core/base_extractor.py +0 -0
  23. {thyra-2.0.1 → thyra-2.0.3}/thyra/core/base_reader.py +0 -0
  24. {thyra-2.0.1 → thyra-2.0.3}/thyra/core/registry.py +0 -0
  25. {thyra-2.0.1 → thyra-2.0.3}/thyra/metadata/__init__.py +0 -0
  26. {thyra-2.0.1 → thyra-2.0.3}/thyra/metadata/extractors/__init__.py +0 -0
  27. {thyra-2.0.1 → thyra-2.0.3}/thyra/metadata/extractors/bruker_extractor.py +0 -0
  28. {thyra-2.0.1 → thyra-2.0.3}/thyra/metadata/extractors/imzml_extractor.py +0 -0
  29. {thyra-2.0.1 → thyra-2.0.3}/thyra/metadata/extractors/waters_extractor.py +0 -0
  30. {thyra-2.0.1 → thyra-2.0.3}/thyra/metadata/ontology/__init__.py +0 -0
  31. {thyra-2.0.1 → thyra-2.0.3}/thyra/metadata/ontology/_ims.py +0 -0
  32. {thyra-2.0.1 → thyra-2.0.3}/thyra/metadata/ontology/_ms.py +0 -0
  33. {thyra-2.0.1 → thyra-2.0.3}/thyra/metadata/ontology/_uo.py +0 -0
  34. {thyra-2.0.1 → thyra-2.0.3}/thyra/metadata/ontology/cache.py +0 -0
  35. {thyra-2.0.1 → thyra-2.0.3}/thyra/metadata/types.py +0 -0
  36. {thyra-2.0.1 → thyra-2.0.3}/thyra/metadata/validator.py +0 -0
  37. {thyra-2.0.1 → thyra-2.0.3}/thyra/preview.py +0 -0
  38. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/__init__.py +0 -0
  39. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/__init__.py +0 -0
  40. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/base_bruker_reader.py +0 -0
  41. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/folder_structure.py +0 -0
  42. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/mis_parser.py +0 -0
  43. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/rapiflex/__init__.py +0 -0
  44. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/rapiflex/rapiflex_reader.py +0 -0
  45. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/__init__.py +0 -0
  46. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/sdk/__init__.py +0 -0
  47. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/sdk/dll/LICENCE-BRUKER.txt +0 -0
  48. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/sdk/dll/README.md +0 -0
  49. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/sdk/dll/timsdata.dll +0 -0
  50. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/sdk/dll/timsdata.so +0 -0
  51. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/sdk/dll_manager.py +0 -0
  52. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/sdk/platform_detector.py +0 -0
  53. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/sdk/sdk_functions.py +0 -0
  54. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/timstof_reader.py +0 -0
  55. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/utils/__init__.py +0 -0
  56. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/utils/batch_processor.py +0 -0
  57. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/utils/coordinate_cache.py +0 -0
  58. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/utils/mass_axis_builder.py +0 -0
  59. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/bruker/timstof/utils/memory_manager.py +0 -0
  60. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/imzml/__init__.py +0 -0
  61. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/imzml/imzml_reader.py +0 -0
  62. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/waters/__init__.py +0 -0
  63. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/waters/imaging_grid.py +0 -0
  64. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/waters/lib/MLReader.dll +0 -0
  65. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/waters/lib/MassLynxRaw.dll +0 -0
  66. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/waters/lib/libMLReader.so +0 -0
  67. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/waters/lib/libMassLynxRaw.so +0 -0
  68. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/waters/masslynx_lib.py +0 -0
  69. {thyra-2.0.1 → thyra-2.0.3}/thyra/readers/waters/waters_reader.py +0 -0
  70. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/__init__.py +0 -0
  71. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/common_axis.py +0 -0
  72. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/constants.py +0 -0
  73. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/data_characteristics.py +0 -0
  74. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/decision_tree.py +0 -0
  75. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/instrument_detectors.py +0 -0
  76. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/mass_axis/__init__.py +0 -0
  77. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/mass_axis/base_generator.py +0 -0
  78. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/mass_axis/fticr_generator.py +0 -0
  79. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/mass_axis/linear_generator.py +0 -0
  80. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/mass_axis/linear_tof_generator.py +0 -0
  81. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/mass_axis/orbitrap_generator.py +0 -0
  82. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/mass_axis/reflector_tof_generator.py +0 -0
  83. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/strategies/__init__.py +0 -0
  84. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/strategies/base.py +0 -0
  85. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/strategies/nearest_neighbor.py +0 -0
  86. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/strategies/tic_preserving.py +0 -0
  87. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/tic.py +0 -0
  88. {thyra-2.0.1 → thyra-2.0.3}/thyra/resampling/types.py +0 -0
  89. {thyra-2.0.1 → thyra-2.0.3}/thyra/tools/__init__.py +0 -0
  90. {thyra-2.0.1 → thyra-2.0.3}/thyra/tools/check_ontology.py +0 -0
  91. {thyra-2.0.1 → thyra-2.0.3}/thyra/tools/make_example_data.py +0 -0
  92. {thyra-2.0.1 → thyra-2.0.3}/thyra/utils/__init__.py +0 -0
  93. {thyra-2.0.1 → thyra-2.0.3}/thyra/utils/bruker_exceptions.py +0 -0
  94. {thyra-2.0.1 → thyra-2.0.3}/thyra/utils/logging_config.py +0 -0
  95. {thyra-2.0.1 → thyra-2.0.3}/thyra/utils/windows_paths.py +0 -0
  96. {thyra-2.0.1 → thyra-2.0.3}/thyra/utils/zarr_atomic_write.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thyra
3
- Version: 2.0.1
3
+ Version: 2.0.3
4
4
  Summary: A modern Python library for converting Mass Spectrometry Imaging (MSI) data into SpatialData/Zarr format - your portal to spatial omics
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "thyra"
7
- version = "2.0.1"
7
+ version = "2.0.3"
8
8
  description = "A modern Python library for converting Mass Spectrometry Imaging (MSI) data into SpatialData/Zarr format - your portal to spatial omics"
9
9
  authors = ["Theodoros Visvikis <t.visvikis@maastrichtuniversity.nl>"]
10
10
  maintainers = ["Theodoros Visvikis <t.visvikis@maastrichtuniversity.nl>"]
@@ -36,7 +36,7 @@ warnings.filterwarnings(
36
36
  category=FutureWarning,
37
37
  )
38
38
 
39
- __version__ = "2.0.1"
39
+ __version__ = "2.0.3"
40
40
 
41
41
  # Import key components - avoid wildcard imports
42
42
  try:
@@ -40,6 +40,12 @@ if SPATIALDATA_AVAILABLE:
40
40
 
41
41
  logger = logging.getLogger(__name__)
42
42
 
43
+ # Elements per chunk when building the string index arrays. Bounds the
44
+ # transient cost of formatting to this many entries rather than the whole
45
+ # axis; measured at 17.5 bytes per entry at peak against 88 for a one-shot
46
+ # build, and 32 for an unchunked vectorised one.
47
+ _INDEX_BUILD_CHUNK = 1_000_000
48
+
43
49
 
44
50
  class StreamingSpatialDataConverter(BaseSpatialDataConverter):
45
51
  """Memory-efficient streaming converter for MSI data to SpatialData format.
@@ -123,10 +129,21 @@ class StreamingSpatialDataConverter(BaseSpatialDataConverter):
123
129
  n_x, n_y, n_z = metadata.dimensions
124
130
  n_pixels = n_x * n_y * n_z
125
131
 
126
- # Estimate number of m/z bins after resampling using the same
127
- # resolution path the axis builder will use, so the gate against
128
- # PCS_SIZE_THRESHOLD_GB sees the real bin count.
129
- if self._resampling_config:
132
+ # Prefer the axis that was actually built. ``convert()`` runs
133
+ # ``_initialize_conversion()`` -- and so ``_setup_mass_axis()`` --
134
+ # before it asks which method to use, so by the time this gate is
135
+ # evaluated the real bin count is known and there is nothing to
136
+ # estimate. That matters most on the raw-axis path, where the
137
+ # fallback below assumes a 10 mDa spacing that the data need not
138
+ # have: a continuous file carrying 4,000 points over 250-1200 m/z
139
+ # was scored as though it had 95,000, and a processed file whose
140
+ # spectra share no m/z values was scored far too low, which routed
141
+ # the largest datasets to the method that holds the most in memory.
142
+ if self._common_mass_axis is not None:
143
+ n_mz_bins = len(self._common_mass_axis)
144
+ elif self._resampling_config:
145
+ # Same resolution path the axis builder will use, so the gate
146
+ # against PCS_SIZE_THRESHOLD_GB sees the real bin count.
130
147
  _, _, _, n_mz_bins = self._resolve_resampling_plan()
131
148
  else:
132
149
  min_mass, max_mass = metadata.mass_range
@@ -491,11 +508,19 @@ class StreamingSpatialDataConverter(BaseSpatialDataConverter):
491
508
  indptr_arr.attrs["encoding-type"] = "array"
492
509
  indptr_arr.attrs["encoding-version"] = "0.2.0"
493
510
 
511
+ # Column indices are bounded by n_cols, not by total_nnz, so they need
512
+ # their own dtype decision. Hardcoding int32 here wrapped silently
513
+ # above 2,147,483,647 m/z bins: zarr truncates an oversized write
514
+ # without warning, and the resulting negative indices survive all the
515
+ # way into scipy without an error. This is the same switch point scipy
516
+ # uses, so the matrix rebuilt from these arrays needs no cast.
517
+ indices_dtype = np.int64 if n_cols > np.iinfo(np.int32).max else np.int32
518
+
494
519
  chunk_size_zarr = min(total_nnz, 1000000)
495
520
  indices_arr = X_group.create_array(
496
521
  "indices",
497
522
  shape=(total_nnz,),
498
- dtype=np.int32,
523
+ dtype=indices_dtype,
499
524
  chunks=(chunk_size_zarr,),
500
525
  )
501
526
  indices_arr.attrs["encoding-type"] = "array"
@@ -566,7 +591,11 @@ class StreamingSpatialDataConverter(BaseSpatialDataConverter):
566
591
  pos = write_pos[pixel_idx]
567
592
  positions = np.arange(pos, pos + nnz)
568
593
  buf_positions.append(positions)
569
- buf_indices.append(mz_indices.astype(np.int32))
594
+ # Take the dtype from the destination array rather than
595
+ # re-deriving it, so the buffer and the store cannot drift
596
+ # apart. Zarr accepts an oversized write and truncates it
597
+ # silently, so a mismatch here would be invisible.
598
+ buf_indices.append(mz_indices.astype(indices_arr.dtype))
570
599
  buf_data.append(resampled_ints.astype(np.float64))
571
600
  write_pos[pixel_idx] += nnz
572
601
  buf_size += nnz
@@ -716,11 +745,15 @@ class StreamingSpatialDataConverter(BaseSpatialDataConverter):
716
745
 
717
746
  logger.info(f"Loaded CSR components: {len(data):,} entries")
718
747
 
719
- # For large datasets (>2.1B entries), ensure 64-bit indices for scipy
748
+ # indices already carries the dtype chosen at write time, keyed on the
749
+ # column count (see _coo_setup_zarr_arrays), so there is nothing to fix
750
+ # up here. Upcasting it was never a safeguard anyway: it widened values
751
+ # that had already been truncated on the way in, and it was keyed on
752
+ # the wrong quantity. indptr is bounded by nnz, hence the check below.
753
+ # copy=False makes the no-op case free rather than a full-size copy.
720
754
  if len(data) > np.iinfo(np.int32).max:
721
755
  logger.info("Large dataset detected, using 64-bit sparse matrix indices")
722
- indptr = indptr.astype(np.int64)
723
- indices = indices.astype(np.int64)
756
+ indptr = indptr.astype(np.int64, copy=False)
724
757
 
725
758
  # Create CSR matrix directly (no COO intermediate)
726
759
  sparse_matrix: Union[sparse.csr_matrix, sparse.csc_matrix] = sparse.csr_matrix(
@@ -1417,7 +1450,9 @@ class StreamingSpatialDataConverter(BaseSpatialDataConverter):
1417
1450
 
1418
1451
  y_values = np.repeat(np.arange(n_y, dtype=np.int32), n_x)
1419
1452
  x_values = np.tile(np.arange(n_x, dtype=np.int32), n_y)
1420
- instance_ids = np.array([str(i) for i in range(n_rows)], dtype=str_dtype)
1453
+ # Same reasoning as the var index below, but n_rows is the pixel count
1454
+ # and stays far smaller than n_cols, so one shot needs no chunking.
1455
+ instance_ids = np.arange(n_rows, dtype=np.int64).astype(str_dtype)
1421
1456
  spatial_x = x_values.astype(np.float64) * self.pixel_size_um
1422
1457
  spatial_y = y_values.astype(np.float64) * self.pixel_size_um
1423
1458
 
@@ -1464,7 +1499,17 @@ class StreamingSpatialDataConverter(BaseSpatialDataConverter):
1464
1499
  mz_values = self._common_mass_axis
1465
1500
  if mz_values is None:
1466
1501
  raise RuntimeError("Common mass axis not initialized")
1467
- mz_index = np.array([f"mz_{i}" for i in range(n_cols)], dtype=str_dtype)
1502
+ # Built in chunks rather than from a list comprehension. Materialising
1503
+ # n_cols Python str objects first costs about 88 bytes per entry at
1504
+ # peak, against 17.5 for this; at 10 million bins that is 883 MB
1505
+ # versus 175 MB, on a path whose docstring promises roughly 200 MB
1506
+ # regardless of dataset size. The values are identical.
1507
+ mz_index = np.empty(n_cols, dtype=str_dtype)
1508
+ for start in range(0, n_cols, _INDEX_BUILD_CHUNK):
1509
+ stop = min(start + _INDEX_BUILD_CHUNK, n_cols)
1510
+ mz_index[start:stop] = np.strings.add(
1511
+ "mz_", np.arange(start, stop, dtype=np.int64).astype(str_dtype)
1512
+ )
1468
1513
  a = var_group.create_array("_index", data=mz_index)
1469
1514
  a.attrs["encoding-type"] = "string-array"
1470
1515
  a.attrs["encoding-version"] = "0.2.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes