tiny-metaio 0.1.1__py3-none-any.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.
- metaio/__init__.py +4 -0
- metaio/image.py +134 -0
- metaio/py.typed +0 -0
- metaio/read.py +185 -0
- metaio/write.py +57 -0
- tiny_metaio-0.1.1.dist-info/METADATA +66 -0
- tiny_metaio-0.1.1.dist-info/RECORD +9 -0
- tiny_metaio-0.1.1.dist-info/WHEEL +5 -0
- tiny_metaio-0.1.1.dist-info/top_level.txt +1 -0
metaio/__init__.py
ADDED
metaio/image.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import zlib
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import numpy.typing as npt
|
|
7
|
+
|
|
8
|
+
from . import write
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MetaImage:
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
data: npt.NDArray[np.generic],
|
|
15
|
+
*,
|
|
16
|
+
spacing: npt.NDArray[np.floating] | None = None,
|
|
17
|
+
origin: npt.NDArray[np.floating] | None = None,
|
|
18
|
+
direction: npt.NDArray[np.floating] | None = None,
|
|
19
|
+
metadata: dict[str, str] | None = None,
|
|
20
|
+
vector: bool = False,
|
|
21
|
+
) -> None:
|
|
22
|
+
self.data = np.asarray(data)
|
|
23
|
+
self.vector = vector
|
|
24
|
+
|
|
25
|
+
self.spacing = spacing
|
|
26
|
+
self.origin = origin
|
|
27
|
+
self.direction = direction
|
|
28
|
+
|
|
29
|
+
self.metadata: dict[str, str] = {}
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def size(self) -> tuple[int, ...]:
|
|
33
|
+
if self.vector:
|
|
34
|
+
return self.data.shape[:-1][::-1]
|
|
35
|
+
else:
|
|
36
|
+
return self.data.shape[::-1]
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def spacing(self) -> npt.NDArray[np.floating]:
|
|
40
|
+
return self._spacing
|
|
41
|
+
|
|
42
|
+
@spacing.setter
|
|
43
|
+
def spacing(self, spacing: npt.NDArray[np.floating] | None) -> None:
|
|
44
|
+
if spacing is None:
|
|
45
|
+
spacing = np.ones(self.ndim)
|
|
46
|
+
else:
|
|
47
|
+
if len(spacing) != self.ndim:
|
|
48
|
+
raise ValueError
|
|
49
|
+
spacing = np.asarray(spacing)
|
|
50
|
+
self._spacing = spacing
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def origin(self) -> npt.NDArray[np.floating]:
|
|
54
|
+
return self._origin
|
|
55
|
+
|
|
56
|
+
@origin.setter
|
|
57
|
+
def origin(self, origin: npt.NDArray[np.floating] | None) -> None:
|
|
58
|
+
if origin is None:
|
|
59
|
+
origin = np.zeros(self.ndim)
|
|
60
|
+
else:
|
|
61
|
+
if len(origin) != self.ndim:
|
|
62
|
+
raise ValueError
|
|
63
|
+
origin = np.asarray(origin)
|
|
64
|
+
self._origin = origin
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def direction(self) -> npt.NDArray[np.floating]:
|
|
68
|
+
return self._direction
|
|
69
|
+
|
|
70
|
+
@direction.setter
|
|
71
|
+
def direction(self, direction: npt.NDArray[np.floating] | None) -> None:
|
|
72
|
+
if direction is None:
|
|
73
|
+
direction = np.eye(self.ndim).ravel(order="F")
|
|
74
|
+
else:
|
|
75
|
+
if len(direction) != self.ndim**2:
|
|
76
|
+
raise ValueError
|
|
77
|
+
direction = np.asarray(direction)
|
|
78
|
+
self._direction = direction
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def ndim(self) -> int:
|
|
82
|
+
return self.data.ndim - (1 if self.vector else 0)
|
|
83
|
+
|
|
84
|
+
def save(self, path: os.PathLike[str], compress: bool = False) -> None:
|
|
85
|
+
path = Path(path)
|
|
86
|
+
data = np.asarray(self.data)
|
|
87
|
+
|
|
88
|
+
native_dtype: np.dtype[np.generic] = data.dtype.newbyteorder("=")
|
|
89
|
+
data = data.astype(native_dtype, copy=False)
|
|
90
|
+
element_type = write.element_type_for(native_dtype)
|
|
91
|
+
|
|
92
|
+
le_dtype: np.dtype[np.generic] = native_dtype.newbyteorder("<")
|
|
93
|
+
pixel_bytes = data.astype(le_dtype, copy=False).tobytes()
|
|
94
|
+
|
|
95
|
+
if compress:
|
|
96
|
+
pixel_bytes = zlib.compress(pixel_bytes)
|
|
97
|
+
|
|
98
|
+
header_path = path.with_suffix(".mha")
|
|
99
|
+
element_data_file = "LOCAL"
|
|
100
|
+
|
|
101
|
+
def line(key: str, val: object) -> None:
|
|
102
|
+
hfh.write(f"{key} = {val}\n".encode("ascii"))
|
|
103
|
+
|
|
104
|
+
with header_path.open("wb") as hfh:
|
|
105
|
+
line("ObjectType", self.metadata.get("ObjectType", "Image"))
|
|
106
|
+
line("NDims", self.ndim)
|
|
107
|
+
line("BinaryData", "True")
|
|
108
|
+
line("BinaryDataByteOrderMSB", "False")
|
|
109
|
+
line("CompressedData", "True" if compress else "False")
|
|
110
|
+
if compress:
|
|
111
|
+
line("CompressedDataSize", len(pixel_bytes))
|
|
112
|
+
if self.vector:
|
|
113
|
+
line("ElementNumberOfChannels", self.ndim)
|
|
114
|
+
line(
|
|
115
|
+
"TransformMatrix",
|
|
116
|
+
write.fmt_floats(
|
|
117
|
+
np.array(self.direction)
|
|
118
|
+
.reshape((self.ndim, self.ndim))
|
|
119
|
+
.ravel(order="F")
|
|
120
|
+
.tolist()
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
line("Offset", write.fmt_floats(self.origin))
|
|
124
|
+
line("ElementSpacing", write.fmt_floats(self.spacing))
|
|
125
|
+
line("DimSize", write.fmt_ints(self.size))
|
|
126
|
+
|
|
127
|
+
for k, v in self.metadata.items():
|
|
128
|
+
if k not in write.MANAGED:
|
|
129
|
+
line(k, v)
|
|
130
|
+
|
|
131
|
+
line("ElementType", element_type)
|
|
132
|
+
line("ElementDataFile", element_data_file)
|
|
133
|
+
|
|
134
|
+
hfh.write(pixel_bytes)
|
metaio/py.typed
ADDED
|
File without changes
|
metaio/read.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import zlib
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import BinaryIO
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import numpy.typing as npt
|
|
8
|
+
|
|
9
|
+
from .image import MetaImage
|
|
10
|
+
|
|
11
|
+
_MET_DTYPE: dict[str, str] = {
|
|
12
|
+
"MET_CHAR": "int8",
|
|
13
|
+
"MET_UCHAR": "uint8",
|
|
14
|
+
"MET_SHORT": "int16",
|
|
15
|
+
"MET_USHORT": "uint16",
|
|
16
|
+
"MET_INT": "int32",
|
|
17
|
+
"MET_UINT": "uint32",
|
|
18
|
+
"MET_LONG": "int32",
|
|
19
|
+
"MET_ULONG": "uint32",
|
|
20
|
+
"MET_LONG_LONG": "int64",
|
|
21
|
+
"MET_ULONG_LONG": "uint64",
|
|
22
|
+
"MET_FLOAT": "float32",
|
|
23
|
+
"MET_DOUBLE": "float64",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _parse_header(src: BinaryIO) -> tuple[dict[str, str], int]:
|
|
28
|
+
header: dict[str, str] = {}
|
|
29
|
+
line_buf = bytearray()
|
|
30
|
+
|
|
31
|
+
while True:
|
|
32
|
+
byte = src.read(1)
|
|
33
|
+
if not byte:
|
|
34
|
+
raise EOFError("Reached end of file before finding ElementDataFile.")
|
|
35
|
+
if byte == b"\n":
|
|
36
|
+
line = line_buf.decode("ascii", errors="replace").strip()
|
|
37
|
+
line_buf.clear()
|
|
38
|
+
if not line or line.startswith("#"):
|
|
39
|
+
continue
|
|
40
|
+
if "=" not in line:
|
|
41
|
+
continue
|
|
42
|
+
key_raw, _, val_raw = line.partition("=")
|
|
43
|
+
key = key_raw.strip()
|
|
44
|
+
val = val_raw.strip()
|
|
45
|
+
header[key] = val
|
|
46
|
+
if key == "ElementDataFile":
|
|
47
|
+
break
|
|
48
|
+
else:
|
|
49
|
+
line_buf.extend(byte)
|
|
50
|
+
|
|
51
|
+
return header, src.tell()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _require(header: dict[str, str], key: str) -> str:
|
|
55
|
+
try:
|
|
56
|
+
return header[key]
|
|
57
|
+
except KeyError:
|
|
58
|
+
raise KeyError(f"Required MetaIO key '{key}' not found in header.") from None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _parse_float_list(s: str) -> list[float]:
|
|
62
|
+
return [float(x) for x in s.split()]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _parse_int_list(s: str) -> list[int]:
|
|
66
|
+
return [int(x) for x in s.split()]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def read_mha(path: str | os.PathLike[str]) -> MetaImage:
|
|
70
|
+
"""
|
|
71
|
+
Read a MetaIO image file (``.mha`` or ``.mhd``).
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
path:
|
|
76
|
+
Path to the ``.mha`` or ``.mhd`` file.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
MhaImage
|
|
81
|
+
Parsed image with voxel ``data`` shaped ``(Z, Y, X[, C])`` and all
|
|
82
|
+
available spatial metadata populated.
|
|
83
|
+
|
|
84
|
+
Raises
|
|
85
|
+
------
|
|
86
|
+
KeyError
|
|
87
|
+
If a required header field is absent.
|
|
88
|
+
ValueError
|
|
89
|
+
If the pixel type is unsupported or the data length is wrong.
|
|
90
|
+
FileNotFoundError
|
|
91
|
+
If the file (or its companion ``.raw``) cannot be found.
|
|
92
|
+
"""
|
|
93
|
+
path = Path(path)
|
|
94
|
+
with path.open("rb") as fh:
|
|
95
|
+
header, _data_offset = _parse_header(fh)
|
|
96
|
+
|
|
97
|
+
element_type = _require(header, "ElementType").upper()
|
|
98
|
+
if element_type not in _MET_DTYPE:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"Unsupported ElementType '{element_type}'. "
|
|
101
|
+
f"Supported: {list(_MET_DTYPE)}"
|
|
102
|
+
)
|
|
103
|
+
dtype = np.dtype(_MET_DTYPE[element_type])
|
|
104
|
+
|
|
105
|
+
ndim = int(_require(header, "NDims"))
|
|
106
|
+
sizes = _parse_int_list(_require(header, "DimSize"))
|
|
107
|
+
if len(sizes) != ndim:
|
|
108
|
+
raise ValueError(f"NDims={ndim} but DimSize has {len(sizes)} entries.")
|
|
109
|
+
|
|
110
|
+
spacing: list[float] = []
|
|
111
|
+
if "ElementSpacing" in header:
|
|
112
|
+
spacing = _parse_float_list(header["ElementSpacing"])
|
|
113
|
+
elif "ElementSize" in header:
|
|
114
|
+
spacing = _parse_float_list(header["ElementSize"])
|
|
115
|
+
|
|
116
|
+
origin: list[float] = []
|
|
117
|
+
if "Offset" in header:
|
|
118
|
+
origin = _parse_float_list(header["Offset"])
|
|
119
|
+
elif "Position" in header:
|
|
120
|
+
origin = _parse_float_list(header["Position"])
|
|
121
|
+
|
|
122
|
+
transform_matrix: list[float] | None = None
|
|
123
|
+
if "TransformMatrix" in header:
|
|
124
|
+
transform_matrix = _parse_float_list(header["TransformMatrix"])
|
|
125
|
+
elif "Rotation" in header:
|
|
126
|
+
transform_matrix = _parse_float_list(header["Rotation"])
|
|
127
|
+
|
|
128
|
+
transform_matrix = (
|
|
129
|
+
np.array(transform_matrix).reshape((ndim, ndim)).ravel(order="F")
|
|
130
|
+
).tolist()
|
|
131
|
+
|
|
132
|
+
big_endian_str = header.get("BinaryDataByteOrderMSB", "False").strip()
|
|
133
|
+
big_endian = big_endian_str.lower() in ("true", "1")
|
|
134
|
+
dtype = dtype.newbyteorder(">" if big_endian else "<")
|
|
135
|
+
|
|
136
|
+
n_channels = int(header.get("ElementNumberOfChannels", "1"))
|
|
137
|
+
|
|
138
|
+
element_data_file = _require(header, "ElementDataFile").strip()
|
|
139
|
+
compressed = header.get("CompressedData", "False").strip().lower() in (
|
|
140
|
+
"true",
|
|
141
|
+
"1",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if element_data_file == "LOCAL":
|
|
145
|
+
raw_bytes: bytes = fh.read()
|
|
146
|
+
else:
|
|
147
|
+
raw_path = path.parent / element_data_file
|
|
148
|
+
if not raw_path.exists():
|
|
149
|
+
raise FileNotFoundError(f"Companion data file not found: {raw_path}")
|
|
150
|
+
with raw_path.open("rb") as raw_fh:
|
|
151
|
+
raw_bytes = raw_fh.read()
|
|
152
|
+
|
|
153
|
+
if compressed:
|
|
154
|
+
raw_bytes = zlib.decompress(raw_bytes)
|
|
155
|
+
|
|
156
|
+
n_voxels = 1
|
|
157
|
+
for s in sizes:
|
|
158
|
+
n_voxels *= s
|
|
159
|
+
n_elements = n_voxels * n_channels
|
|
160
|
+
expected_bytes = n_elements * dtype.itemsize
|
|
161
|
+
|
|
162
|
+
if len(raw_bytes) < expected_bytes:
|
|
163
|
+
raise ValueError(
|
|
164
|
+
f"Expected {expected_bytes} bytes of pixel data but got {len(raw_bytes)}."
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
flat: npt.NDArray[np.generic] = np.frombuffer(
|
|
168
|
+
raw_bytes[:expected_bytes], dtype=dtype
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
shape: tuple[int, ...] = tuple(reversed(sizes))
|
|
172
|
+
if n_channels > 1:
|
|
173
|
+
shape = shape + (n_channels,)
|
|
174
|
+
|
|
175
|
+
data = flat.reshape(shape)
|
|
176
|
+
data = data.astype(data.dtype.newbyteorder("="), copy=False)
|
|
177
|
+
|
|
178
|
+
return MetaImage(
|
|
179
|
+
data=data,
|
|
180
|
+
spacing=np.asarray(spacing),
|
|
181
|
+
origin=np.asarray(origin),
|
|
182
|
+
direction=np.asarray(transform_matrix),
|
|
183
|
+
metadata=header,
|
|
184
|
+
vector=n_channels != 1,
|
|
185
|
+
)
|
metaio/write.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import numpy.typing as npt
|
|
5
|
+
|
|
6
|
+
_DTYPE_MET: dict[tuple[str, int], str] = {
|
|
7
|
+
("u", 1): "MET_UCHAR",
|
|
8
|
+
("i", 1): "MET_CHAR",
|
|
9
|
+
("u", 2): "MET_USHORT",
|
|
10
|
+
("i", 2): "MET_SHORT",
|
|
11
|
+
("u", 4): "MET_UINT",
|
|
12
|
+
("i", 4): "MET_INT",
|
|
13
|
+
("u", 8): "MET_ULONG_LONG",
|
|
14
|
+
("i", 8): "MET_LONG_LONG",
|
|
15
|
+
("f", 4): "MET_FLOAT",
|
|
16
|
+
("f", 8): "MET_DOUBLE",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
MANAGED = {
|
|
20
|
+
"ObjectType",
|
|
21
|
+
"NDims",
|
|
22
|
+
"BinaryData",
|
|
23
|
+
"BinaryDataByteOrderMSB",
|
|
24
|
+
"CompressedData",
|
|
25
|
+
"CompressedDataSize",
|
|
26
|
+
"TransformMatrix",
|
|
27
|
+
"Offset",
|
|
28
|
+
"Position",
|
|
29
|
+
"ElementSpacing",
|
|
30
|
+
"ElementSize",
|
|
31
|
+
"DimSize",
|
|
32
|
+
"ElementNumberOfChannels",
|
|
33
|
+
"ElementType",
|
|
34
|
+
"ElementDataFile",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def fmt_floats(values: Sequence[float] | npt.NDArray[np.floating]) -> str:
|
|
39
|
+
if isinstance(values, np.ndarray):
|
|
40
|
+
values = values.tolist()
|
|
41
|
+
return " ".join(repr(v) for v in values)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def fmt_ints(values: Sequence[int]) -> str:
|
|
45
|
+
return " ".join(str(v) for v in values)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def element_type_for(dtype: np.dtype[np.generic]) -> str:
|
|
49
|
+
"""Return the MetaIO ElementType string for *dtype*, raising on failure."""
|
|
50
|
+
key = (dtype.kind, dtype.itemsize)
|
|
51
|
+
if key not in _DTYPE_MET:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"No MetaIO ElementType for dtype '{dtype}' "
|
|
54
|
+
f"(kind='{dtype.kind}', itemsize={dtype.itemsize}). "
|
|
55
|
+
f"Supported numpy kinds/sizes: {list(_DTYPE_MET)}"
|
|
56
|
+
)
|
|
57
|
+
return _DTYPE_MET[key]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tiny-metaio
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Read and write MetaImages with minimal dependencies
|
|
5
|
+
Author-email: Nicolas Cedilnik <nicolas.cedilnik@inria.fr>
|
|
6
|
+
Project-URL: Homepage, https://gitlab.inria.fr/ncedilni/metaio
|
|
7
|
+
Project-URL: Issues, https://gitlab.inria.fr/ncedilni/metaio/-/issues
|
|
8
|
+
Project-URL: Repository, https://gitlab.inria.fr/ncedilni/metaio
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: numpy~=2.0
|
|
17
|
+
|
|
18
|
+
# MetaIO
|
|
19
|
+
|
|
20
|
+
Read and write [MetaImages](https://docs.itk.org/en/latest/learn/metaio.html)
|
|
21
|
+
in python with minimal dependencies.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Available on pypi.org: `pip install metaio`.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Writing a MHA file
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
>>> from metaio import MetaImage, read_mha
|
|
33
|
+
>>> img = MetaImage([[0, 1], [42, 43]], spacing=[1, 2])
|
|
34
|
+
>>> img.save("some-name.mha")
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Reading a MHA file
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
>>> img = read_mha("some-name.mha")
|
|
42
|
+
>>> img.spacing
|
|
43
|
+
array([1., 2.])
|
|
44
|
+
>>> img.data
|
|
45
|
+
array([[ 0, 1],
|
|
46
|
+
[42, 43]])
|
|
47
|
+
>>> img.direction
|
|
48
|
+
array([1., 0., 0., 1.])
|
|
49
|
+
>>> img.origin
|
|
50
|
+
array([0., 0.])
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Philosophy
|
|
55
|
+
|
|
56
|
+
- `numpy` as only runtime dependency.
|
|
57
|
+
- idiomatic python.
|
|
58
|
+
- similar behavior than SimpleITK's `GetArrayFromImage`, `GetImageFromArray`,
|
|
59
|
+
`GetDirection`, `GetOrigin`, `GetSpacing`, `ReadImage`, `WriteImage`.
|
|
60
|
+
|
|
61
|
+
## Why should I use this over SimpleITK?
|
|
62
|
+
|
|
63
|
+
If you just need the IO parts and do not want the large SimpleITK package,
|
|
64
|
+
this package might be for you.
|
|
65
|
+
If you do not care about having SimpleITK as a dependency for your project,
|
|
66
|
+
this package is not for you.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
metaio/__init__.py,sha256=ESxrAVQNytShMXAbnloV45CNK-DdoauwoeDrpL9obYU,91
|
|
2
|
+
metaio/image.py,sha256=OUDB8BYP2AC4nfOzfpRjdzSOmvU0GshwFJH1CZQ4hTE,4208
|
|
3
|
+
metaio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
metaio/read.py,sha256=Vqd-xfj8Gtwy05FMTN6fd1YYJUPkTzK1srpbujHf18Y,5591
|
|
5
|
+
metaio/write.py,sha256=EALxGlvJzLWyJHfwaBlVhvIeOd3egLRNtUEXuF4JdQs,1474
|
|
6
|
+
tiny_metaio-0.1.1.dist-info/METADATA,sha256=PJrC4rFwfR4fKoh5sR-9YjYBOxUOzmrjfV-lNzuUq2Q,1809
|
|
7
|
+
tiny_metaio-0.1.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
8
|
+
tiny_metaio-0.1.1.dist-info/top_level.txt,sha256=mbuJhHfrVASzx9c0IbjP6llb_znhMzrsAYhM0KreOgw,7
|
|
9
|
+
tiny_metaio-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
metaio
|