mtmhdf 0.0.2__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.
mtmhdf/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from .reader import Reader
mtmhdf/_hdf4.py ADDED
@@ -0,0 +1,53 @@
1
+ from pyhdf.SD import SD, SDC, SDS
2
+ import numpy as np
3
+
4
+
5
+
6
+
7
+
8
+ class HDF4:
9
+ DATATYPES = {
10
+ 4: "char",
11
+ 3: "uchar",
12
+ 20: "int8",
13
+ 21: "uint8",
14
+ 22: "int16",
15
+ 23: "uint16",
16
+ 24: "int32",
17
+ 25: "uint32",
18
+ 5: "float32",
19
+ 6: "float64",
20
+ }
21
+ OPENMODES = {"r": SDC.READ, "w": SDC.WRITE}
22
+
23
+ @staticmethod
24
+ def open(file_path:str, mode='r', *args, **kwargs):
25
+ return SD(file_path, mode=HDF4.OPENMODES[mode], *args, **kwargs)
26
+
27
+ @staticmethod
28
+ def read(fp:SD, dataset_name:str):
29
+ return fp.select(dataset_name)
30
+
31
+ @staticmethod
32
+ def keys(fp:SD) -> list[str]:
33
+ return list(fp.datasets().keys())
34
+
35
+ @staticmethod
36
+ def infos(fp:SD) -> dict:
37
+ return {name: HDF4.dpinfo(HDF4.read(fp, name)) for name in HDF4.keys(fp)}
38
+
39
+ @staticmethod
40
+ def dpinfo(dp:SDS) -> dict:
41
+ attrs = dp.attributes()
42
+ _info_list = dp.info()
43
+ _info_dict = {
44
+ "dataset_name": _info_list[0],
45
+ "dataset_rank": _info_list[1],
46
+ "dataset_dims": _info_list[2],
47
+ "dataset_type": HDF4.DATATYPES[_info_list[3]]
48
+ }
49
+ _info_dict.update(attrs)
50
+ return _info_dict
51
+
52
+
53
+
mtmhdf/_hdf5.py ADDED
@@ -0,0 +1,52 @@
1
+ from netCDF4 import Dataset, Variable
2
+
3
+ class HDF5(Dataset):
4
+ @staticmethod
5
+ def open(file_path:str, mode='r', *args, **kwargs):
6
+ return Dataset(file_path, mode=HDF5.OPENMODES[mode], *args, **kwargs)
7
+
8
+ @staticmethod
9
+ def read(fp:Dataset, name:str) -> Variable:
10
+ return HDF5._jump(fp, name)
11
+
12
+ @staticmethod
13
+ def keys(fp:Dataset) -> list[str]:
14
+ return list(HDF5._walk(fp))
15
+
16
+ @staticmethod
17
+ def dpinfo(dp: Variable) -> dict:
18
+ info_dict = dp.__dict__
19
+ info_dict.update({
20
+ "dataset_name": dp.group().path + "/" + dp.name,
21
+ "dataset_dims": dp.shape,
22
+ "dataset_type": dp.datatype.name
23
+ })
24
+ return info_dict
25
+
26
+ @staticmethod
27
+ def infos(fp:Dataset) -> dict:
28
+ return {name: HDF5.dpinfo(HDF5.read(fp, name)) for name in HDF5.keys(fp)}
29
+
30
+ @staticmethod
31
+ def _walk(fp: Dataset, path=""):
32
+ if not len(path) or path[-1] != "/":
33
+ path += "/"
34
+ current_variables = list(fp.variables.keys())
35
+ for variable in current_variables:
36
+ yield path + variable
37
+ current_groups = list(fp.groups.keys())
38
+ for group in current_groups:
39
+ yield from HDF5._walk(fp.groups[group], path + group)
40
+
41
+ @staticmethod
42
+ def _jump(fp: Dataset, path="/"):
43
+ path_list = path.lstrip("/").split("/")
44
+ if not len(path_list):
45
+ return fp
46
+ subnode_fp = fp.__getitem__(path_list[0])
47
+ subnode_path = "/" + "/".join(path_list[1:])
48
+ if len(path_list) == 1:
49
+ return subnode_fp
50
+ else:
51
+ return HDF5._jump(subnode_fp, subnode_path)
52
+
mtmhdf/_utils.py ADDED
@@ -0,0 +1,15 @@
1
+ import numpy as np
2
+ from numbers import Number
3
+
4
+ def int2binarystring(num: int, length: int = 0) -> str:
5
+ return bin(num)[2:].zfill(length)
6
+
7
+ def bitoffset(data:np.ndarray | Number, bit_start_pos:int, bit_end_pos:int) -> np.ndarray | Number:
8
+ # 左开右闭区间,即从bit_start_pos开始,到bit_end_pos结束,不包含bit_end_pos
9
+ return (data >> bit_start_pos) % (2 ** (bit_end_pos - bit_start_pos))
10
+
11
+ def scale(data:np.ndarray | Number, scale_factor:Number=1, add_offset:Number=0) -> np.ndarray | Number:
12
+ return data * scale_factor + add_offset
13
+
14
+ def mask(data:np.ndarray | Number, fill_value:Number) -> np.ma.MaskedArray:
15
+ return np.ma.masked_values(data, fill_value, rtol=1e-8, atol=1e-9)
mtmhdf/reader.py ADDED
@@ -0,0 +1,214 @@
1
+ import os
2
+ import numpy as np
3
+ import numpy.ma as ma
4
+
5
+ from _hdf4 import HDF4
6
+ from _hdf5 import HDF5
7
+
8
+ from _utils import int2binarystring, bitoffset, scale, mask
9
+
10
+
11
+ class TemplateData:
12
+ def __init__(self) -> None:
13
+ raise NotImplementedError("Subclass must implement this method")
14
+
15
+ def infos(self) -> dict:
16
+ raise NotImplementedError("Subclass must implement this method")
17
+
18
+ def __getitem__(self, *item) -> np.ma.MaskedArray | np.ndarray:
19
+ raise NotImplementedError("Subclass must implement this method")
20
+
21
+
22
+ class TemplateReader:
23
+ LinkedDataClass = None
24
+
25
+ def __init__(self, data_file:str, *args, **kwargs):
26
+ raise NotImplementedError("Subclass must implement this method")
27
+
28
+ def read(self, name:str, *args, **kwargs):
29
+ raise NotImplementedError("Subclass must implement this method")
30
+
31
+ def __getitem__(self, name:str):
32
+ return self.rawread(name)
33
+
34
+ def readraw(self, name:str):
35
+ raise NotImplementedError("Subclass must implement this method")
36
+
37
+ def readbit(self, name:str, bit_start_pos: int, bit_end_pos: int, *args, **kwargs) -> np.ma.MaskedArray | np.ndarray:
38
+ # Bit fields within each byte are numbered from the left:
39
+ # 7, 6, 5, 4, 3, 2, 1, 0.
40
+ # The left-most bit (bit 7) is the most significant bit.
41
+ # The right-most bit (bit 0) is the least significant bit.
42
+ # 左开右闭区间,即从bit_start_pos开始,到bit_end_pos结束,不包含bit_end_pos
43
+ dp = self.readraw(name)
44
+ return bitoffset(np.array(dp[:]), bit_start_pos, bit_end_pos)
45
+
46
+ def keys(self) -> list[str]:
47
+ raise NotImplementedError("Subclass must implement this method")
48
+
49
+ def infos(self) -> dict:
50
+ raise NotImplementedError("Subclass must implement this method")
51
+
52
+ # ===================================================================================================
53
+
54
+
55
+ class HDF4Data(TemplateData):
56
+ def __init__(self, dp:HDF4, mode="manual", isScaleAndOffset: bool = True, isMasked: bool = True, manual_options=None, **kwargs) -> None:
57
+ self.dp = dp
58
+ self.mode = mode
59
+ self.isMasked = isMasked
60
+ self.isScaleAndOffset = isScaleAndOffset
61
+
62
+ default_manual_options = {
63
+ "attr_scale_factor": "scale_factor",
64
+ "attr_add_offset": "add_offset",
65
+ "attr_fill_value": "_FillValue",
66
+ "attr_decimal": 8,
67
+ }
68
+ if manual_options is None:
69
+ self.manual_options = default_manual_options
70
+ else:
71
+ self.manual_options = manual_options.copy()
72
+ self.manual_options.update(default_manual_options)
73
+
74
+ def infos(self):
75
+ return HDF4.dpinfo(self.dp)
76
+
77
+ def manual_transform(self, data: np.ndarray) -> np.ma.MaskedArray|np.ndarray:
78
+ infos: dict = self.infos()
79
+
80
+ attr_scale_factor = self.manual_options["attr_scale_factor"]
81
+ attr_add_offset = self.manual_options["attr_add_offset"]
82
+ attr_fill_value = self.manual_options["attr_fill_value"]
83
+ attr_decimal = self.manual_options["attr_decimal"]
84
+
85
+ scale_factor = round(infos.get(attr_scale_factor, 1), attr_decimal)
86
+ add_offset = round(infos.get(attr_add_offset, 0), attr_decimal)
87
+ fill_value = infos.get(attr_fill_value)
88
+
89
+ if self.isMasked:
90
+ data = mask(data, fill_value)
91
+ if self.isScaleAndOffset:
92
+ data = scale(data, scale_factor, add_offset)
93
+ return data
94
+
95
+ def __getitem__(self, *item) -> np.ma.MaskedArray | np.ndarray:
96
+ data = self.dp.__getitem__(*item)
97
+ if self.mode == "native":
98
+ return data
99
+ elif self.mode == "manual":
100
+ return self.manual_transform(np.array(data))
101
+ else:
102
+ raise ValueError(f"Invalid mode: {self.mode}")
103
+
104
+
105
+ class HDF4Reader(TemplateReader):
106
+ LinkedDataClass = HDF4Data
107
+
108
+ def __init__(self, data_file:str, *args, **kwargs):
109
+ self.fp = HDF4.open(data_file, *args, **kwargs)
110
+
111
+ def readraw(self, name:str):
112
+ return HDF4.read(self.fp, name)
113
+
114
+ def read(self, name:str, isScaleAndOffset: bool = True, isMasked: bool = True, **kwargs):
115
+ dp = HDF4.read(self.fp, name)
116
+ DataClass = HDF4Reader.LinkedDataClass
117
+ return DataClass(dp, mode="manual", isScaleAndOffset=isScaleAndOffset, isMasked=isMasked, **kwargs)
118
+
119
+ def keys(self) -> list[str]:
120
+ return HDF4.keys(self.fp)
121
+
122
+ def infos(self) -> dict:
123
+ return HDF4.infos(self.fp)
124
+
125
+ # ===================================================================================================
126
+
127
+
128
+ class HDF5Data(TemplateData):
129
+ def __init__(self, dp, mode="manual", isScaleAndOffset: bool = False, isMasked: bool = True, manual_options=None, **kwargs) -> None:
130
+ self.dp = dp
131
+ self.mode = mode
132
+ self.isScaleAndOffset = isScaleAndOffset
133
+ self.isMasked = isMasked
134
+
135
+ default_manual_options = {
136
+ "attr_scale_factor": "scale_factor",
137
+ "attr_add_offset": "add_offset",
138
+ "attr_fill_value": "_FillValue",
139
+ "attr_decimal": 8,
140
+ }
141
+ if manual_options is None:
142
+ self.manual_options = default_manual_options
143
+ else:
144
+ self.manual_options = manual_options.copy()
145
+ self.manual_options.update(default_manual_options)
146
+
147
+ if mode == "manual":
148
+ self.dp.set_auto_scale(False)
149
+ self.dp.set_auto_mask(False)
150
+ elif mode == "native":
151
+ self.dp.set_auto_scale(isScaleAndOffset)
152
+ self.dp.set_auto_mask(isMasked)
153
+ else:
154
+ raise ValueError(f"Invalid mode: {self.mode}")
155
+
156
+ def infos(self):
157
+ return HDF5.dpinfo(self.dp)
158
+
159
+ def manual_transform(self, data: np.ndarray) -> np.ma.MaskedArray|np.ndarray:
160
+ infos: dict = self.infos()
161
+
162
+ attr_scale_factor = self.manual_options["attr_scale_factor"]
163
+ attr_add_offset = self.manual_options["attr_add_offset"]
164
+ attr_fill_value = self.manual_options["attr_fill_value"]
165
+ attr_decimal = self.manual_options["attr_decimal"]
166
+ scale_factor = round(infos.get(attr_scale_factor, 1), attr_decimal)
167
+ add_offset = round(infos.get(attr_add_offset, 0), attr_decimal)
168
+ fill_value = infos.get(attr_fill_value)
169
+
170
+ if self.isMasked:
171
+ data = mask(data, fill_value)
172
+ if self.isScaleAndOffset:
173
+ data = scale(data, scale_factor, add_offset)
174
+ return data
175
+
176
+ def __getitem__(self, *item) -> np.ma.MaskedArray | np.ndarray:
177
+ data = self.dp.__getitem__(*item)
178
+ if self.mode == "native":
179
+ return data
180
+ elif self.mode == "manual":
181
+ return self.manual_transform(np.array(data))
182
+ else:
183
+ raise ValueError(f"Invalid mode: {self.mode}")
184
+
185
+ class HDF5Reader(TemplateReader):
186
+ LinkedDataClass = HDF5Data
187
+
188
+ def __init__(self, data_file:str, *args, **kwargs):
189
+ self.fp = HDF5.open(data_file, *args, **kwargs)
190
+
191
+ def readraw(self, name:str):
192
+ return HDF5.read(self.fp, name)
193
+
194
+ def read(self, name:str, isScaleAndOffset: bool = True, isMasked: bool = True, mode="native", **kwargs):
195
+ dp = HDF5.read(self.fp, name)
196
+ DataClass = HDF5Reader.LinkedDataClass
197
+ return DataClass(dp, mode=mode, isScaleAndOffset=isScaleAndOffset, isMasked=isMasked, **kwargs)
198
+
199
+ def keys(self) -> list[str]:
200
+ return HDF5.keys(self.fp)
201
+
202
+ def infos(self) -> dict:
203
+ return HDF5.infos(self.fp)
204
+
205
+
206
+ def Reader(data_file: str, *args, **kwargs) -> TemplateReader:
207
+ _, ext = os.path.splitext(data_file)
208
+ ext = ext.lower()
209
+ if ext in ['.nc', '.h5', '.he5', '.hdf5']:
210
+ return HDF5Reader(data_file, *args, **kwargs)
211
+ elif ext in ['.hdf', '.hdf4']:
212
+ return HDF4Reader(data_file, *args, **kwargs)
213
+ else:
214
+ raise ValueError(f"Unsupported file extension: {ext}")
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: mtmhdf
3
+ Version: 0.0.2
4
+ Summary: imutum's packages for HDF
5
+ License: MIT License
6
+
7
+ Copyright (c) 2023 imutum
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Classifier: Development Status :: 3 - Alpha
28
+ Classifier: Programming Language :: Python :: 3.11
29
+ Classifier: Intended Audience :: Developers
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Operating System :: MacOS
32
+ Classifier: Operating System :: POSIX :: Linux
33
+ Classifier: Operating System :: Microsoft :: Windows
34
+ Requires-Python: >=3.11
35
+ Description-Content-Type: text/markdown
36
+ License-File: LICENSE
37
+ Requires-Dist: pyhdf>=0.10.5
38
+ Requires-Dist: netcdf4>=1.6.5
39
+ Requires-Dist: numpy>=1.26.0
40
+ Dynamic: license-file
41
+
42
+ # imutum_python_hdf
43
+
@@ -0,0 +1,10 @@
1
+ mtmhdf/__init__.py,sha256=P4ZUG3Klkmyx1S3RbAxzL8VzWm0P7bQoRJBgbZ3M2eo,26
2
+ mtmhdf/_hdf4.py,sha256=RonrjU2D0v4eG0TcluY6Xb0nKYKY-vcxu6BWEB3tZx4,1235
3
+ mtmhdf/_hdf5.py,sha256=tux71Zey-JyAcvE1r6rTl165xlPVDxmUiWbS_brMQrk,1654
4
+ mtmhdf/_utils.py,sha256=8YeVA2NPu8MoW9pRAw9VTbSwdwvjktgt2M1IQc7VcdQ,711
5
+ mtmhdf/reader.py,sha256=Hi1uN7nh7lRgwuJUr5TesWL1YfDLCGsYDrmEt9PBm7s,7856
6
+ mtmhdf-0.0.2.dist-info/licenses/LICENSE,sha256=-lZJZIaH__2OkAxU2yFoe0cHHe48x8s2LIxCC3KFUqA,1063
7
+ mtmhdf-0.0.2.dist-info/METADATA,sha256=V-9N4BYwexAHmmgDFw1QNbmm-DTAfKgusAlRmVlZrzk,1873
8
+ mtmhdf-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ mtmhdf-0.0.2.dist-info/top_level.txt,sha256=eBIgfbDXWQslU5lXIbDr0x77IXgUqXKTu6OGzdipLMs,7
10
+ mtmhdf-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 imutum
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ mtmhdf