pyisyntax 0.1.4__tar.gz → 0.1.6__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 (80) hide show
  1. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/CHANGELOG.md +13 -1
  2. {pyisyntax-0.1.4/pyisyntax.egg-info → pyisyntax-0.1.6}/PKG-INFO +27 -22
  3. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/README.md +23 -11
  4. pyisyntax-0.1.6/isyntax/_pyisyntax.pyi +4 -0
  5. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax/lowlevel/io_management.py +11 -8
  6. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax/lowlevel/libisyntax.py +68 -46
  7. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax/wrapper.py +41 -5
  8. pyisyntax-0.1.6/isyntax_build/builder.py +112 -0
  9. pyisyntax-0.1.6/isyntax_build/vendor/libisyntax/src/examples/isyntax_dirwalk.c +91 -0
  10. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/isyntax/isyntax.c +58 -33
  11. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/isyntax/isyntax.h +6 -3
  12. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/isyntax/isyntax_reader.c +16 -8
  13. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/isyntax/isyntax_reader.h +3 -2
  14. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/isyntax/isyntax_streamer.c +4 -1
  15. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/libisyntax.c +97 -39
  16. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/libisyntax.h +11 -1
  17. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/utils/block_allocator.c +22 -13
  18. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/utils/memrw.c +24 -15
  19. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/utils/timerutils.c +24 -15
  20. {pyisyntax-0.1.4 → pyisyntax-0.1.6/pyisyntax.egg-info}/PKG-INFO +27 -22
  21. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/pyisyntax.egg-info/SOURCES.txt +3 -0
  22. pyisyntax-0.1.6/pyisyntax.egg-info/requires.txt +2 -0
  23. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/pyproject.toml +39 -22
  24. pyisyntax-0.1.6/tests/__init__.py +0 -0
  25. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/tests/conftest.py +2 -2
  26. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/tests/test_lowlevel.py +33 -7
  27. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/tests/test_wrapper.py +9 -1
  28. pyisyntax-0.1.4/isyntax_build/builder.py +0 -98
  29. pyisyntax-0.1.4/pyisyntax.egg-info/requires.txt +0 -11
  30. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/LICENSE +0 -0
  31. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/MANIFEST.in +0 -0
  32. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax/__init__.py +0 -0
  33. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax/lowlevel/__init__.py +0 -0
  34. /pyisyntax-0.1.4/isyntax_build/__init__.py → /pyisyntax-0.1.6/isyntax/py.typed +0 -0
  35. {pyisyntax-0.1.4/tests → pyisyntax-0.1.6/isyntax_build}/__init__.py +0 -0
  36. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/pyisyntax.c +0 -0
  37. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/src/python_platform_utils.c +0 -0
  38. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/src/python_platform_utils.h +0 -0
  39. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/src/win32_utils.c +0 -0
  40. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/LICENSE.txt +0 -0
  41. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/README.md +0 -0
  42. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/examples/isyntax_example.c +0 -0
  43. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/examples/isyntax_to_tiff.c +0 -0
  44. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/isyntax/isyntax_dwt.c +0 -0
  45. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/isyntax/isyntax_streamer.h +0 -0
  46. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/platform/arena.h +0 -0
  47. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/platform/common.h +0 -0
  48. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/platform/intrinsics.h +0 -0
  49. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/platform/linux_platform.c +0 -0
  50. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/platform/linux_utils.c +0 -0
  51. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/platform/platform.c +0 -0
  52. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/platform/platform.h +0 -0
  53. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/platform/win32_utils.c +0 -0
  54. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/platform/win32_utils.h +0 -0
  55. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/platform/work_queue.c +0 -0
  56. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/platform/work_queue.h +0 -0
  57. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/third_party/ltalloc.cc +0 -0
  58. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/third_party/ltalloc.h +0 -0
  59. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/third_party/stb_ds.h +0 -0
  60. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/third_party/stb_image.h +0 -0
  61. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/third_party/stb_image_write.h +0 -0
  62. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/third_party/stb_sprintf.h +0 -0
  63. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/third_party/yxml.c +0 -0
  64. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/third_party/yxml.h +0 -0
  65. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/utils/benaphore.c +0 -0
  66. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/utils/benaphore.h +0 -0
  67. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/utils/block_allocator.h +0 -0
  68. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/utils/libtiff_api.h +0 -0
  69. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/utils/mathutils.c +0 -0
  70. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/utils/mathutils.h +0 -0
  71. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/utils/memrw.h +0 -0
  72. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/utils/stringutils.c +0 -0
  73. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/utils/stringutils.h +0 -0
  74. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/isyntax_build/vendor/libisyntax/src/utils/timerutils.h +0 -0
  75. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/pyisyntax.egg-info/dependency_links.txt +0 -0
  76. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/pyisyntax.egg-info/not-zip-safe +0 -0
  77. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/pyisyntax.egg-info/top_level.txt +0 -0
  78. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/setup.cfg +0 -0
  79. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/setup.py +0 -0
  80. {pyisyntax-0.1.4 → pyisyntax-0.1.6}/tests/data/DOWNLOAD.txt +0 -0
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.5] - 2025-04-18
11
+
12
+ ### Added
13
+
14
+ - Properties `offset_x`and `offset_y` for the WSI image offset in relation to the macro image.
15
+
16
+ ### Changed
17
+
18
+ - Bump the `libisyntax` submodule commit.
19
+ - Use `uv` for development environment management (was previously `conda-lock`).
20
+
10
21
  ## [0.1.4] - 2024-08-31
11
22
 
12
23
  ### Added
@@ -61,7 +72,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
61
72
 
62
73
  - This is the initial release.
63
74
 
64
- [unreleased]: https://github.com/anibali/pyisyntax/compare/v0.1.4...HEAD
75
+ [unreleased]: https://github.com/anibali/pyisyntax/compare/v0.1.5...HEAD
76
+ [0.1.4]: https://github.com/anibali/pyisyntax/compare/v0.1.4...v0.1.5
65
77
  [0.1.4]: https://github.com/anibali/pyisyntax/compare/v0.1.3...v0.1.4
66
78
  [0.1.3]: https://github.com/anibali/pyisyntax/compare/v0.1.2...v0.1.3
67
79
  [0.1.2]: https://github.com/anibali/pyisyntax/compare/v0.1.1...v0.1.2
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyisyntax
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: Python bindings for libisyntax
5
5
  Author-email: Aiden Nibali <dismaldenizen@gmail.com>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/anibali/pyisyntax
8
8
  Project-URL: Source, https://github.com/anibali/pyisyntax
9
9
  Project-URL: Download, https://pypi.org/project/pyisyntax/#files
@@ -14,14 +14,7 @@ Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
  Requires-Dist: cffi>=1.12
16
16
  Requires-Dist: numpy
17
- Provides-Extra: dev
18
- Requires-Dist: conda-lock; extra == "dev"
19
- Requires-Dist: pytest; extra == "dev"
20
- Requires-Dist: pytest-mock; extra == "dev"
21
- Requires-Dist: ruff==0.2.1; extra == "dev"
22
- Requires-Dist: tox>=4.0; extra == "dev"
23
- Requires-Dist: twine; extra == "dev"
24
- Requires-Dist: typing_extensions; extra == "dev"
17
+ Dynamic: license-file
25
18
 
26
19
  # pyisyntax
27
20
 
@@ -73,22 +66,34 @@ with ISyntax.open("my_file.isyntax") as isyntax:
73
66
 
74
67
  To set up a development environment from the lock file:
75
68
 
76
- 1. Ensure that you have
77
- [micromamba](https://mamba.readthedocs.io/en/latest/user_guide/micromamba.html)
78
- installed.
79
- 2. Create the environment:
69
+ 1. Ensure that you have [uv](https://docs.astral.sh/uv/) installed.
70
+ 2. Create the virtual environment:
80
71
  ```console
81
- $ micromamba create -n pyisyntax -f conda-lock.yml --category main --category dev
72
+ $ uv sync --frozen
82
73
  ```
83
- 3. Activate the environment:
74
+
75
+ To modify pyisyntax project dependencies:
76
+
77
+ 1. Edit dependencies in `pyproject.toml`.
78
+ 2. Update the lock file using uv:
84
79
  ```console
85
- $ micromamba activate pyisyntax
80
+ $ uv lock
86
81
  ```
87
82
 
88
- To modify pyisyntax project dependencies:
83
+ ### Cutting a new release
89
84
 
90
- 1. Edit pyproject.toml.
91
- 2. Update the lock file using conda-lock:
85
+ 1. Ensure that tests are passing and everything is ready for release.
86
+ 2. Create and push a Git tag:
92
87
  ```console
93
- $ conda-lock lock -f pyproject.toml -p linux-64 --micromamba
88
+ $ git tag v0.1.6
89
+ $ git push --tags
94
90
  ```
91
+ 3. Download the artifacts from GitHub Actions, which will include the source distribution tarball and binary wheels.
92
+ 4. Create a new release on GitHub from the tagged commit and upload the packages as attachments to the release.
93
+ 5. Also upload the packages to PyPI using Twine:
94
+ ```console
95
+ $ uv run twine upload pyisyntax-*.tar.gz pyisyntax-*.whl
96
+ ```
97
+ 6. Bump the version number in `pyproject.toml` and create a commit, signalling the start of development on the next version.
98
+
99
+ These files should also be added to a GitHub release.
@@ -48,22 +48,34 @@ with ISyntax.open("my_file.isyntax") as isyntax:
48
48
 
49
49
  To set up a development environment from the lock file:
50
50
 
51
- 1. Ensure that you have
52
- [micromamba](https://mamba.readthedocs.io/en/latest/user_guide/micromamba.html)
53
- installed.
54
- 2. Create the environment:
51
+ 1. Ensure that you have [uv](https://docs.astral.sh/uv/) installed.
52
+ 2. Create the virtual environment:
55
53
  ```console
56
- $ micromamba create -n pyisyntax -f conda-lock.yml --category main --category dev
54
+ $ uv sync --frozen
57
55
  ```
58
- 3. Activate the environment:
56
+
57
+ To modify pyisyntax project dependencies:
58
+
59
+ 1. Edit dependencies in `pyproject.toml`.
60
+ 2. Update the lock file using uv:
59
61
  ```console
60
- $ micromamba activate pyisyntax
62
+ $ uv lock
61
63
  ```
62
64
 
63
- To modify pyisyntax project dependencies:
65
+ ### Cutting a new release
64
66
 
65
- 1. Edit pyproject.toml.
66
- 2. Update the lock file using conda-lock:
67
+ 1. Ensure that tests are passing and everything is ready for release.
68
+ 2. Create and push a Git tag:
67
69
  ```console
68
- $ conda-lock lock -f pyproject.toml -p linux-64 --micromamba
70
+ $ git tag v0.1.6
71
+ $ git push --tags
69
72
  ```
73
+ 3. Download the artifacts from GitHub Actions, which will include the source distribution tarball and binary wheels.
74
+ 4. Create a new release on GitHub from the tagged commit and upload the packages as attachments to the release.
75
+ 5. Also upload the packages to PyPI using Twine:
76
+ ```console
77
+ $ uv run twine upload pyisyntax-*.tar.gz pyisyntax-*.whl
78
+ ```
79
+ 6. Bump the version number in `pyproject.toml` and create a commit, signalling the start of development on the next version.
80
+
81
+ These files should also be added to a GitHub release.
@@ -0,0 +1,4 @@
1
+ from typing import Any
2
+
3
+ ffi: Any
4
+ lib: Any
@@ -27,12 +27,15 @@ class ByHandleRegistry(Generic[T]):
27
27
  Handles start at 1, autoincrement, and will automatically be recycled
28
28
  once objects are removed from the registry.
29
29
  """
30
- self._list = []
30
+ self._list: list[T | ByHandleRegistry._EmptySlot] = []
31
31
  self._n_free = 0
32
32
  self._empty_slot = self._EmptySlot()
33
33
 
34
34
  def pop(self, handle: int) -> T:
35
35
  element = self._list[handle - 1]
36
+ if isinstance(element, ByHandleRegistry._EmptySlot):
37
+ msg = "empty slot"
38
+ raise IndexError(msg)
36
39
  self._list[handle - 1] = self._empty_slot
37
40
  self._n_free += 1
38
41
  if len(self._list) == handle:
@@ -52,11 +55,15 @@ class ByHandleRegistry(Generic[T]):
52
55
  return index + 1
53
56
 
54
57
  def __getitem__(self, handle: int) -> T:
55
- return self._list[handle - 1]
58
+ element = self._list[handle - 1]
59
+ if isinstance(element, ByHandleRegistry._EmptySlot):
60
+ msg = "empty slot"
61
+ raise IndexError(msg)
62
+ return element
56
63
 
57
64
  def items(self) -> Iterator[tuple[int, T]]:
58
65
  for i, element in enumerate(self._list):
59
- if element is not self._empty_slot:
66
+ if not isinstance(element, ByHandleRegistry._EmptySlot):
60
67
  yield i + 1, element
61
68
 
62
69
 
@@ -69,7 +76,6 @@ def init_python_io_hooks() -> None:
69
76
  _io_registry[handle].f.seek(offset)
70
77
  return True
71
78
 
72
-
73
79
  @ffi.def_extern()
74
80
  def python_file_read_into(handle: int, dest: VoidPtr, bytes_to_read: int) -> int:
75
81
  bytes_read = _io_registry[handle].f.readinto(ffi.buffer(dest, bytes_to_read))
@@ -77,17 +83,14 @@ def init_python_io_hooks() -> None:
77
83
  raise RuntimeError
78
84
  return max(bytes_read, 1)
79
85
 
80
-
81
86
  @ffi.def_extern()
82
87
  def python_file_get_size(handle: int) -> int:
83
88
  return _io_registry[handle].n_bytes
84
89
 
85
-
86
90
  @ffi.def_extern()
87
91
  def python_file_close(handle: int) -> None:
88
92
  _io_registry.pop(handle).f.close()
89
93
 
90
-
91
94
  lib.init_python_platform_utils(
92
95
  lib.python_file_set_pos,
93
96
  lib.python_file_read_into,
@@ -109,7 +112,7 @@ def register_io(f: RawIOBase | BufferedIOBase, n_bytes: int) -> int:
109
112
  Returns:
110
113
  New handle assigned to the IO object.
111
114
  """
112
- if not f.readable:
115
+ if not f.readable():
113
116
  msg = "IO object must be readable"
114
117
  raise RuntimeError(msg)
115
118
  return _io_registry.add(SizedIO(f, n_bytes))
@@ -77,11 +77,13 @@ def open_from_registered_handle(handle: int, *, is_init_allocators: bool = False
77
77
  init()
78
78
 
79
79
  isyntax = ffi.new("isyntax_t**")
80
- check_error(lib.libisyntax_open(
81
- ffi.new("char[]", str(handle).encode("utf-8")),
82
- is_init_allocators,
83
- isyntax,
84
- ))
80
+ check_error(
81
+ lib.libisyntax_open(
82
+ ffi.new("char[]", str(handle).encode("utf-8")),
83
+ is_init_allocators,
84
+ isyntax,
85
+ ),
86
+ )
85
87
  return isyntax[0]
86
88
 
87
89
 
@@ -131,6 +133,14 @@ def image_get_level(wsi_image: ISyntaxImagePtr, index: int) -> ISyntaxLevelPtr:
131
133
  return lib.libisyntax_image_get_level(wsi_image, index)
132
134
 
133
135
 
136
+ def image_get_offset_x(wsi_image: ISyntaxImagePtr) -> int:
137
+ return lib.libisyntax_image_get_offset_x(wsi_image)
138
+
139
+
140
+ def image_get_offset_y(wsi_image: ISyntaxImagePtr) -> int:
141
+ return lib.libisyntax_image_get_offset_y(wsi_image)
142
+
143
+
134
144
  def level_get_scale(level: ISyntaxLevelPtr) -> int:
135
145
  return lib.libisyntax_level_get_scale(level)
136
146
 
@@ -165,11 +175,13 @@ def cache_create(debug_name: str | None, cache_size: int) -> ISyntaxCachePtr:
165
175
  debug_name_or_null = ffi.NULL
166
176
  else:
167
177
  debug_name_or_null = ffi.new("char[]", debug_name.encode("utf-8"))
168
- check_error(lib.libisyntax_cache_create(
169
- debug_name_or_null,
170
- cache_size,
171
- isyntax_cache,
172
- ))
178
+ check_error(
179
+ lib.libisyntax_cache_create(
180
+ debug_name_or_null,
181
+ cache_size,
182
+ isyntax_cache,
183
+ ),
184
+ )
173
185
  return isyntax_cache[0]
174
186
 
175
187
 
@@ -192,15 +204,17 @@ def tile_read(
192
204
  pixels_buffer: "Buffer | FFI.buffer",
193
205
  pixel_format: ISyntaxPixelFormat,
194
206
  ) -> None:
195
- check_error(lib.libisyntax_tile_read(
196
- isyntax,
197
- isyntax_cache,
198
- level,
199
- tile_x,
200
- tile_y,
201
- ffi.from_buffer("uint32_t[]", pixels_buffer, require_writable=True),
202
- pixel_format,
203
- ))
207
+ check_error(
208
+ lib.libisyntax_tile_read(
209
+ isyntax,
210
+ isyntax_cache,
211
+ level,
212
+ tile_x,
213
+ tile_y,
214
+ ffi.from_buffer("uint32_t[]", pixels_buffer, require_writable=True),
215
+ pixel_format,
216
+ ),
217
+ )
204
218
 
205
219
 
206
220
  def read_region(
@@ -214,27 +228,31 @@ def read_region(
214
228
  pixels_buffer: "Buffer | FFI.buffer",
215
229
  pixel_format: ISyntaxPixelFormat,
216
230
  ) -> None:
217
- check_error(lib.libisyntax_read_region(
218
- isyntax,
219
- isyntax_cache,
220
- level,
221
- x,
222
- y,
223
- width,
224
- height,
225
- ffi.from_buffer("uint32_t[]", pixels_buffer, require_writable=True),
226
- pixel_format,
227
- ))
231
+ check_error(
232
+ lib.libisyntax_read_region(
233
+ isyntax,
234
+ isyntax_cache,
235
+ level,
236
+ x,
237
+ y,
238
+ width,
239
+ height,
240
+ ffi.from_buffer("uint32_t[]", pixels_buffer, require_writable=True),
241
+ pixel_format,
242
+ ),
243
+ )
228
244
 
229
245
 
230
246
  def read_label_image_jpeg(isyntax: ISyntaxPtr) -> memoryview:
231
247
  jpeg_buffer_ptr = ffi.new("uint8_t**")
232
248
  jpeg_size_ptr = ffi.new("uint32_t*")
233
- check_error(lib.libisyntax_read_label_image_jpeg(
234
- isyntax,
235
- jpeg_buffer_ptr,
236
- jpeg_size_ptr,
237
- ))
249
+ check_error(
250
+ lib.libisyntax_read_label_image_jpeg(
251
+ isyntax,
252
+ jpeg_buffer_ptr,
253
+ jpeg_size_ptr,
254
+ ),
255
+ )
238
256
  jpeg_buffer = ffi.gc(jpeg_buffer_ptr[0], free)
239
257
  jpeg_size = jpeg_size_ptr[0]
240
258
  return memoryview(ffi.buffer(jpeg_buffer, jpeg_size))
@@ -243,11 +261,13 @@ def read_label_image_jpeg(isyntax: ISyntaxPtr) -> memoryview:
243
261
  def read_macro_image_jpeg(isyntax: ISyntaxPtr) -> memoryview:
244
262
  jpeg_buffer_ptr = ffi.new("uint8_t**")
245
263
  jpeg_size_ptr = ffi.new("uint32_t*")
246
- check_error(lib.libisyntax_read_macro_image_jpeg(
247
- isyntax,
248
- jpeg_buffer_ptr,
249
- jpeg_size_ptr,
250
- ))
264
+ check_error(
265
+ lib.libisyntax_read_macro_image_jpeg(
266
+ isyntax,
267
+ jpeg_buffer_ptr,
268
+ jpeg_size_ptr,
269
+ ),
270
+ )
251
271
  jpeg_buffer = ffi.gc(jpeg_buffer_ptr[0], free)
252
272
  jpeg_size = jpeg_size_ptr[0]
253
273
  return memoryview(ffi.buffer(jpeg_buffer, jpeg_size))
@@ -256,12 +276,14 @@ def read_macro_image_jpeg(isyntax: ISyntaxPtr) -> memoryview:
256
276
  def read_icc_profile(isyntax: ISyntaxPtr, image: ISyntaxImagePtr) -> memoryview:
257
277
  profile_buffer_ptr = ffi.new("uint8_t**")
258
278
  profile_size_ptr = ffi.new("uint32_t*")
259
- check_error(lib.libisyntax_read_icc_profile(
260
- isyntax,
261
- image,
262
- profile_buffer_ptr,
263
- profile_size_ptr,
264
- ))
279
+ check_error(
280
+ lib.libisyntax_read_icc_profile(
281
+ isyntax,
282
+ image,
283
+ profile_buffer_ptr,
284
+ profile_size_ptr,
285
+ ),
286
+ )
265
287
  profile_buffer = ffi.gc(profile_buffer_ptr[0], free)
266
288
  profile_size = profile_size_ptr[0]
267
289
  return memoryview(ffi.buffer(profile_buffer, profile_size))
@@ -76,6 +76,16 @@ class ISyntaxImage:
76
76
  for i in range(self.level_count):
77
77
  yield self.get_level(i)
78
78
 
79
+ @property
80
+ def offset_x(self) -> int:
81
+ """X-offset in pixels of the WSI image origin from the macro image origin."""
82
+ return libisyntax.image_get_offset_x(self.ptr)
83
+
84
+ @property
85
+ def offset_y(self) -> int:
86
+ """Y-offset in pixels of the WSI image origin from the macro image origin."""
87
+ return libisyntax.image_get_offset_y(self.ptr)
88
+
79
89
 
80
90
  class ISyntax:
81
91
  def __init__(self, f: RawIOBase | BufferedIOBase, n_bytes: int, cache_size: int = 2000) -> None:
@@ -86,7 +96,7 @@ class ISyntax:
86
96
  self.ptr = libisyntax.open_from_registered_handle(self.io_handle, is_init_allocators=False)
87
97
  self.closed = False
88
98
  self._cache_size = cache_size
89
- self._cache = None
99
+ self._cache: ISyntaxCache | None = None
90
100
 
91
101
  @classmethod
92
102
  def open(cls: type["ISyntax"], filename: str | Path, cache_size: int = 2000) -> "ISyntax":
@@ -133,8 +143,15 @@ class ISyntax:
133
143
  """
134
144
  cache = self.get_cache()
135
145
  buf = np.empty((self.tile_height, self.tile_width, 4), dtype=np.uint8)
136
- libisyntax.tile_read(self.ptr, cache.ptr, level, tile_x, tile_y,
137
- buf.data, libisyntax.ISyntaxPixelFormat.RGBA)
146
+ libisyntax.tile_read(
147
+ self.ptr,
148
+ cache.ptr,
149
+ level,
150
+ tile_x,
151
+ tile_y,
152
+ buf.data,
153
+ libisyntax.ISyntaxPixelFormat.RGBA,
154
+ )
138
155
  return buf
139
156
 
140
157
  def read_region(self, x: int, y: int, width: int, height: int, level: int = 0) -> np.ndarray:
@@ -152,8 +169,17 @@ class ISyntax:
152
169
  """
153
170
  cache = self.get_cache()
154
171
  buf = np.empty((height, width, 4), dtype=np.uint8)
155
- libisyntax.read_region(self.ptr, cache.ptr, level, x, y, width, height,
156
- buf.data, libisyntax.ISyntaxPixelFormat.RGBA)
172
+ libisyntax.read_region(
173
+ self.ptr,
174
+ cache.ptr,
175
+ level,
176
+ x,
177
+ y,
178
+ width,
179
+ height,
180
+ buf.data,
181
+ libisyntax.ISyntaxPixelFormat.RGBA,
182
+ )
157
183
  return buf
158
184
 
159
185
  def read_label_image_jpeg(self) -> memoryview | None:
@@ -224,6 +250,16 @@ class ISyntax:
224
250
  level0 = self.wsi.get_level(0)
225
251
  return level0.mpp_y
226
252
 
253
+ @property
254
+ def offset_x(self) -> int:
255
+ """X-offset in pixels of the WSI image origin from the macro image origin."""
256
+ return self.wsi.offset_x
257
+
258
+ @property
259
+ def offset_y(self) -> int:
260
+ """Y-offset in pixels of the WSI image origin from the macro image origin."""
261
+ return self.wsi.offset_y
262
+
227
263
  def __enter__(self) -> "ISyntax":
228
264
  return self
229
265
 
@@ -0,0 +1,112 @@
1
+ import os
2
+ import platform
3
+ from importlib import resources
4
+ from pathlib import Path
5
+
6
+ from cffi import FFI
7
+
8
+ import isyntax_build
9
+
10
+ cdef_extra = """
11
+ extern "Python" {
12
+ bool python_file_set_pos(int, int64_t);
13
+ int64_t python_file_read_into(int, void*, size_t);
14
+ int64_t python_file_get_size(int);
15
+ void python_file_close(int);
16
+ }
17
+
18
+ void free(void *ptr);
19
+ """
20
+
21
+
22
+ def paths_to_strings(*paths: Path) -> list[str]:
23
+ return [str(path) for path in paths]
24
+
25
+
26
+ def create_ffibuilder() -> FFI:
27
+ ffibuilder = FFI()
28
+
29
+ project_dir = Path(__file__).parent.parent
30
+
31
+ vendor = project_dir / "isyntax_build" / "vendor"
32
+ src = project_dir / "isyntax_build" / "src"
33
+ libisyntax_src = vendor / "libisyntax" / "src"
34
+
35
+ if os.name == "nt":
36
+ platform_sources = [
37
+ src / "win32_utils.c",
38
+ ]
39
+ extra_compile_args = ["/std:c11"]
40
+ libraries = ["winmm"]
41
+ else:
42
+ platform_sources = []
43
+ extra_compile_args = []
44
+ libraries = []
45
+ # The vendored libisyntax guards its SIMD/atomics on x86-centric macros
46
+ # that GCC does not define on aarch64 Linux, so the arm build otherwise
47
+ # fails to compile / links x86-only intrinsics. Select the correct paths:
48
+ # __ARM_NEON__ -> intrinsics.h includes <arm_neon.h> for the NEON code
49
+ # __arm64 -> ltalloc spin-wait uses sched_yield, not x86 `pause`
50
+ # the last two map x86-only bit intrinsics onto portable GCC builtins
51
+ if platform.machine() in ("aarch64", "arm64"):
52
+ extra_compile_args += [
53
+ "-D__ARM_NEON__=1",
54
+ "-D__arm64=1",
55
+ "-D_bit_scan_forward=__builtin_ctz",
56
+ "-D_popcnt32=__builtin_popcount",
57
+ ]
58
+
59
+ ffibuilder.set_source(
60
+ "isyntax._pyisyntax",
61
+ resources.read_text(isyntax_build, "pyisyntax.c"),
62
+ sources=paths_to_strings(
63
+ libisyntax_src / "libisyntax.c",
64
+ libisyntax_src / "isyntax" / "isyntax.c",
65
+ libisyntax_src / "isyntax" / "isyntax_reader.c",
66
+ libisyntax_src / "utils" / "timerutils.c",
67
+ libisyntax_src / "utils" / "block_allocator.c",
68
+ libisyntax_src / "utils" / "benaphore.c",
69
+ libisyntax_src / "platform" / "platform.c",
70
+ libisyntax_src / "platform" / "work_queue.c",
71
+ libisyntax_src / "third_party" / "yxml.c",
72
+ libisyntax_src / "third_party" / "ltalloc.cc",
73
+ src / "python_platform_utils.c",
74
+ *platform_sources,
75
+ ),
76
+ include_dirs=paths_to_strings(
77
+ libisyntax_src,
78
+ libisyntax_src / "isyntax",
79
+ libisyntax_src / "platform",
80
+ libisyntax_src / "third_party",
81
+ libisyntax_src / "utils",
82
+ src,
83
+ ),
84
+ extra_compile_args=extra_compile_args,
85
+ libraries=libraries,
86
+ )
87
+
88
+ header_lines = []
89
+ with (libisyntax_src / "libisyntax.h").open() as f:
90
+ for line in f:
91
+ if line.startswith("#"):
92
+ continue
93
+ header_lines.append(line)
94
+ header_lines.append("\n")
95
+ with (src / "python_platform_utils.h").open() as f:
96
+ for line in f:
97
+ if line.startswith("#"):
98
+ continue
99
+ header_lines.append(line)
100
+ header_text = "".join(header_lines)
101
+ header_text += "\n\n"
102
+ header_text += cdef_extra
103
+
104
+ ffibuilder.cdef(header_text)
105
+
106
+ return ffibuilder
107
+
108
+
109
+ ffibuilder = create_ffibuilder()
110
+
111
+ if __name__ == "__main__":
112
+ ffibuilder.compile(verbose=True)
@@ -0,0 +1,91 @@
1
+ #include "libisyntax.h"
2
+
3
+ #include <stdint.h>
4
+ #include <stdio.h>
5
+ #include <dirent.h>
6
+ #include <sys/stat.h>
7
+ #include <sys/types.h>
8
+ #include <malloc.h>
9
+
10
+
11
+ #define CHECK_LIBISYNTAX_OK(_libisyntax_call) do { \
12
+ isyntax_error_t result = _libisyntax_call; \
13
+ assert(result == LIBISYNTAX_OK); \
14
+ } while(0)
15
+
16
+
17
+ #define LOG_VAR(fmt, var) printf("%s: %s=" fmt "\n", __FUNCTION__, #var, var)
18
+
19
+ #ifdef _WIN32
20
+ #define PATH_SEP "\\"
21
+ #else
22
+ #define PATH_SEP "/";
23
+ #endif
24
+
25
+ // This program loops through all the .isyntax files in a folder (including subfolders),
26
+ // and outputs the barcode for each file to stdout (in comma-separated format).
27
+ // This may be useful for large collections of WSIs where you can't tell which is which from the filename.
28
+
29
+ // NOTE: the barcode/label information must be preserved for this to work.
30
+ // This information might be deleted when using the Philips IMS export function (unless that setting is changed).
31
+
32
+ int read_barcode_of_isyntax_files_in_directory(const char* dir, const char* subdir_prefix) {
33
+ // Looping through files in a folder.
34
+ // Reference: https://stackoverflow.com/questions/1271064/how-do-i-loop-through-all-files-in-a-folder-using-c
35
+ struct dirent *dp;
36
+ DIR *dfd;
37
+ if ((dfd = opendir(dir)) == NULL) {
38
+ fprintf(stderr, "Can't open %s\n", dir);
39
+ return 0;
40
+ }
41
+
42
+ char filename[260] ;
43
+ while ((dp = readdir(dfd)) != NULL) {
44
+ struct stat stbuf ;
45
+ sprintf(filename, "%s" PATH_SEP "%s", dir, dp->d_name);
46
+ if(stat(filename, &stbuf) == -1) {
47
+ printf("Unable to stat file: %s\n" , filename);
48
+ continue ;
49
+ }
50
+ if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
51
+ if (dp->d_name[0] != '.') {
52
+ char* subdir_path = malloc(260);
53
+ snprintf(subdir_path, 260, "%s" PATH_SEP "%s", dir, dp->d_name);
54
+ read_barcode_of_isyntax_files_in_directory(subdir_path, dp->d_name); // recurse into subdirectories
55
+ free(subdir_path);
56
+ }
57
+ continue; // Skip directories
58
+ } else {
59
+ size_t filename_len = strlen(dp->d_name);
60
+ const char* suffix = ".isyntax";
61
+ size_t suffix_len = strlen(suffix);
62
+ if (strncmp(dp->d_name + filename_len - suffix_len, suffix, suffix_len) == 0) {
63
+ // Open the iSyntax file in a special mode where the header is only read partially (for speed).
64
+ isyntax_t* isyntax;
65
+ if (libisyntax_open(filename, LIBISYNTAX_OPEN_FLAG_READ_BARCODE_ONLY, &isyntax) == LIBISYNTAX_OK) {
66
+ // Print out the path of the .isyntax file + the barcode in .csv format.
67
+ if (subdir_prefix) {
68
+ printf("%s" PATH_SEP "%s,%s\n", subdir_prefix, dp->d_name, libisyntax_get_barcode(isyntax));
69
+ } else {
70
+ printf("%s,%s\n", dp->d_name, libisyntax_get_barcode(isyntax));
71
+ }
72
+ libisyntax_close(isyntax);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ return 0;
78
+ }
79
+
80
+ int main(int argc, char** argv) {
81
+ if (argc <= 1) {
82
+ printf("Usage: %s <directory_path> - output filename and barcode (comma-separated) for each iSyntax file in the directory and its subdirectories\n",
83
+ argv[0], argv[0], argv[0], argv[0]);
84
+ return 0;
85
+ }
86
+
87
+ libisyntax_init();
88
+
89
+ char* dir = argv[1];
90
+ return read_barcode_of_isyntax_files_in_directory(dir, NULL);
91
+ }