webknossos 4.2.2__tar.gz → 4.2.4__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 (178) hide show
  1. {webknossos-4.2.2 → webknossos-4.2.4}/PKG-INFO +2 -2
  2. {webknossos-4.2.2 → webknossos-4.2.4}/pyproject.toml +2 -2
  3. {webknossos-4.2.2 → webknossos-4.2.4}/tests/test_cli.py +41 -1
  4. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/annotation/annotation_info.py +2 -0
  5. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/export_as_tiff.py +2 -2
  6. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/api_client/models.py +2 -1
  7. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/export.py +8 -1
  8. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/layer.py +6 -0
  9. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/view/_array.py +48 -18
  10. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/view/mag_view.py +30 -6
  11. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/view/remote_mag_view.py +10 -0
  12. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/view/view.py +58 -4
  13. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset_properties/dataset_properties.py +5 -0
  14. webknossos-4.2.4/webknossos/version.py +1 -0
  15. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos.egg-info/PKG-INFO +2 -2
  16. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos.egg-info/requires.txt +1 -1
  17. webknossos-4.2.2/webknossos/version.py +0 -1
  18. {webknossos-4.2.2 → webknossos-4.2.4}/README.md +0 -0
  19. {webknossos-4.2.2 → webknossos-4.2.4}/setup.cfg +0 -0
  20. {webknossos-4.2.2 → webknossos-4.2.4}/tests/test_annotation.py +0 -0
  21. {webknossos-4.2.2 → webknossos-4.2.4}/tests/test_api_client.py +0 -0
  22. {webknossos-4.2.2 → webknossos-4.2.4}/tests/test_cli_utils.py +0 -0
  23. {webknossos-4.2.2 → webknossos-4.2.4}/tests/test_skeleton.py +0 -0
  24. {webknossos-4.2.2 → webknossos-4.2.4}/tests/test_utils.py +0 -0
  25. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/__init__.py +0 -0
  26. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/__init__.py +0 -0
  27. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/branchpoint.py +0 -0
  28. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/comment.py +0 -0
  29. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/edge.py +0 -0
  30. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/group.py +0 -0
  31. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/meta.py +0 -0
  32. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/metadata_entry.py +0 -0
  33. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/nml.py +0 -0
  34. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/node.py +0 -0
  35. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/parameters.py +0 -0
  36. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/segment.py +0 -0
  37. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/tree.py +0 -0
  38. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/utils.py +0 -0
  39. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/_nml/volume.py +0 -0
  40. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/administration/__init__.py +0 -0
  41. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/administration/project.py +0 -0
  42. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/administration/task.py +0 -0
  43. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/administration/team.py +0 -0
  44. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/administration/user.py +0 -0
  45. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/ai_model/__init__.py +0 -0
  46. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/ai_model/ai_model_category.py +0 -0
  47. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/ai_model/remote_ai_model.py +0 -0
  48. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/annotation/__init__.py +0 -0
  49. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/annotation/_nml_conversion.py +0 -0
  50. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/annotation/annotation.py +0 -0
  51. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/annotation/volume_layer.py +0 -0
  52. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/__init__.py +0 -0
  53. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/_utils.py +0 -0
  54. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/check_equality.py +0 -0
  55. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/compress.py +0 -0
  56. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/convert.py +0 -0
  57. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/convert_knossos.py +0 -0
  58. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/convert_raw.py +0 -0
  59. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/convert_zarr.py +0 -0
  60. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/copy_dataset.py +0 -0
  61. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/download.py +0 -0
  62. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/downsample.py +0 -0
  63. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/main.py +0 -0
  64. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/merge_fallback.py +0 -0
  65. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/upload.py +0 -0
  66. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/cli/upsample.py +0 -0
  67. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/__init__.py +0 -0
  68. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/_defaults.py +0 -0
  69. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/_download_dataset.py +0 -0
  70. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/_resolve_short_link.py +0 -0
  71. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/_resumable/__init__.py +0 -0
  72. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/_resumable/chunk.py +0 -0
  73. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/_resumable/core.py +0 -0
  74. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/_resumable/file.py +0 -0
  75. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/_resumable/util.py +0 -0
  76. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/_upload_dataset.py +0 -0
  77. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/api_client/__init__.py +0 -0
  78. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/api_client/_abstract_api_client.py +0 -0
  79. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/api_client/_serialization.py +0 -0
  80. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/api_client/datastore_api_client.py +0 -0
  81. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/api_client/errors.py +0 -0
  82. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/api_client/tracingstore_api_client.py +0 -0
  83. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/api_client/wk_api_client.py +0 -0
  84. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/client/context.py +0 -0
  85. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/__init__.py +0 -0
  86. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/__init__.py +0 -0
  87. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/chunked_image_source.py +0 -0
  88. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/common_slice_readers.py +0 -0
  89. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/czi_image_source.py +0 -0
  90. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/dm_slice_reader.py +0 -0
  91. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/image_conversion.py +0 -0
  92. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/image_source.py +0 -0
  93. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/image_source_registry.py +0 -0
  94. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/ims_image_source.py +0 -0
  95. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/infer_bounding_box_existing_files.py +0 -0
  96. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/mrc_image_source.py +0 -0
  97. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/n5_image_source.py +0 -0
  98. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/neuroglancer_precomputed_image_source.py +0 -0
  99. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/ome_zarr_helpers.py +0 -0
  100. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/ozx_image_source.py +0 -0
  101. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/segmentation_recognition.py +0 -0
  102. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/slice_reader.py +0 -0
  103. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/sliced_image_source.py +0 -0
  104. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/tensorstore_chunked_image_source.py +0 -0
  105. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/tiff_slice_reader.py +0 -0
  106. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/vendor/dm3.py +0 -0
  107. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/vendor/dm4.py +0 -0
  108. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_image_conversion/zarr_image_source.py +0 -0
  109. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_metadata.py +0 -0
  110. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_utils/__init__.py +0 -0
  111. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_utils/buffered_slice_reader.py +0 -0
  112. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_utils/buffered_slice_writer.py +0 -0
  113. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_utils/tensorstore_helpers.py +0 -0
  114. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/_utils/tinywkw.py +0 -0
  115. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/abstract_dataset.py +0 -0
  116. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/dataset.py +0 -0
  117. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/defaults.py +0 -0
  118. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/errors.py +0 -0
  119. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/__init__.py +0 -0
  120. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/_downsampling_numba.py +0 -0
  121. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/_downsampling_utils.py +0 -0
  122. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/_transform_utils.py +0 -0
  123. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/_upsampling_utils.py +0 -0
  124. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/abstract_layer.py +0 -0
  125. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/layer_to_link.py +0 -0
  126. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/remote_layer.py +0 -0
  127. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/segmentation_layer/__init__.py +0 -0
  128. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/segmentation_layer/abstract_segmentation_layer.py +0 -0
  129. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/segmentation_layer/attachments/__init__.py +0 -0
  130. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/segmentation_layer/attachments/agglomerate_attachment.py +0 -0
  131. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/segmentation_layer/attachments/attachment.py +0 -0
  132. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/segmentation_layer/attachments/attachments.py +0 -0
  133. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/segmentation_layer/remote_segmentation_layer.py +0 -0
  134. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/segmentation_layer/segmentation_layer.py +0 -0
  135. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/layer/view/__init__.py +0 -0
  136. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/ome_metadata.py +0 -0
  137. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/remote_access_mode.py +0 -0
  138. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/remote_dataset.py +0 -0
  139. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/remote_dataset_registry.py +0 -0
  140. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/remote_folder.py +0 -0
  141. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/sampling_modes.py +0 -0
  142. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset/transfer_mode.py +0 -0
  143. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset_properties/__init__.py +0 -0
  144. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset_properties/_bigint_envelope.py +0 -0
  145. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset_properties/coordinate_transformations.py +0 -0
  146. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset_properties/data_format.py +0 -0
  147. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset_properties/dtype_conversion.py +0 -0
  148. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset_properties/layer_categories.py +0 -0
  149. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset_properties/length_unit.py +0 -0
  150. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/dataset_properties/structuring.py +0 -0
  151. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/datastore/__init__.py +0 -0
  152. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/datastore/datastore.py +0 -0
  153. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/geometry/__init__.py +0 -0
  154. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/geometry/bounding_box.py +0 -0
  155. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/geometry/constants.py +0 -0
  156. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/geometry/mag.py +0 -0
  157. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/geometry/nd_bounding_box.py +0 -0
  158. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/geometry/normalized_bounding_box.py +0 -0
  159. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/geometry/vec3_float.py +0 -0
  160. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/geometry/vec3_int.py +0 -0
  161. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/geometry/vec_int.py +0 -0
  162. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/proofreading/__init__.py +0 -0
  163. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/proofreading/agglomerate_graph_data.py +0 -0
  164. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/proofreading/generated/__init__.py +0 -0
  165. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/proofreading/generated/agglomerate_graph_pb2.py +0 -0
  166. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/proofreading/generated/agglomerate_graph_pb2.pyi +0 -0
  167. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/py.typed +0 -0
  168. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/skeleton/__init__.py +0 -0
  169. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/skeleton/group.py +0 -0
  170. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/skeleton/node.py +0 -0
  171. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/skeleton/skeleton.py +0 -0
  172. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/skeleton/tree.py +0 -0
  173. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/ssl_context.py +0 -0
  174. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos/utils.py +0 -0
  175. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos.egg-info/SOURCES.txt +0 -0
  176. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos.egg-info/dependency_links.txt +0 -0
  177. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos.egg-info/entry_points.txt +0 -0
  178. {webknossos-4.2.2 → webknossos-4.2.4}/webknossos.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webknossos
3
- Version: 4.2.2
3
+ Version: 4.2.4
4
4
  Summary: Python API for working with WEBKNOSSOS datasets, annotations, and for WEBKNOSSOS server interaction.
5
5
  Author-email: scalable minds <hello@scalableminds.com>
6
6
  License: AGPL-3.0
@@ -37,7 +37,7 @@ Requires-Dist: boltons>=21.0.0
37
37
  Requires-Dist: cattrs>=22.0.0
38
38
  Requires-Dist: certifi>=2023
39
39
  Requires-Dist: click<8.2.0
40
- Requires-Dist: cluster-tools==4.2.2
40
+ Requires-Dist: cluster-tools==4.2.4
41
41
  Requires-Dist: httpx~=0.27.0
42
42
  Requires-Dist: imageio>=2.37.0
43
43
  Requires-Dist: lz4>=4.0.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "webknossos"
3
- version = "4.2.2" # filled by dunamai
3
+ version = "4.2.4" # filled by dunamai
4
4
  description = "Python API for working with WEBKNOSSOS datasets, annotations, and for WEBKNOSSOS server interaction."
5
5
  authors = [{ name = "scalable minds", email = "hello@scalableminds.com" }]
6
6
  readme = "README.md"
@@ -39,7 +39,7 @@ dependencies = [
39
39
  "cattrs >=22.0.0",
40
40
  "certifi >=2023",
41
41
  "click <8.2.0", # pinned because of incompatibility with typer
42
- "cluster-tools==4.2.2",
42
+ "cluster-tools==4.2.4",
43
43
  "httpx ~=0.27.0",
44
44
  "imageio >=2.37.0",
45
45
  "lz4 >=4.0.0",
@@ -25,7 +25,7 @@ from tests.constants import (
25
25
  TESTDATA_DIR,
26
26
  )
27
27
  from tests.data_fixtures import download_wklibs_sample_archive
28
- from webknossos import BoundingBox, DataFormat, Dataset, Mag
28
+ from webknossos import BoundingBox, DataFormat, Dataset, Mag, NDBoundingBox
29
29
  from webknossos.cli.export_as_tiff import _apply_mapping, _make_tiff_name
30
30
  from webknossos.cli.main import app
31
31
  from webknossos.dataset._utils.tensorstore_helpers import (
@@ -629,6 +629,46 @@ def test_export_tiff_stack(tmp_upath: UPath) -> None:
629
629
  )
630
630
 
631
631
 
632
+ def test_export_tiff_stack_nd_layer(tmp_upath: UPath) -> None:
633
+ """Tests export of a layer whose array is stored as (c, t, x, y, z)."""
634
+
635
+ source_path = tmp_upath / "nd_dataset"
636
+ destination_path = tmp_upath / "nd_dataset_tiff"
637
+ nd_bbox = NDBoundingBox(
638
+ (0, 0, 0, 0, 0),
639
+ (1, 1, 16, 16, 8),
640
+ axes=("c", "t", "x", "y", "z"),
641
+ index=(0, 1, 2, 3, 4),
642
+ )
643
+ data = np.random.default_rng(0).integers(0, 255, (1, 1, 16, 16, 8), dtype="uint8")
644
+ layer = Dataset(source_path, voxel_size=(11, 11, 25)).add_layer(
645
+ "color", "color", dtype="uint8", data_format="zarr3", bounding_box=nd_bbox
646
+ )
647
+ layer.add_mag(1).write(data=data, absolute_bounding_box=nd_bbox)
648
+ assert layer.get_mag(1).info.bounding_box.axes == ("c", "t", "x", "y", "z")
649
+
650
+ result = runner.invoke(
651
+ app,
652
+ [
653
+ "export-as-tiff",
654
+ "--layer-name",
655
+ "color",
656
+ "--bbox",
657
+ "2,4,1,8,10,5",
658
+ str(source_path),
659
+ str(destination_path),
660
+ ],
661
+ )
662
+
663
+ assert result.exit_code == 0, result.stdout
664
+ for slice_index in range(5):
665
+ with (destination_path / _make_tiff_name("", slice_index)).open("rb") as f:
666
+ test_image = np.array(Image.open(f)).T
667
+ np.testing.assert_array_equal(
668
+ test_image, data[0, 0, 2:10, 4:14, 1 + slice_index]
669
+ )
670
+
671
+
632
672
  def test_export_tiff_stack_tile_size(tmp_upath: UPath) -> None:
633
673
  """Tests the tile size support of exporting a tiff stack."""
634
674
 
@@ -16,6 +16,7 @@ class AnnotationInfo:
16
16
  state: AnnotationState
17
17
  duration_in_seconds: float | None
18
18
  modified: int | None
19
+ created: int | None = None
19
20
 
20
21
  def download_annotation(self) -> Annotation:
21
22
  """Downloads and returns the annotation that is described by this AnnotationInfo object"""
@@ -35,6 +36,7 @@ class AnnotationInfo:
35
36
  if api_annotation.tracing_time is not None
36
37
  else None,
37
38
  modified=api_annotation.modified,
39
+ created=api_annotation.created,
38
40
  )
39
41
 
40
42
  @classmethod
@@ -109,7 +109,7 @@ def export_tiff_slice_batch(
109
109
  mapping_array = open_zarr3_array(mapping_path, context=ts_context)
110
110
 
111
111
  if tiling_size is None:
112
- tiff_data = view.read()
112
+ tiff_data = view.read_cxyz()
113
113
  if mapping_array is not None:
114
114
  tiff_data = _apply_mapping(tiff_data, mapping_array)
115
115
  else:
@@ -118,7 +118,7 @@ def export_tiff_slice_batch(
118
118
  tiling_size[1] * ceil(tiff_bbox.size.y / tiling_size[1]),
119
119
  tiff_bbox.size.z,
120
120
  )
121
- tiff_data = view.read()
121
+ tiff_data = view.read_cxyz()
122
122
  if mapping_array is not None:
123
123
  tiff_data = _apply_mapping(tiff_data, mapping_array)
124
124
  padded_tiff_data = np.zeros(
@@ -451,10 +451,11 @@ class ApiAnnotation:
451
451
  name: str
452
452
  description: str
453
453
  state: str
454
- modified: int
455
454
  annotation_layers: list[ApiAnnotationLayer] | None
455
+ modified: int
456
456
  data_store: ApiDataStore | None = None
457
457
  tracing_time: int | None = None # millis
458
+ created: int | None = None
458
459
 
459
460
 
460
461
  @attr.s(auto_attribs=True)
@@ -64,7 +64,7 @@ def _resolve_export_bbox(
64
64
  The layer's bounding box can't be used for that: for remote layers it may
65
65
  lack the channel axis that the streamed array has (or vice versa). The
66
66
  request is clipped to the layer per axis name, axes the array doesn't
67
- have are ignored and the channel axis always spans all channels.
67
+ have are ignored and the channel axis always spans all channels of the layer.
68
68
  """
69
69
  layer_bbox = layer.normalized_bounding_box
70
70
  if bounding_box is None:
@@ -73,7 +73,14 @@ def _resolve_export_bbox(
73
73
  topleft, size = [], []
74
74
  for axis in array_axes:
75
75
  if axis == C_AXIS:
76
+ mag_bbox = mag_view.normalized_bounding_box
77
+ array_start, array_end = mag_view.info.bounding_box.get_bounds(C_AXIS)
76
78
  start, end = 0, layer.num_channels
79
+ if C_AXIS in mag_bbox.axes:
80
+ mag_start, mag_end = mag_bbox.get_bounds(C_AXIS)
81
+ # Streamed arrays might only contain the layer's channels.
82
+ if array_start <= mag_start and mag_end <= array_end:
83
+ start, end = mag_start, mag_end
77
84
  elif axis in layer_bbox.axes:
78
85
  start, end = layer_bbox.get_bounds(axis)
79
86
  if axis in bounding_box.axes:
@@ -868,6 +868,12 @@ class Layer(AbstractLayer):
868
868
  mag=mag,
869
869
  path=dump_path(foreign_mag_view.path, self.dataset.resolved_path),
870
870
  cube_length=foreign_mag_view._properties.cube_length,
871
+ # Not set for mags whose arrays are re-encoded by zarr streaming.
872
+ axis_order=(
873
+ foreign_mag_view._properties.axis_order
874
+ if foreign_mag_view._get_dimension_names() is not None
875
+ else None
876
+ ),
871
877
  )
872
878
  )
873
879
  self._save_layer_properties()
@@ -436,12 +436,35 @@ class WKWArray(BaseArray):
436
436
  class TensorStoreArray(BaseArray):
437
437
  _cached_array: tensorstore.TensorStore | None
438
438
  _tensorstore_data_format: str
439
+ _dimension_names: tuple[str, ...] | None
439
440
 
440
441
  def __init__(
441
- self, path: UPath, _cached_array: tensorstore.TensorStore | None = None
442
+ self,
443
+ path: UPath,
444
+ _cached_array: tensorstore.TensorStore | None = None,
445
+ dimension_names: tuple[str, ...] | None = None,
442
446
  ):
447
+ """`dimension_names` (e.g. from the `axisOrder` of the layer properties)
448
+ take precedence over the dimension names in the array's metadata, if their
449
+ number matches the array's rank."""
443
450
  super().__init__(path)
444
- self._cached_array = _cached_array
451
+ self._dimension_names = dimension_names
452
+ self._cached_array = (
453
+ None
454
+ if _cached_array is None
455
+ else self._apply_dimension_names(_cached_array)
456
+ )
457
+
458
+ def _apply_dimension_names(
459
+ self, array: tensorstore.TensorStore
460
+ ) -> tensorstore.TensorStore:
461
+ if (
462
+ self._dimension_names is not None
463
+ and len(self._dimension_names) == array.rank
464
+ and array.domain.labels != self._dimension_names
465
+ ):
466
+ return array[tensorstore.d[:].label[self._dimension_names]]
467
+ return array
445
468
 
446
469
  @staticmethod
447
470
  def _get_array_dimensions(
@@ -560,7 +583,7 @@ class TensorStoreArray(BaseArray):
560
583
  }
561
584
 
562
585
  @classmethod
563
- def _open(cls, path: UPath) -> Self:
586
+ def _open(cls, path: UPath, dimension_names: tuple[str, ...] | None = None) -> Self:
564
587
  try:
565
588
  _array = call_with_retries(
566
589
  lambda: tensorstore.open(
@@ -572,7 +595,7 @@ class TensorStoreArray(BaseArray):
572
595
  ).result(),
573
596
  description="Opening tensorstore array",
574
597
  ) # check that everything exists
575
- return cls(path, _array)
598
+ return cls(path, _array, dimension_names=dimension_names)
576
599
  except Exception as exc:
577
600
  raise ArrayException(f"Could not open array at {path}.") from exc
578
601
 
@@ -620,16 +643,18 @@ class TensorStoreArray(BaseArray):
620
643
 
621
644
  if new_domain != array.domain:
622
645
  # Check on-disk for changes to shape
623
- current_array = call_with_retries(
624
- lambda: tensorstore.open(
625
- {
626
- "driver": self._tensorstore_data_format,
627
- "kvstore": _make_kvstore(self._path),
628
- },
629
- context=TS_CONTEXT,
630
- recheck_cached="open",
631
- ).result(),
632
- description="Opening tensorstore array for resizing",
646
+ current_array = self._apply_dimension_names(
647
+ call_with_retries(
648
+ lambda: tensorstore.open(
649
+ {
650
+ "driver": self._tensorstore_data_format,
651
+ "kvstore": _make_kvstore(self._path),
652
+ },
653
+ context=TS_CONTEXT,
654
+ recheck_cached="open",
655
+ ).result(),
656
+ description="Opening tensorstore array for resizing",
657
+ )
633
658
  )
634
659
  if array.domain != current_array.domain:
635
660
  raise RuntimeError(
@@ -739,6 +764,7 @@ class TensorStoreArray(BaseArray):
739
764
  ).result(),
740
765
  description="Creating tensorstore array",
741
766
  )
767
+ self._cached_array = self._apply_dimension_names(self._cached_array)
742
768
  except Exception as e:
743
769
  raise ArrayException(
744
770
  f"Exception while opening array for {_make_kvstore(self._path)}"
@@ -767,8 +793,10 @@ class Zarr3Array(TensorStoreArray):
767
793
  _tensorstore_data_format = "zarr3"
768
794
 
769
795
  @classmethod
770
- def open(cls, path: UPath) -> "Zarr3Array":
771
- return cls._open(path)
796
+ def open(
797
+ cls, path: UPath, dimension_names: tuple[str, ...] | None = None
798
+ ) -> "Zarr3Array":
799
+ return cls._open(path, dimension_names)
772
800
 
773
801
  @property
774
802
  def info(self) -> Zarr3ArrayInfo:
@@ -887,8 +915,10 @@ class Zarr2Array(TensorStoreArray):
887
915
  _tensorstore_data_format = "zarr"
888
916
 
889
917
  @classmethod
890
- def open(cls, path: UPath) -> "Zarr2Array":
891
- return cls._open(path)
918
+ def open(
919
+ cls, path: UPath, dimension_names: tuple[str, ...] | None = None
920
+ ) -> "Zarr2Array":
921
+ return cls._open(path, dimension_names)
892
922
 
893
923
  @property
894
924
  def info(self) -> ArrayInfo:
@@ -17,7 +17,7 @@ from webknossos.utils import (
17
17
  warn_deprecated,
18
18
  )
19
19
 
20
- from ....dataset_properties import DataFormat, MagViewProperties
20
+ from ....dataset_properties import DataFormat, LayerProperties, MagViewProperties
21
21
  from ....geometry import (
22
22
  Mag,
23
23
  NDBoundingBox,
@@ -202,6 +202,7 @@ class MagView(View, Generic[LayerTypeT]):
202
202
  read_only=read_only,
203
203
  )
204
204
  self._layer = layer
205
+ self._cached_dimension_names: tuple[str, ...] | None = None
205
206
 
206
207
  def _ensure_writable(self) -> None:
207
208
  if self._read_only:
@@ -241,6 +242,29 @@ class MagView(View, Generic[LayerTypeT]):
241
242
  """
242
243
  return self.layer.num_channels
243
244
 
245
+ def _get_dimension_names(self) -> tuple[str, ...] | None:
246
+ # An explicit `axisOrder` takes precedence over the array's metadata.
247
+ # The bounding box's axes are sorted by their index in the array.
248
+ if self._cached_dimension_names is None:
249
+ mag_properties = self._find_properties()
250
+ if mag_properties is None or mag_properties.axis_order is None:
251
+ return None
252
+ self._cached_dimension_names = self.normalized_bounding_box.axes
253
+ return self._cached_dimension_names
254
+
255
+ def _get_layer_properties(self) -> LayerProperties:
256
+ return self.layer._properties
257
+
258
+ def _find_properties(self) -> MagViewProperties | None:
259
+ return next(
260
+ (
261
+ mag_property
262
+ for mag_property in self._get_layer_properties().mags
263
+ if mag_property.mag == self._mag
264
+ ),
265
+ None,
266
+ )
267
+
244
268
  # Own methods:
245
269
 
246
270
  @property
@@ -395,6 +419,7 @@ class MagView(View, Generic[LayerTypeT]):
395
419
  rel_mag1_bbox=relative_bounding_box,
396
420
  current_mag_size=data_shape,
397
421
  )
422
+ self._check_write_shape(data, mag1_bbox)
398
423
 
399
424
  # Only update the layer's bbox if we are actually larger
400
425
  # than the mag-aligned, rounded up bbox (self.bounding_box):
@@ -872,11 +897,10 @@ class MagView(View, Generic[LayerTypeT]):
872
897
  MagViewProperties: Properties object for this magnification level.
873
898
 
874
899
  """
875
- return next(
876
- mag_property
877
- for mag_property in self.layer._properties.mags
878
- if mag_property.mag == self._mag
879
- )
900
+ mag_properties = self._find_properties()
901
+ if mag_properties is None:
902
+ raise KeyError(f"No properties found for mag {self._mag}.")
903
+ return mag_properties
880
904
 
881
905
  def __repr__(self) -> str:
882
906
  return f"MagView(name={repr(self.name)}, bounding_box={self.bounding_box})"
@@ -93,6 +93,16 @@ class RemoteMagView(MagView["RemoteLayer"]):
93
93
  def num_channels(self) -> int:
94
94
  return self.normalized_bounding_box.size.get("c", 1)
95
95
 
96
+ def _get_layer_properties(self) -> "LayerProperties":
97
+ return self._layer_properties
98
+
99
+ def _get_dimension_names(self) -> tuple[str, ...] | None:
100
+ # Zarr streaming re-encodes the arrays with their own axis order, while
101
+ # the other access modes serve the original arrays.
102
+ if self._access_mode == RemoteAccessMode.ZARR_STREAMING:
103
+ return None
104
+ return super()._get_dimension_names()
105
+
96
106
  @property
97
107
  def paths_by_access_mode(self) -> Mapping[RemoteAccessMode, UPath]:
98
108
  """All paths at which this mag's data can be reached, keyed by access mode.
@@ -28,7 +28,7 @@ from ....utils import (
28
28
  wait_and_ensure_success,
29
29
  warn_deprecated,
30
30
  )
31
- from ._array import ArrayInfo, BaseArray
31
+ from ._array import ArrayInfo, BaseArray, Zarr2Array, Zarr3Array
32
32
 
33
33
  if TYPE_CHECKING:
34
34
  from ..._utils.buffered_slice_reader import BufferedSliceReader
@@ -81,6 +81,7 @@ class View:
81
81
  data_format: DataFormat,
82
82
  read_only: bool = False,
83
83
  cached_array: BaseArray | None = None,
84
+ dimension_names: tuple[str, ...] | None = None,
84
85
  ):
85
86
  """Initialize a View instance for accessing and manipulating dataset regions.
86
87
 
@@ -113,6 +114,7 @@ class View:
113
114
  self._normalized_bounding_box = bounding_box
114
115
  self._read_only = read_only
115
116
  self._cached_array = cached_array
117
+ self._dimension_names = dimension_names
116
118
  self._mag = mag
117
119
 
118
120
  @property
@@ -225,6 +227,22 @@ class View:
225
227
  """
226
228
  return self._read_only
227
229
 
230
+ def _normalize_axes_with_channel_start(
231
+ self, bbox: NDBoundingBox
232
+ ) -> NormalizedBoundingBox:
233
+ normalized_bbox = bbox.normalize_axes(self.num_channels)
234
+ if (
235
+ C_AXIS not in bbox.axes
236
+ and C_AXIS in normalized_bbox.axes
237
+ and C_AXIS in self.normalized_bounding_box.axes
238
+ ):
239
+ # Start at the view's first channel, which is non-zero for
240
+ # layers with a `channelIndex`.
241
+ normalized_bbox = normalized_bbox.with_bounds(
242
+ C_AXIS, self.normalized_bounding_box.topleft.c, self.num_channels
243
+ )
244
+ return normalized_bbox
245
+
228
246
  def _get_mag1_bbox(
229
247
  self,
230
248
  *,
@@ -255,10 +273,10 @@ class View:
255
273
  )
256
274
 
257
275
  if abs_mag1_bbox is not None:
258
- return abs_mag1_bbox.normalize_axes(self.num_channels)
276
+ return self._normalize_axes_with_channel_start(abs_mag1_bbox)
259
277
 
260
278
  if rel_mag1_bbox is not None:
261
- return rel_mag1_bbox.normalize_axes(self.num_channels).offset(
279
+ return self._normalize_axes_with_channel_start(rel_mag1_bbox).offset(
262
280
  self.bounding_box.topleft
263
281
  )
264
282
 
@@ -335,6 +353,7 @@ class View:
335
353
  - Number of channels doesn't match the dataset
336
354
  - Write region is outside the view's bounding box
337
355
  - Multiple positioning parameters are provided
356
+ ValueError: If the data shape doesn't match the size of the target region.
338
357
 
339
358
  Examples:
340
359
  ```python
@@ -434,6 +453,8 @@ class View:
434
453
 
435
454
  current_mag_bbox = mag1_bbox.in_mag(self._mag)
436
455
 
456
+ self._check_write_shape(data, mag1_bbox)
457
+
437
458
  if not allow_unaligned:
438
459
  if self._data_format == DataFormat.WKW and not self._is_compressed():
439
460
  try:
@@ -456,6 +477,28 @@ class View:
456
477
  else:
457
478
  self._array.write(current_mag_bbox, data)
458
479
 
480
+ def _check_write_shape(
481
+ self, data: np.ndarray, mag1_bbox: NormalizedBoundingBox
482
+ ) -> None:
483
+ current_mag_bbox = mag1_bbox.in_mag(self._mag)
484
+ c_index = current_mag_bbox.index.c if C_AXIS in current_mag_bbox.axes else None
485
+ expected_shape = tuple(current_mag_bbox.size)
486
+ data_shape = tuple(data.shape)
487
+ if c_index is not None and len(data_shape) + 1 == len(expected_shape):
488
+ # data without channel axis
489
+ data_shape = data_shape[:c_index] + (1,) + data_shape[c_index:]
490
+ # the number of channels is validated separately
491
+ if len(data_shape) != len(expected_shape) or any(
492
+ actual != expected
493
+ for i, (actual, expected) in enumerate(zip(data_shape, expected_shape))
494
+ if i != c_index
495
+ ):
496
+ raise ValueError(
497
+ f"The shape of the passed data {data.shape} does not match the size "
498
+ f"of the bounding box to write {expected_shape} "
499
+ f"(axes {current_mag_bbox.axes}, in {self._mag})."
500
+ )
501
+
459
502
  def _resolve_cxyz_write(
460
503
  self,
461
504
  data: np.ndarray,
@@ -1094,6 +1137,7 @@ class View:
1094
1137
  data_format=self._data_format,
1095
1138
  read_only=read_only,
1096
1139
  cached_array=self._cached_array,
1140
+ dimension_names=self._get_dimension_names(),
1097
1141
  )
1098
1142
 
1099
1143
  def get_buffered_slice_writer(
@@ -1669,10 +1713,20 @@ class View:
1669
1713
  def _get_file_dimensions_mag1(self) -> Vec3Int:
1670
1714
  return Vec3Int(self._get_file_dimensions() * self.mag.to_vec3_int())
1671
1715
 
1716
+ def _get_dimension_names(self) -> tuple[str, ...] | None:
1717
+ return self._dimension_names
1718
+
1672
1719
  @property
1673
1720
  def _array(self) -> BaseArray:
1674
1721
  if self._cached_array is None:
1675
- self._cached_array = BaseArray.get_class(self._data_format).open(self._path)
1722
+ array_class = BaseArray.get_class(self._data_format)
1723
+ dimension_names = self._get_dimension_names()
1724
+ if dimension_names is not None and issubclass(
1725
+ array_class, (Zarr3Array, Zarr2Array)
1726
+ ):
1727
+ self._cached_array = array_class.open(self._path, dimension_names)
1728
+ else:
1729
+ self._cached_array = array_class.open(self._path)
1676
1730
  return self._cached_array
1677
1731
 
1678
1732
  @_array.deleter
@@ -79,6 +79,11 @@ class MagViewProperties:
79
79
  Could be None for older datasource-proterties.json files.
80
80
  """
81
81
  cube_length: int | None = None
82
+ axis_order: dict[str, int] | None = attr.field(default=None, eq=False)
83
+ """
84
+ Index of each axis in the array, as read from the datasource-properties.json.
85
+ None if not set there. On save, it is derived from the layer's bounding box.
86
+ """
82
87
 
83
88
 
84
89
  @attr.define
@@ -0,0 +1 @@
1
+ __version__ = '4.2.4'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webknossos
3
- Version: 4.2.2
3
+ Version: 4.2.4
4
4
  Summary: Python API for working with WEBKNOSSOS datasets, annotations, and for WEBKNOSSOS server interaction.
5
5
  Author-email: scalable minds <hello@scalableminds.com>
6
6
  License: AGPL-3.0
@@ -37,7 +37,7 @@ Requires-Dist: boltons>=21.0.0
37
37
  Requires-Dist: cattrs>=22.0.0
38
38
  Requires-Dist: certifi>=2023
39
39
  Requires-Dist: click<8.2.0
40
- Requires-Dist: cluster-tools==4.2.2
40
+ Requires-Dist: cluster-tools==4.2.4
41
41
  Requires-Dist: httpx~=0.27.0
42
42
  Requires-Dist: imageio>=2.37.0
43
43
  Requires-Dist: lz4>=4.0.0
@@ -4,7 +4,7 @@ boltons>=21.0.0
4
4
  cattrs>=22.0.0
5
5
  certifi>=2023
6
6
  click<8.2.0
7
- cluster-tools==4.2.2
7
+ cluster-tools==4.2.4
8
8
  httpx~=0.27.0
9
9
  imageio>=2.37.0
10
10
  lz4>=4.0.0
@@ -1 +0,0 @@
1
- __version__ = '4.2.2'
File without changes
File without changes