pyisyntax 0.1.0__cp310-cp310-win_amd64.whl

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.
_pyisyntax.pyd ADDED
Binary file
isyntax/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from isyntax.wrapper import ISyntax
2
+
3
+ __all__ = [
4
+ "ISyntax",
5
+ ]
File without changes
@@ -0,0 +1,115 @@
1
+ from collections.abc import Iterator
2
+ from dataclasses import dataclass
3
+ from io import BufferedIOBase, RawIOBase
4
+ from typing import Generic, NewType, TypeVar
5
+
6
+ from _pyisyntax import ffi, lib
7
+
8
+ VoidPtr = NewType("VoidPtr", object)
9
+ T = TypeVar("T")
10
+
11
+
12
+ @dataclass
13
+ class SizedIO:
14
+ #: The IO object.
15
+ f: RawIOBase | BufferedIOBase
16
+ #: The complete length of the IO object's data (in bytes).
17
+ n_bytes: int
18
+
19
+
20
+ class ByHandleRegistry(Generic[T]):
21
+ class _EmptySlot:
22
+ pass
23
+
24
+ def __init__(self) -> None:
25
+ """Creates a registry of objects keyed by integer handles.
26
+
27
+ Handles start at 1, autoincrement, and will automatically be recycled
28
+ once objects are removed from the registry.
29
+ """
30
+ self._list = []
31
+ self._n_free = 0
32
+ self._empty_slot = self._EmptySlot()
33
+
34
+ def pop(self, handle: int) -> T:
35
+ element = self._list[handle - 1]
36
+ self._list[handle - 1] = self._empty_slot
37
+ self._n_free += 1
38
+ if len(self._list) == handle:
39
+ while len(self._list) > 0 and self._list[-1] is self._empty_slot:
40
+ self._list.pop()
41
+ self._n_free -= 1
42
+ return element
43
+
44
+ def add(self, element: T) -> int:
45
+ if self._n_free == 0:
46
+ self._list.append(element)
47
+ return len(self._list)
48
+ index = self._list.index(self._empty_slot)
49
+ self._list[index] = element
50
+ self._n_free -= 1
51
+ # Handles start at 1.
52
+ return index + 1
53
+
54
+ def __getitem__(self, handle: int) -> T:
55
+ return self._list[handle - 1]
56
+
57
+ def items(self) -> Iterator[tuple[int, T]]:
58
+ for i, element in enumerate(self._list):
59
+ if element is not self._empty_slot:
60
+ yield i + 1, element
61
+
62
+
63
+ _io_registry = ByHandleRegistry[SizedIO]()
64
+
65
+
66
+ def init_python_io_hooks() -> None:
67
+ @ffi.def_extern()
68
+ def python_file_set_pos(handle: int, offset: int) -> bool:
69
+ _io_registry[handle].f.seek(offset)
70
+ return True
71
+
72
+
73
+ @ffi.def_extern()
74
+ def python_file_read_into(handle: int, dest: VoidPtr, bytes_to_read: int) -> int:
75
+ bytes_read = _io_registry[handle].f.readinto(ffi.buffer(dest, bytes_to_read))
76
+ if bytes_read is None:
77
+ raise RuntimeError
78
+ return max(bytes_read, 1)
79
+
80
+
81
+ @ffi.def_extern()
82
+ def python_file_get_size(handle: int) -> int:
83
+ return _io_registry[handle].n_bytes
84
+
85
+
86
+ @ffi.def_extern()
87
+ def python_file_close(handle: int) -> None:
88
+ _io_registry.pop(handle).f.close()
89
+
90
+
91
+ lib.init_python_platform_utils(
92
+ lib.python_file_set_pos,
93
+ lib.python_file_read_into,
94
+ lib.python_file_get_size,
95
+ lib.python_file_close,
96
+ )
97
+
98
+
99
+ def register_io(f: RawIOBase | BufferedIOBase, n_bytes: int) -> int:
100
+ """Registers a Python IO object for use with the underlying C library.
101
+
102
+ Args:
103
+ f: IO object to register.
104
+ n_bytes: Size of the IO object's data (in bytes).
105
+
106
+ Raises:
107
+ RuntimeError: When the IO object is not readable.
108
+
109
+ Returns:
110
+ New handle assigned to the IO object.
111
+ """
112
+ if not f.readable:
113
+ msg = "IO object must be readable"
114
+ raise RuntimeError(msg)
115
+ return _io_registry.add(SizedIO(f, n_bytes))
@@ -0,0 +1,260 @@
1
+ import os
2
+ from enum import IntEnum
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING, NewType
5
+
6
+ from _pyisyntax import ffi, lib
7
+
8
+ from isyntax.lowlevel.io_management import init_python_io_hooks, register_io
9
+
10
+ if TYPE_CHECKING:
11
+ from cffi import FFI
12
+ from typing_extensions import Buffer
13
+
14
+
15
+ Pointer = NewType("Pointer", "FFI.CData")
16
+ ISyntaxPtr = NewType("ISyntaxPtr", Pointer)
17
+ ISyntaxImagePtr = NewType("ISyntaxImagePtr", Pointer)
18
+ ISyntaxLevelPtr = NewType("ISyntaxLevelPtr", Pointer)
19
+ ISyntaxCachePtr = NewType("ISyntaxCachePtr", Pointer)
20
+
21
+
22
+ LIBISYNTAX_OK = 0
23
+ # Generic error that the user should not expect to recover from.
24
+ LIBISYNTAX_FATAL = 1
25
+ # One of the arguments passed to a function is invalid.
26
+ LIBISYNTAX_INVALID_ARGUMENT = 2
27
+
28
+
29
+ class ISyntaxPixelFormat(IntEnum):
30
+ RGBA = lib.LIBISYNTAX_PIXEL_FORMAT_RGBA
31
+ BGRA = lib.LIBISYNTAX_PIXEL_FORMAT_BGRA
32
+
33
+
34
+ class LibISyntaxError(Exception):
35
+ pass
36
+
37
+
38
+ class LibISyntaxFatalError(LibISyntaxError):
39
+ pass
40
+
41
+
42
+ class LibISyntaxInvalidArgumentError(LibISyntaxError):
43
+ pass
44
+
45
+
46
+ class LibISyntaxUnknownError(LibISyntaxError):
47
+ pass
48
+
49
+
50
+ class NullPointerError(Exception):
51
+ pass
52
+
53
+
54
+ def check_error(status: int) -> None:
55
+ if status == LIBISYNTAX_OK:
56
+ return
57
+ if status == LIBISYNTAX_FATAL:
58
+ raise LibISyntaxFatalError
59
+ if status == LIBISYNTAX_INVALID_ARGUMENT:
60
+ raise LibISyntaxInvalidArgumentError
61
+ raise LibISyntaxUnknownError
62
+
63
+
64
+ def free(ptr: Pointer) -> None:
65
+ lib.free(ptr)
66
+
67
+
68
+ def _do_init() -> None:
69
+ check_error(lib.libisyntax_init())
70
+ init_python_io_hooks()
71
+
72
+
73
+ def init() -> None:
74
+ ffi.init_once(_do_init, "libisyntax_init")
75
+
76
+
77
+ def open_from_registered_handle(handle: int, *, is_init_allocators: bool = False) -> ISyntaxPtr:
78
+ init()
79
+
80
+ isyntax = ffi.new("isyntax_t**")
81
+ check_error(lib.libisyntax_open(
82
+ ffi.new("char[]", str(handle).encode("utf-8")),
83
+ is_init_allocators,
84
+ isyntax,
85
+ ))
86
+ return isyntax[0]
87
+
88
+
89
+ def open_from_filename(filename: str | Path, *, is_init_allocators: bool = False) -> ISyntaxPtr:
90
+ filename = Path(filename)
91
+ f = filename.open("rb")
92
+ handle = register_io(f, os.fstat(f.fileno()).st_size)
93
+ return open_from_registered_handle(handle, is_init_allocators=is_init_allocators)
94
+
95
+
96
+ def close(isyntax: ISyntaxPtr) -> None:
97
+ # If a null pointer gets through the program will segfault.
98
+ if isyntax == ffi.NULL:
99
+ raise NullPointerError
100
+ lib.libisyntax_close(isyntax)
101
+
102
+
103
+ def get_tile_width(isyntax: ISyntaxPtr) -> int:
104
+ return lib.libisyntax_get_tile_width(isyntax)
105
+
106
+
107
+ def get_tile_height(isyntax: ISyntaxPtr) -> int:
108
+ return lib.libisyntax_get_tile_height(isyntax)
109
+
110
+
111
+ def get_wsi_image_index(isyntax: ISyntaxPtr) -> int:
112
+ return lib.libisyntax_get_wsi_image_index(isyntax)
113
+
114
+
115
+ def get_image(isyntax: ISyntaxPtr, wsi_image_index: int) -> ISyntaxImagePtr:
116
+ return lib.libisyntax_get_image(isyntax, wsi_image_index)
117
+
118
+
119
+ def image_get_level_count(wsi_image: ISyntaxImagePtr) -> int:
120
+ return lib.libisyntax_image_get_level_count(wsi_image)
121
+
122
+
123
+ def image_get_level(wsi_image: ISyntaxImagePtr, index: int) -> ISyntaxLevelPtr:
124
+ return lib.libisyntax_image_get_level(wsi_image, index)
125
+
126
+
127
+ def level_get_scale(level: ISyntaxLevelPtr) -> int:
128
+ return lib.libisyntax_level_get_scale(level)
129
+
130
+
131
+ def level_get_width_in_tiles(level: ISyntaxLevelPtr) -> int:
132
+ return lib.libisyntax_level_get_width_in_tiles(level)
133
+
134
+
135
+ def level_get_height_in_tiles(level: ISyntaxLevelPtr) -> int:
136
+ return lib.libisyntax_level_get_height_in_tiles(level)
137
+
138
+
139
+ def level_get_width(level: ISyntaxLevelPtr) -> int:
140
+ return lib.libisyntax_level_get_width(level)
141
+
142
+
143
+ def level_get_height(level: ISyntaxLevelPtr) -> int:
144
+ return lib.libisyntax_level_get_height(level)
145
+
146
+
147
+ def level_get_mpp_x(level: ISyntaxLevelPtr) -> float:
148
+ return lib.libisyntax_level_get_mpp_x(level)
149
+
150
+
151
+ def level_get_mpp_y(level: ISyntaxLevelPtr) -> float:
152
+ return lib.libisyntax_level_get_mpp_y(level)
153
+
154
+
155
+ def cache_create(debug_name: str | None, cache_size: int) -> ISyntaxCachePtr:
156
+ isyntax_cache = ffi.new("isyntax_cache_t**")
157
+ if debug_name is None:
158
+ debug_name_or_null = ffi.NULL
159
+ else:
160
+ debug_name_or_null = ffi.new("char[]", debug_name.encode("utf-8"))
161
+ check_error(lib.libisyntax_cache_create(
162
+ debug_name_or_null,
163
+ cache_size,
164
+ isyntax_cache,
165
+ ))
166
+ return isyntax_cache[0]
167
+
168
+
169
+ def cache_inject(isyntax_cache: ISyntaxCachePtr, isyntax: ISyntaxPtr) -> None:
170
+ check_error(lib.libisyntax_cache_inject(isyntax_cache, isyntax))
171
+
172
+
173
+ def cache_destroy(isyntax_cache: ISyntaxCachePtr) -> None:
174
+ if isyntax_cache == ffi.NULL:
175
+ raise NullPointerError
176
+ lib.libisyntax_cache_destroy(isyntax_cache)
177
+
178
+
179
+ def tile_read(
180
+ isyntax: ISyntaxPtr,
181
+ isyntax_cache: ISyntaxCachePtr,
182
+ level: int,
183
+ tile_x: int,
184
+ tile_y: int,
185
+ pixels_buffer: "Buffer | FFI.buffer",
186
+ pixel_format: ISyntaxPixelFormat,
187
+ ) -> None:
188
+ check_error(lib.libisyntax_tile_read(
189
+ isyntax,
190
+ isyntax_cache,
191
+ level,
192
+ tile_x,
193
+ tile_y,
194
+ ffi.from_buffer("uint32_t[]", pixels_buffer, require_writable=True),
195
+ pixel_format,
196
+ ))
197
+
198
+
199
+ def read_region(
200
+ isyntax: ISyntaxPtr,
201
+ isyntax_cache: ISyntaxCachePtr,
202
+ level: int,
203
+ x: int,
204
+ y: int,
205
+ width: int,
206
+ height: int,
207
+ pixels_buffer: "Buffer | FFI.buffer",
208
+ pixel_format: ISyntaxPixelFormat,
209
+ ) -> None:
210
+ check_error(lib.libisyntax_read_region(
211
+ isyntax,
212
+ isyntax_cache,
213
+ level,
214
+ x,
215
+ y,
216
+ width,
217
+ height,
218
+ ffi.from_buffer("uint32_t[]", pixels_buffer, require_writable=True),
219
+ pixel_format,
220
+ ))
221
+
222
+
223
+ def read_label_image_jpeg(isyntax: ISyntaxPtr) -> memoryview:
224
+ jpeg_buffer_ptr = ffi.new("uint8_t**")
225
+ jpeg_size_ptr = ffi.new("uint32_t*")
226
+ check_error(lib.libisyntax_read_label_image_jpeg(
227
+ isyntax,
228
+ jpeg_buffer_ptr,
229
+ jpeg_size_ptr,
230
+ ))
231
+ jpeg_buffer = ffi.gc(jpeg_buffer_ptr[0], free)
232
+ jpeg_size = jpeg_size_ptr[0]
233
+ return memoryview(ffi.buffer(jpeg_buffer, jpeg_size))
234
+
235
+
236
+ def read_macro_image_jpeg(isyntax: ISyntaxPtr) -> memoryview:
237
+ jpeg_buffer_ptr = ffi.new("uint8_t**")
238
+ jpeg_size_ptr = ffi.new("uint32_t*")
239
+ check_error(lib.libisyntax_read_macro_image_jpeg(
240
+ isyntax,
241
+ jpeg_buffer_ptr,
242
+ jpeg_size_ptr,
243
+ ))
244
+ jpeg_buffer = ffi.gc(jpeg_buffer_ptr[0], free)
245
+ jpeg_size = jpeg_size_ptr[0]
246
+ return memoryview(ffi.buffer(jpeg_buffer, jpeg_size))
247
+
248
+
249
+ def read_icc_profile(isyntax: ISyntaxPtr, image: ISyntaxImagePtr) -> memoryview:
250
+ profile_buffer_ptr = ffi.new("uint8_t**")
251
+ profile_size_ptr = ffi.new("uint32_t*")
252
+ check_error(lib.libisyntax_read_icc_profile(
253
+ isyntax,
254
+ image,
255
+ profile_buffer_ptr,
256
+ profile_size_ptr,
257
+ ))
258
+ profile_buffer = ffi.gc(profile_buffer_ptr[0], free)
259
+ profile_size = profile_size_ptr[0]
260
+ return memoryview(ffi.buffer(profile_buffer, profile_size))
isyntax/wrapper.py ADDED
@@ -0,0 +1,228 @@
1
+ import os
2
+ from collections.abc import Iterator
3
+ from io import BufferedIOBase, RawIOBase
4
+ from pathlib import Path
5
+ from types import TracebackType
6
+
7
+ import numpy as np
8
+
9
+ from isyntax.lowlevel import libisyntax
10
+ from isyntax.lowlevel.io_management import register_io
11
+
12
+
13
+ class ISyntaxCache:
14
+ def __init__(self, debug_name: str | None = None, cache_size: int = 2000) -> None:
15
+ self.ptr = libisyntax.cache_create(debug_name, cache_size)
16
+ self.destroyed = False
17
+
18
+ def inject(self, isyntax: "ISyntax") -> None:
19
+ libisyntax.cache_inject(self.ptr, isyntax.ptr)
20
+
21
+ def destroy(self) -> None:
22
+ libisyntax.cache_destroy(self.ptr)
23
+ self.destroyed = True
24
+
25
+ def __del__(self) -> None:
26
+ if not self.destroyed:
27
+ self.destroy()
28
+
29
+
30
+ class ISyntaxLevel:
31
+ def __init__(self, ptr: libisyntax.ISyntaxLevelPtr) -> None:
32
+ self.ptr = ptr
33
+
34
+ @property
35
+ def scale(self) -> int:
36
+ return libisyntax.level_get_scale(self.ptr)
37
+
38
+ @property
39
+ def width_in_tiles(self) -> int:
40
+ return libisyntax.level_get_width_in_tiles(self.ptr)
41
+
42
+ @property
43
+ def height_in_tiles(self) -> int:
44
+ return libisyntax.level_get_height_in_tiles(self.ptr)
45
+
46
+ @property
47
+ def width(self) -> int:
48
+ return libisyntax.level_get_width(self.ptr)
49
+
50
+ @property
51
+ def height(self) -> int:
52
+ return libisyntax.level_get_height(self.ptr)
53
+
54
+ @property
55
+ def mpp_x(self) -> float:
56
+ return libisyntax.level_get_mpp_x(self.ptr)
57
+
58
+ @property
59
+ def mpp_y(self) -> float:
60
+ return libisyntax.level_get_mpp_y(self.ptr)
61
+
62
+
63
+ class ISyntaxImage:
64
+ def __init__(self, ptr: libisyntax.ISyntaxImagePtr) -> None:
65
+ self.ptr = ptr
66
+
67
+ @property
68
+ def level_count(self) -> int:
69
+ return libisyntax.image_get_level_count(self.ptr)
70
+
71
+ def get_level(self, level_index: int) -> ISyntaxLevel:
72
+ return ISyntaxLevel(libisyntax.image_get_level(self.ptr, level_index))
73
+
74
+ @property
75
+ def levels(self) -> Iterator[ISyntaxLevel]:
76
+ for i in range(self.level_count):
77
+ yield self.get_level(i)
78
+
79
+
80
+ class ISyntax:
81
+ def __init__(self, f: RawIOBase | BufferedIOBase, n_bytes: int) -> None:
82
+ self.io_handle = register_io(f, n_bytes)
83
+ self.ptr = libisyntax.open_from_registered_handle(self.io_handle, is_init_allocators=False)
84
+ self.closed = False
85
+ self._cache = None
86
+
87
+ @classmethod
88
+ def open(cls: type["ISyntax"], filename: str | Path) -> "ISyntax":
89
+ filename = Path(filename)
90
+ f = filename.open("rb")
91
+ n_bytes = os.fstat(f.fileno()).st_size
92
+ return cls(f, n_bytes)
93
+
94
+ def close(self) -> None:
95
+ if self.closed:
96
+ return
97
+ libisyntax.close(self.ptr)
98
+ self.closed = True
99
+
100
+ @property
101
+ def tile_width(self) -> int:
102
+ return libisyntax.get_tile_width(self.ptr)
103
+
104
+ @property
105
+ def tile_height(self) -> int:
106
+ return libisyntax.get_tile_height(self.ptr)
107
+
108
+ @property
109
+ def wsi_index(self) -> int:
110
+ return libisyntax.get_wsi_image_index(self.ptr)
111
+
112
+ def get_image(self, image_index: int) -> ISyntaxImage:
113
+ return ISyntaxImage(libisyntax.get_image(self.ptr, image_index))
114
+
115
+ @property
116
+ def wsi(self) -> ISyntaxImage:
117
+ return self.get_image(self.wsi_index)
118
+
119
+ def get_cache(self) -> ISyntaxCache:
120
+ if self._cache is None:
121
+ self._cache = ISyntaxCache(cache_size=2000)
122
+ self._cache.inject(self)
123
+ return self._cache
124
+
125
+ def read_tile(self, tile_x: int, tile_y: int, level: int = 0) -> np.ndarray:
126
+ """Reads RGBA pixel data from the specified tile.
127
+
128
+ Args:
129
+ tile_x: Tile column.
130
+ tile_y: Tile row.
131
+ level: Level number. Defaults to 0.
132
+
133
+ Returns:
134
+ Region RGBA pixel data in a [tile_height, tile_width, 4] array.
135
+ """
136
+ cache = self.get_cache()
137
+ buf = np.empty((self.tile_height, self.tile_width, 4), dtype=np.uint8)
138
+ libisyntax.tile_read(self.ptr, cache.ptr, level, tile_x, tile_y,
139
+ buf.data, libisyntax.ISyntaxPixelFormat.RGBA)
140
+ return buf
141
+
142
+ def read_region(self, x: int, y: int, width: int, height: int, level: int = 0) -> np.ndarray:
143
+ """Reads RGBA pixel data from the specified region.
144
+
145
+ Args:
146
+ x: Left edge position of the region in the target level reference frame.
147
+ y: Top edge position of the region in the target level reference frame.
148
+ width: Width of the region.
149
+ height: Height of the region.
150
+ level: Level number. Defaults to 0.
151
+
152
+ Returns:
153
+ Region RGBA pixel data in a [height, width, 4] array.
154
+ """
155
+ cache = self.get_cache()
156
+ buf = np.empty((height, width, 4), dtype=np.uint8)
157
+ libisyntax.read_region(self.ptr, cache.ptr, level, x, y, width, height,
158
+ buf.data, libisyntax.ISyntaxPixelFormat.RGBA)
159
+ return buf
160
+
161
+ def read_label_image_jpeg(self) -> memoryview:
162
+ """Reads the associated label image as a JPEG-compressed image.
163
+
164
+ Returns:
165
+ Compressed JPEG image data.
166
+ """
167
+ return libisyntax.read_label_image_jpeg(self.ptr)
168
+
169
+ def read_macro_image_jpeg(self) -> memoryview:
170
+ """Reads the associated macro image as a JPEG-compressed image.
171
+
172
+ Returns:
173
+ Compressed JPEG image data.
174
+ """
175
+ return libisyntax.read_macro_image_jpeg(self.ptr)
176
+
177
+ def read_icc_profile(self, image_index: int) -> memoryview:
178
+ """Reads the ICC color profile for an image.
179
+
180
+ Returns:
181
+ The ICC color profile.
182
+ """
183
+ return libisyntax.read_icc_profile(self.ptr, self.get_image(image_index).ptr)
184
+
185
+ @property
186
+ def level_count(self) -> int:
187
+ return self.wsi.level_count
188
+
189
+ @property
190
+ def dimensions(self) -> tuple[int, int]:
191
+ level0 = self.wsi.get_level(0)
192
+ return (level0.width, level0.height)
193
+
194
+ @property
195
+ def level_dimensions(self) -> list[tuple[int, int]]:
196
+ return [(level.width, level.height) for level in self.wsi.levels]
197
+
198
+ @property
199
+ def level_downsamples(self) -> list[int]:
200
+ return [1 << level.scale for level in self.wsi.levels]
201
+
202
+ @property
203
+ def level_tiles(self) -> list[tuple[int, int]]:
204
+ return [(level.width_in_tiles, level.height_in_tiles) for level in self.wsi.levels]
205
+
206
+ @property
207
+ def mpp_x(self) -> float:
208
+ level0 = self.wsi.get_level(0)
209
+ return level0.mpp_x
210
+
211
+ @property
212
+ def mpp_y(self) -> float:
213
+ level0 = self.wsi.get_level(0)
214
+ return level0.mpp_y
215
+
216
+ def __enter__(self) -> "ISyntax":
217
+ return self
218
+
219
+ def __exit__(
220
+ self,
221
+ exc_type: type[BaseException] | None,
222
+ exc: BaseException | None,
223
+ traceback: TracebackType | None,
224
+ ) -> None:
225
+ self.close()
226
+
227
+ def __del__(self) -> None:
228
+ self.close()
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2024 Aiden Nibali
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.1
2
+ Name: pyisyntax
3
+ Version: 0.1.0
4
+ Summary: Python bindings for libisyntax
5
+ Author-email: Aiden Nibali <dismaldenizen@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: cffi >=1.12
11
+ Requires-Dist: numpy
12
+ Provides-Extra: dev
13
+ Requires-Dist: conda-lock ; extra == 'dev'
14
+ Requires-Dist: pytest ; extra == 'dev'
15
+ Requires-Dist: pytest-mock ; extra == 'dev'
16
+ Requires-Dist: ruff ==0.2.1 ; extra == 'dev'
17
+ Requires-Dist: twine ; extra == 'dev'
18
+
19
+ # pyisyntax
20
+
21
+ A Python library for working with pathology images in the iSyntax file format,
22
+ powered by [libisyntax](https://github.com/amspath/libisyntax).
23
+
24
+ ## Usage
25
+
26
+ Read and display a region of the WSI via
27
+ [Pillow](https://pillow.readthedocs.io/).
28
+
29
+ ```python
30
+ from isyntax import ISyntax
31
+ import PIL.Image
32
+
33
+ with ISyntax.open("my_file.isyntax") as isyntax:
34
+ # Read pixels from the specified region into a numpy array
35
+ pixels = isyntax.read_region(500, 500, 400, 200, level=4)
36
+ # Convert numpy array into a PIL image
37
+ pil_image = PIL.Image.fromarray(pixels)
38
+ # Show the image
39
+ pil_image.show()
40
+ ```
41
+
42
+ Extract and save the associated macro image.
43
+
44
+ ```python
45
+ from isyntax import ISyntax
46
+
47
+ with ISyntax.open("my_file.isyntax") as isyntax:
48
+ # The macro image will be returned as compressed JPEG data.
49
+ jpeg_data = isyntax.read_macro_image_jpeg()
50
+ # This JPEG data can be written directly to a file.
51
+ with open("macro_image.jpg", "wb") as f:
52
+ f.write(jpeg_data)
53
+ # Alternatively, you could decompress the data using Pillow:
54
+ # pil_image = PIL.Image.open(io.BytesIO(jpeg_data), formats=["JPEG"])
55
+ ```
56
+
57
+ ## Development
58
+
59
+ ### Dependency management
60
+
61
+ To set up a development environment from the lock file:
62
+
63
+ 1. Ensure that you have
64
+ [micromamba](https://mamba.readthedocs.io/en/latest/user_guide/micromamba.html)
65
+ installed.
66
+ 2. Create the environment:
67
+ ```console
68
+ $ micromamba create -n pyisyntax -f conda-lock.yml --category main --category dev
69
+ ```
70
+ 3. Activate the environment:
71
+ ```console
72
+ $ micromamba activate pyisyntax
73
+ ```
74
+
75
+ To modify pyisyntax project dependencies:
76
+
77
+ 1. Edit pyproject.toml.
78
+ 2. Update the lock file using conda-lock:
79
+ ```console
80
+ $ conda-lock lock -f pyproject.toml -p linux-64 --micromamba
81
+ ```
@@ -0,0 +1,11 @@
1
+ _pyisyntax.pyd,sha256=Sn67vwQVGNa3JTMFRcbW2A7DHQ6TVEoc-EoZBr-zwVY,164864
2
+ isyntax/__init__.py,sha256=m4BuPQ8bPeEoltBIpWdyHiZjXh4rTrnXyQxa71YtQNA,66
3
+ isyntax/wrapper.py,sha256=b5M21yIb9D7D6bR16y_lcqAHaCFeXHWqUIkv_ntAb8I,6887
4
+ isyntax/lowlevel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ isyntax/lowlevel/io_management.py,sha256=gz0M0xy70kPTJeAdH2ETUB8vYBVq9WJCmhGIH2stkOQ,3227
6
+ isyntax/lowlevel/libisyntax.py,sha256=zR75XPpPsGrOmJMEMktWCKfUmukvzj34MRff6n3qA8Q,6947
7
+ pyisyntax-0.1.0.dist-info/LICENSE,sha256=eKo8d-7o2VI1N6WpXbAPFDjlUt-zPcTh15qDBbmdvnc,1056
8
+ pyisyntax-0.1.0.dist-info/METADATA,sha256=9sO6DZIg16JTmxZReQMtpCUWxqdU-LkQ4AxEL0KGBOQ,2371
9
+ pyisyntax-0.1.0.dist-info/WHEEL,sha256=5JPYeYl5ZdvdSkrGS4u21mmpPzpFx42qrXOSIgWf4pg,102
10
+ pyisyntax-0.1.0.dist-info/top_level.txt,sha256=AN4WEBKUcjQivO8_X2eugc6S8ViTNFJO-xKLOMbTbAw,19
11
+ pyisyntax-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.42.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp310-cp310-win_amd64
5
+
@@ -0,0 +1,2 @@
1
+ _pyisyntax
2
+ isyntax