xradio 0.0.55__py3-none-any.whl → 0.0.58__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.
- xradio/__init__.py +2 -2
- xradio/_utils/_casacore/casacore_from_casatools.py +1001 -0
- xradio/_utils/_casacore/tables.py +6 -1
- xradio/_utils/coord_math.py +22 -23
- xradio/_utils/dict_helpers.py +76 -11
- xradio/_utils/schema.py +5 -2
- xradio/_utils/zarr/common.py +1 -73
- xradio/image/_util/_casacore/common.py +11 -3
- xradio/image/_util/_casacore/xds_from_casacore.py +59 -35
- xradio/image/_util/_casacore/xds_to_casacore.py +47 -16
- xradio/image/_util/_fits/xds_from_fits.py +172 -77
- xradio/image/_util/casacore.py +9 -4
- xradio/image/_util/common.py +4 -4
- xradio/image/_util/image_factory.py +8 -8
- xradio/image/image.py +45 -5
- xradio/measurement_set/__init__.py +19 -9
- xradio/measurement_set/_utils/__init__.py +1 -3
- xradio/measurement_set/_utils/_msv2/__init__.py +0 -0
- xradio/measurement_set/_utils/_msv2/_tables/read.py +35 -90
- xradio/measurement_set/_utils/_msv2/_tables/read_main_table.py +6 -686
- xradio/measurement_set/_utils/_msv2/_tables/table_query.py +13 -3
- xradio/measurement_set/_utils/_msv2/conversion.py +129 -145
- xradio/measurement_set/_utils/_msv2/create_antenna_xds.py +9 -16
- xradio/measurement_set/_utils/_msv2/create_field_and_source_xds.py +125 -221
- xradio/measurement_set/_utils/_msv2/msv2_to_msv4_meta.py +1 -2
- xradio/measurement_set/_utils/_msv2/msv4_info_dicts.py +13 -8
- xradio/measurement_set/_utils/_msv2/msv4_sub_xdss.py +27 -72
- xradio/measurement_set/_utils/_msv2/partition_queries.py +5 -262
- xradio/measurement_set/_utils/_msv2/subtables.py +0 -107
- xradio/measurement_set/_utils/_utils/interpolate.py +60 -0
- xradio/measurement_set/_utils/_zarr/encoding.py +2 -7
- xradio/measurement_set/convert_msv2_to_processing_set.py +0 -2
- xradio/measurement_set/load_processing_set.py +2 -2
- xradio/measurement_set/measurement_set_xdt.py +14 -14
- xradio/measurement_set/open_processing_set.py +1 -3
- xradio/measurement_set/processing_set_xdt.py +41 -835
- xradio/measurement_set/schema.py +96 -123
- xradio/schema/check.py +91 -97
- xradio/schema/dataclass.py +159 -22
- xradio/schema/export.py +99 -0
- xradio/schema/metamodel.py +51 -16
- xradio/schema/typing.py +5 -5
- {xradio-0.0.55.dist-info → xradio-0.0.58.dist-info}/METADATA +43 -11
- xradio-0.0.58.dist-info/RECORD +65 -0
- {xradio-0.0.55.dist-info → xradio-0.0.58.dist-info}/WHEEL +1 -1
- xradio/image/_util/fits.py +0 -13
- xradio/measurement_set/_utils/_msv2/_tables/load.py +0 -63
- xradio/measurement_set/_utils/_msv2/_tables/load_main_table.py +0 -487
- xradio/measurement_set/_utils/_msv2/_tables/read_subtables.py +0 -395
- xradio/measurement_set/_utils/_msv2/_tables/write.py +0 -320
- xradio/measurement_set/_utils/_msv2/_tables/write_exp_api.py +0 -385
- xradio/measurement_set/_utils/_msv2/chunks.py +0 -115
- xradio/measurement_set/_utils/_msv2/descr.py +0 -165
- xradio/measurement_set/_utils/_msv2/msv2_msv3.py +0 -7
- xradio/measurement_set/_utils/_msv2/partitions.py +0 -392
- xradio/measurement_set/_utils/_utils/cds.py +0 -40
- xradio/measurement_set/_utils/_utils/xds_helper.py +0 -404
- xradio/measurement_set/_utils/_zarr/read.py +0 -263
- xradio/measurement_set/_utils/_zarr/write.py +0 -329
- xradio/measurement_set/_utils/msv2.py +0 -106
- xradio/measurement_set/_utils/zarr.py +0 -133
- xradio-0.0.55.dist-info/RECORD +0 -77
- {xradio-0.0.55.dist-info → xradio-0.0.58.dist-info}/licenses/LICENSE.txt +0 -0
- {xradio-0.0.55.dist-info → xradio-0.0.58.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1001 @@
|
|
|
1
|
+
"""This module serves as an API bridge from `casatools` to `python-casacore`.
|
|
2
|
+
|
|
3
|
+
Features:
|
|
4
|
+
- Returns C-order numpy arrays.
|
|
5
|
+
- Workaround fpr the tablerow/tablecolumn-related API differences essential for the `xradio` use case.
|
|
6
|
+
|
|
7
|
+
Note: not fully implemented; not intended to be a full API adapter layer.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
import inspect
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import shutil
|
|
15
|
+
from functools import wraps
|
|
16
|
+
from typing import Any, Dict, List, Sequence, Union
|
|
17
|
+
|
|
18
|
+
# Configure casaconfig settings prior to casatools import
|
|
19
|
+
# this ensures optimal initialization and resource allocation for casatools
|
|
20
|
+
# Also see : https://casadocs.readthedocs.io/en/stable/api/casaconfig.html
|
|
21
|
+
try:
|
|
22
|
+
import casaconfig.config
|
|
23
|
+
except ModuleNotFoundError as exc:
|
|
24
|
+
raise ModuleNotFoundError(
|
|
25
|
+
f"casaconfig.config cannot be found, probably because the package "
|
|
26
|
+
"casatools is not available. If you are here that is probably because "
|
|
27
|
+
"python-casacore is not available either. MSv2 related functionality "
|
|
28
|
+
"requires either python-casacore or casatools. Import failure details: "
|
|
29
|
+
f"{exc}"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
import numpy as np
|
|
33
|
+
import toolviper.utils.logger as logger
|
|
34
|
+
|
|
35
|
+
casaconfig.config.data_auto_update = False
|
|
36
|
+
casaconfig.config.measures_auto_update = False
|
|
37
|
+
casaconfig.config.nologger = False
|
|
38
|
+
casaconfig.config.nogui = False
|
|
39
|
+
casaconfig.config.agg = True
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_logger_config():
|
|
43
|
+
"""Retrieve logger configuration details.
|
|
44
|
+
|
|
45
|
+
This function checks if the logger has a `FileHandler` attached and retrieves
|
|
46
|
+
the log file name if available. It also checks if a `StreamHandler` is attached
|
|
47
|
+
to the logger.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
tuple: A tuple containing:
|
|
51
|
+
- logfile (str or None): The log file name if a `FileHandler` is found, otherwise `None`.
|
|
52
|
+
- has_stream_handler (bool): `True` if a `StreamHandler` is attached, otherwise `False`.
|
|
53
|
+
"""
|
|
54
|
+
logfile = None
|
|
55
|
+
logger_instance = logging.getLogger()
|
|
56
|
+
|
|
57
|
+
# Check for FileHandler and extract log filename
|
|
58
|
+
for handler in logger_instance.handlers:
|
|
59
|
+
if isinstance(handler, logging.FileHandler):
|
|
60
|
+
logfile = handler.baseFilename
|
|
61
|
+
break
|
|
62
|
+
|
|
63
|
+
# Check if a StreamHandler is attached
|
|
64
|
+
has_stream_handler = any(
|
|
65
|
+
isinstance(handler, logging.StreamHandler)
|
|
66
|
+
for handler in logger_instance.handlers
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return logfile, has_stream_handler
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Poropagate existing logger configuration to casatools
|
|
73
|
+
# this ensures consistent logging behavior across both application and casatools components
|
|
74
|
+
|
|
75
|
+
logfile, log2term = get_logger_config()
|
|
76
|
+
casaconfig.config.log2term = log2term
|
|
77
|
+
if logfile:
|
|
78
|
+
casaconfig.config.logfile = logfile
|
|
79
|
+
else:
|
|
80
|
+
casaconfig.config.logfile = "/dev/null"
|
|
81
|
+
casaconfig.config.nologfile = True
|
|
82
|
+
|
|
83
|
+
import casatools # noqa: E402 (because of previous config initialization)
|
|
84
|
+
|
|
85
|
+
casatools.logger.setglobal(True)
|
|
86
|
+
casatools.logger.ompSetNumThreads(1)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _wrap_table(swig_object: Any) -> "table":
|
|
90
|
+
"""Wraps a SWIG table object.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
swig_object : Any
|
|
95
|
+
The SWIG object to wrap.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
table
|
|
100
|
+
The wrapped table object.
|
|
101
|
+
"""
|
|
102
|
+
return table(swig_object=swig_object)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def method_wrapper(method: Any) -> Any:
|
|
106
|
+
"""Wraps a method to recursively transpose NumPy array results.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
method : callable
|
|
111
|
+
The method to wrap.
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
callable
|
|
116
|
+
The wrapped method.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
@wraps(method)
|
|
120
|
+
def wrapped(*args, **kwargs):
|
|
121
|
+
ret = method(*args, **kwargs)
|
|
122
|
+
return recursive_transpose(ret)
|
|
123
|
+
|
|
124
|
+
return wrapped
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def recursive_transpose(val: Any) -> Any:
|
|
128
|
+
"""Recursively transposes all NumPy arrays within the given object.
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
val : Any
|
|
133
|
+
The object to process. It can be a dictionary, list, NumPy array, or other object.
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
Any
|
|
138
|
+
The modified object with all NumPy arrays transposed.
|
|
139
|
+
"""
|
|
140
|
+
if isinstance(val, np.ndarray) and val.flags.f_contiguous:
|
|
141
|
+
return val.T
|
|
142
|
+
elif isinstance(val, list):
|
|
143
|
+
return [recursive_transpose(item) for item in val]
|
|
144
|
+
elif isinstance(val, dict):
|
|
145
|
+
return {key: recursive_transpose(value) for key, value in val.items()}
|
|
146
|
+
else:
|
|
147
|
+
return val
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def wrap_class_methods(cls: type) -> type:
|
|
151
|
+
"""Class decorator to wrap all methods of a class, including inherited ones.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
cls : type
|
|
156
|
+
The class to wrap.
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
type
|
|
161
|
+
The class with its methods wrapped.
|
|
162
|
+
"""
|
|
163
|
+
for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
|
|
164
|
+
if callable(method):
|
|
165
|
+
setattr(cls, name, method_wrapper(method))
|
|
166
|
+
return cls
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@wrap_class_methods
|
|
170
|
+
class table(casatools.table):
|
|
171
|
+
"""A wrapper for the casatools table object.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
tablename : str, optional
|
|
176
|
+
The name of the table.
|
|
177
|
+
tabledesc : bool, optional
|
|
178
|
+
Table description.
|
|
179
|
+
nrow : int, optional
|
|
180
|
+
Number of rows.
|
|
181
|
+
readonly : bool, optional
|
|
182
|
+
Whether the table is read-only.
|
|
183
|
+
lockoptions : dict, optional
|
|
184
|
+
Locking options.
|
|
185
|
+
ack : bool, optional
|
|
186
|
+
Acknowledgment flag.
|
|
187
|
+
dminfo : dict, optional
|
|
188
|
+
Data manager information.
|
|
189
|
+
endian : str, optional
|
|
190
|
+
Endian type.
|
|
191
|
+
memorytable : bool, optional
|
|
192
|
+
Whether the table is in memory.
|
|
193
|
+
concatsubtables : list, optional
|
|
194
|
+
Concatenated subtables.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
def __init__(
|
|
198
|
+
self,
|
|
199
|
+
tablename: str = "",
|
|
200
|
+
tabledesc: bool = False,
|
|
201
|
+
nrow: int = 0,
|
|
202
|
+
readonly: bool = True,
|
|
203
|
+
lockoptions: Dict = {},
|
|
204
|
+
ack: bool = True,
|
|
205
|
+
dminfo: Dict = {},
|
|
206
|
+
endian: str = "aipsrc",
|
|
207
|
+
memorytable: bool = False,
|
|
208
|
+
concatsubtables: List = [],
|
|
209
|
+
**kwargs,
|
|
210
|
+
):
|
|
211
|
+
_tablename = tablename.replace("::", "/")
|
|
212
|
+
super().__init__(
|
|
213
|
+
tablename=_tablename, lockoptions=lockoptions, nomodify=readonly, **kwargs
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def __enter__(self):
|
|
217
|
+
"""Function to enter a with block."""
|
|
218
|
+
return self
|
|
219
|
+
|
|
220
|
+
def __exit__(self, type, value, traceback):
|
|
221
|
+
"""Function to exit a with block which closes the table object."""
|
|
222
|
+
self.close()
|
|
223
|
+
|
|
224
|
+
def row(self, columnnames: List[str] = [], exclude: bool = False) -> "tablerow":
|
|
225
|
+
"""Access rows in the table.
|
|
226
|
+
|
|
227
|
+
Parameters
|
|
228
|
+
----------
|
|
229
|
+
columnnames : list of str, optional
|
|
230
|
+
Column names to include or exclude.
|
|
231
|
+
exclude : bool, optional
|
|
232
|
+
Whether to exclude the specified columns.
|
|
233
|
+
|
|
234
|
+
Returns
|
|
235
|
+
-------
|
|
236
|
+
tablerow
|
|
237
|
+
A tablerow object for accessing rows.
|
|
238
|
+
"""
|
|
239
|
+
return tablerow(self, columnnames=columnnames, exclude=exclude)
|
|
240
|
+
|
|
241
|
+
def col(self, columnname: str) -> "tablecolumn":
|
|
242
|
+
"""Access a specific column in the table.
|
|
243
|
+
|
|
244
|
+
Parameters
|
|
245
|
+
----------
|
|
246
|
+
columnname : str
|
|
247
|
+
The name of the column to access.
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
tablecolumn
|
|
252
|
+
A tablecolumn object for accessing column data.
|
|
253
|
+
"""
|
|
254
|
+
return tablecolumn(self, columnname)
|
|
255
|
+
|
|
256
|
+
def taql(self, taqlcommand="TaQL expression"):
|
|
257
|
+
"""Expose TaQL (Table Query Language) to the user.
|
|
258
|
+
|
|
259
|
+
This method allows the execution of a TaQL expression on the table.
|
|
260
|
+
It substitutes `$mtable` and `$gtable` in the provided `taqlcommand`
|
|
261
|
+
with the current table name. A temporary copy of the table is created
|
|
262
|
+
if it is not currently opened.
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
taqlcommand : str, optional
|
|
267
|
+
The TaQL expression to execute. Default is `'TaQL expression'`.
|
|
268
|
+
|
|
269
|
+
Returns
|
|
270
|
+
-------
|
|
271
|
+
tb_query_to : object
|
|
272
|
+
The result of the TaQL query as a wrapped table object.
|
|
273
|
+
|
|
274
|
+
Notes
|
|
275
|
+
-----
|
|
276
|
+
For more details on TaQL, refer to:
|
|
277
|
+
https://casacore.github.io/casacore-notes/199.html
|
|
278
|
+
|
|
279
|
+
Examples
|
|
280
|
+
--------
|
|
281
|
+
>>> result_table = obj.taql('SELECT * FROM $mtable WHERE col1 > 5')
|
|
282
|
+
>>> print(result_table.name())
|
|
283
|
+
"""
|
|
284
|
+
is_open = self.isopened(self.name())
|
|
285
|
+
if not is_open:
|
|
286
|
+
tablename = self.name() + "_copy"
|
|
287
|
+
tb_query_from = self.copy(tablename, deep=False, valuecopy=False)
|
|
288
|
+
else:
|
|
289
|
+
tablename = self.name()
|
|
290
|
+
tb_query_from = self
|
|
291
|
+
tb_query = taqlcommand.replace("$mtable", tablename).replace(
|
|
292
|
+
"$gtable", tablename
|
|
293
|
+
)
|
|
294
|
+
logger.debug(f"tb_query_from: {tb_query_from.name()}")
|
|
295
|
+
logger.debug(f"tb_query_cmd: {tb_query}")
|
|
296
|
+
tb_query_to = _wrap_table(swig_object=tb_query_from._swigobj.taql(tb_query))
|
|
297
|
+
if not is_open:
|
|
298
|
+
tb_query_from.close()
|
|
299
|
+
shutil.rmtree(tablename)
|
|
300
|
+
logger.debug(f"tb_query_to: {tb_query_to.name()}")
|
|
301
|
+
return tb_query_to
|
|
302
|
+
|
|
303
|
+
def getcolshapestring(self, *args, **kwargs):
|
|
304
|
+
"""Get the shape of table columns as string representations.
|
|
305
|
+
|
|
306
|
+
This method retrieves the shapes of table columns and formats them as
|
|
307
|
+
reversed string representations of the shapes. It is useful for viewing
|
|
308
|
+
column dimensions in a human-readable format.
|
|
309
|
+
|
|
310
|
+
Parameters
|
|
311
|
+
----------
|
|
312
|
+
*args : tuple
|
|
313
|
+
Positional arguments to pass to the superclass method.
|
|
314
|
+
**kwargs : dict
|
|
315
|
+
Keyword arguments to pass to the superclass method.
|
|
316
|
+
|
|
317
|
+
Returns
|
|
318
|
+
-------
|
|
319
|
+
list of str
|
|
320
|
+
A list of reversed shapes as strings.
|
|
321
|
+
|
|
322
|
+
Examples
|
|
323
|
+
--------
|
|
324
|
+
>>> shapes = obj.getcolshapestring()
|
|
325
|
+
>>> print(shapes)
|
|
326
|
+
['[10, 5]', '[20, 15]']
|
|
327
|
+
"""
|
|
328
|
+
ret = super().getcolshapestring(*args, **kwargs)
|
|
329
|
+
return [str(list(reversed(ast.literal_eval(shape)))) for shape in ret]
|
|
330
|
+
|
|
331
|
+
def getcellslice(self, columnname, rownr, blc, trc, incr=1):
|
|
332
|
+
"""Retrieve a sliced portion of a cell from a specified column.
|
|
333
|
+
|
|
334
|
+
This method extracts a subarray from a cell within a table column,
|
|
335
|
+
given the bottom-left corner (BLC) and top-right corner (TRC) indices.
|
|
336
|
+
It also supports an optional increment (`incr`) to control step size.
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
columnname : str
|
|
341
|
+
The name of the column from which to extract data.
|
|
342
|
+
rownr : int
|
|
343
|
+
The row number(s) from which to extract data. If a sequence is provided,
|
|
344
|
+
it is reversed before processing.
|
|
345
|
+
blc : Sequence[int]
|
|
346
|
+
The bottom-left corner indices of the slice.
|
|
347
|
+
trc : Sequence[int]
|
|
348
|
+
The top-right corner indices of the slice.
|
|
349
|
+
incr : int or Sequence[int], optional
|
|
350
|
+
Step size for slicing. If a sequence is provided, it is reversed.
|
|
351
|
+
If a single integer is given, it is expanded to match `blc` dimensions.
|
|
352
|
+
Defaults to 1.
|
|
353
|
+
|
|
354
|
+
Returns
|
|
355
|
+
-------
|
|
356
|
+
Any
|
|
357
|
+
The extracted slice from the specified column and row(s).
|
|
358
|
+
|
|
359
|
+
Notes
|
|
360
|
+
-----
|
|
361
|
+
- If `rownr` is a sequence, it is reversed before processing.
|
|
362
|
+
- The `blc`, `trc`, and `incr` parameters are converted to lists of integers.
|
|
363
|
+
- Calls the superclass method `getcellslice` for actual data retrieval.
|
|
364
|
+
"""
|
|
365
|
+
if isinstance(blc, Sequence):
|
|
366
|
+
blc = list(map(int, blc[::-1]))
|
|
367
|
+
if isinstance(trc, Sequence):
|
|
368
|
+
trc = list(map(int, trc[::-1]))
|
|
369
|
+
if isinstance(incr, Sequence):
|
|
370
|
+
incr = incr[::-1]
|
|
371
|
+
else:
|
|
372
|
+
incr = [incr] * len(blc)
|
|
373
|
+
datatype = self.coldatatype(columnname)
|
|
374
|
+
|
|
375
|
+
ret = super().getcellslice(
|
|
376
|
+
columnname=columnname, rownr=rownr, blc=blc, trc=trc, incr=incr
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
if datatype == "float":
|
|
380
|
+
return ret.astype(np.float32)
|
|
381
|
+
else:
|
|
382
|
+
return ret
|
|
383
|
+
|
|
384
|
+
def putcellslice(self, columnname, rownr, value, blc, trc, incr=1):
|
|
385
|
+
"""Retrieve a sliced portion of a cell from a specified column.
|
|
386
|
+
|
|
387
|
+
This method extracts a subarray from a cell within a table column,
|
|
388
|
+
given the bottom-left corner (BLC) and top-right corner (TRC) indices.
|
|
389
|
+
It also supports an optional increment (`incr`) to control step size.
|
|
390
|
+
|
|
391
|
+
Parameters
|
|
392
|
+
----------
|
|
393
|
+
columnname : str
|
|
394
|
+
The name of the column from which to extract data.
|
|
395
|
+
rownr : int or Sequence[int]
|
|
396
|
+
The row number(s) from which to extract data. If a sequence is provided,
|
|
397
|
+
it is reversed before processing.
|
|
398
|
+
blc : Sequence[int]
|
|
399
|
+
The bottom-left corner indices of the slice.
|
|
400
|
+
trc : Sequence[int]
|
|
401
|
+
The top-right corner indices of the slice.
|
|
402
|
+
incr : int or Sequence[int], optional
|
|
403
|
+
Step size for slicing. If a sequence is provided, it is reversed.
|
|
404
|
+
If a single integer is given, it is expanded to match `blc` dimensions.
|
|
405
|
+
Defaults to 1.
|
|
406
|
+
|
|
407
|
+
Returns
|
|
408
|
+
-------
|
|
409
|
+
Any
|
|
410
|
+
The extracted slice from the specified column and row(s).
|
|
411
|
+
|
|
412
|
+
Notes
|
|
413
|
+
-----
|
|
414
|
+
- If `rownr` is a sequence, it is reversed before processing.
|
|
415
|
+
- The `blc`, `trc`, and `incr` parameters are converted to lists of integers.
|
|
416
|
+
- Calls the superclass method `getcellslice` for actual data retrieval.
|
|
417
|
+
"""
|
|
418
|
+
if isinstance(rownr, Sequence):
|
|
419
|
+
rownr = rownr[::-1]
|
|
420
|
+
else:
|
|
421
|
+
rownr = [rownr] * len(blc)
|
|
422
|
+
rownr = 0
|
|
423
|
+
if isinstance(blc, Sequence):
|
|
424
|
+
blc = list(map(int, blc[::-1]))
|
|
425
|
+
if isinstance(trc, Sequence):
|
|
426
|
+
trc = list(map(int, trc[::-1]))
|
|
427
|
+
if isinstance(incr, Sequence):
|
|
428
|
+
incr = incr[::-1]
|
|
429
|
+
else:
|
|
430
|
+
incr = [incr] * len(blc)
|
|
431
|
+
|
|
432
|
+
super().putcellslice(
|
|
433
|
+
columnname=columnname,
|
|
434
|
+
rownr=rownr,
|
|
435
|
+
value=value.T,
|
|
436
|
+
blc=blc,
|
|
437
|
+
trc=trc,
|
|
438
|
+
incr=incr,
|
|
439
|
+
)
|
|
440
|
+
return
|
|
441
|
+
|
|
442
|
+
def putkeyword(
|
|
443
|
+
self, keyword: str, value: str | int | float | bool, makesubrecord: bool = False
|
|
444
|
+
) -> None:
|
|
445
|
+
"""Insert a keyword and its associated value into the record.
|
|
446
|
+
|
|
447
|
+
This method wraps the `casatools.tables.table`'s `putkeyword` method and handles
|
|
448
|
+
the insertion of a keyword and its corresponding value into the record, with a
|
|
449
|
+
specific conversion for NumPy scalar types.
|
|
450
|
+
|
|
451
|
+
NumPy scalar types in `value` are automatically converted to native Python
|
|
452
|
+
types before writing. This conversion is necessary because `casatools`
|
|
453
|
+
appears to exclude NumPy scalars in the keyword value (e.g., within
|
|
454
|
+
a nested directory) during serialization.
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
Parameters
|
|
458
|
+
----------
|
|
459
|
+
keyword : str
|
|
460
|
+
The name of the keyword to insert.
|
|
461
|
+
value
|
|
462
|
+
The value associated with the keyword. NumPy scalars are automatically converted to native types.
|
|
463
|
+
makesubrecord : bool, optional
|
|
464
|
+
If True, creates a new subrecord for the keyword (default is False).
|
|
465
|
+
|
|
466
|
+
Returns
|
|
467
|
+
-------
|
|
468
|
+
None
|
|
469
|
+
"""
|
|
470
|
+
super().putkeyword(
|
|
471
|
+
keyword,
|
|
472
|
+
_convert_numpy_scalars_to_native(value),
|
|
473
|
+
makesubrecord=makesubrecord,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _convert_numpy_scalars_to_native(value: Any) -> Any:
|
|
478
|
+
"""Recursively convert NumPy scalar types to their equivalent Python native types.
|
|
479
|
+
|
|
480
|
+
This function traverses nested data structures (e.g., dictionaries, lists, tuples) and replaces any NumPy scalar
|
|
481
|
+
types (e.g., `np.float64`, `np.int32`) with their native Python equivalents (e.g., `float`, `int`). This is
|
|
482
|
+
particularly useful before serializing data structures to formats like JSON, which do not natively support NumPy
|
|
483
|
+
scalar types.
|
|
484
|
+
|
|
485
|
+
Parameters
|
|
486
|
+
----------
|
|
487
|
+
value
|
|
488
|
+
A scalar or nested structure (dictionary, list, or tuple) potentially containing NumPy scalar types.
|
|
489
|
+
|
|
490
|
+
Returns
|
|
491
|
+
-------
|
|
492
|
+
A new structure with NumPy scalars converted to native types. Original container types are preserved.
|
|
493
|
+
"""
|
|
494
|
+
if isinstance(value, dict):
|
|
495
|
+
return {k: _convert_numpy_scalars_to_native(v) for k, v in value.items()}
|
|
496
|
+
|
|
497
|
+
elif isinstance(value, (list, tuple)):
|
|
498
|
+
# Preserve list or tuple type
|
|
499
|
+
return type(value)(_convert_numpy_scalars_to_native(item) for item in value)
|
|
500
|
+
|
|
501
|
+
elif isinstance(value, np.generic):
|
|
502
|
+
# Convert NumPy scalar to native Python type
|
|
503
|
+
return value.item()
|
|
504
|
+
|
|
505
|
+
return value
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
@wrap_class_methods
|
|
509
|
+
class image(casatools.image):
|
|
510
|
+
"""A Wrapper class around `casatools.image` that provides python-casacore-like methods."""
|
|
511
|
+
|
|
512
|
+
def __init__(
|
|
513
|
+
self,
|
|
514
|
+
imagename,
|
|
515
|
+
axis=0,
|
|
516
|
+
maskname="mask0",
|
|
517
|
+
images=(),
|
|
518
|
+
values=None,
|
|
519
|
+
coordsys=None,
|
|
520
|
+
overwrite=True,
|
|
521
|
+
ashdf5=False,
|
|
522
|
+
mask=(),
|
|
523
|
+
shape=None,
|
|
524
|
+
tileshape=(),
|
|
525
|
+
):
|
|
526
|
+
super().__init__()
|
|
527
|
+
self._imagename = imagename
|
|
528
|
+
self._maskname = maskname
|
|
529
|
+
if shape is None:
|
|
530
|
+
# self.open(*arg, **kwargs)
|
|
531
|
+
# Add a temporary filter to the CASA instance global logger log sink filter
|
|
532
|
+
# to suppress 'SEVERE' messages when probing images/
|
|
533
|
+
casatools.logger.filterMsg("Exception Reported: Exception")
|
|
534
|
+
self.open(imagename)
|
|
535
|
+
casatools.logger.clearFilterMsgList()
|
|
536
|
+
else:
|
|
537
|
+
if values is None:
|
|
538
|
+
self.fromshape(imagename, shape=list(shape[::-1]), overwrite=overwrite)
|
|
539
|
+
else:
|
|
540
|
+
self.fromarray(
|
|
541
|
+
imagename, pixels=np.full(shape, values).T, overwrite=overwrite
|
|
542
|
+
)
|
|
543
|
+
if maskname:
|
|
544
|
+
self.calcmask("T", name=maskname)
|
|
545
|
+
self.maskhandler("set", maskname)
|
|
546
|
+
|
|
547
|
+
def toworld(self, pixel):
|
|
548
|
+
world = super().toworld(pixel[::-1])
|
|
549
|
+
return world["numeric"][::-1]
|
|
550
|
+
|
|
551
|
+
def tofits(
|
|
552
|
+
self,
|
|
553
|
+
filename,
|
|
554
|
+
overwrite=True,
|
|
555
|
+
velocity=True,
|
|
556
|
+
optical=True,
|
|
557
|
+
bitpix=-32,
|
|
558
|
+
minpix=1,
|
|
559
|
+
maxpix=-1,
|
|
560
|
+
):
|
|
561
|
+
super().tofits(
|
|
562
|
+
filename,
|
|
563
|
+
overwrite=overwrite,
|
|
564
|
+
velocity=velocity,
|
|
565
|
+
optical=optical,
|
|
566
|
+
bitpix=bitpix,
|
|
567
|
+
minpix=minpix,
|
|
568
|
+
maxpix=maxpix,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
def getdata(self, blc=None, trc=None, inc=None):
|
|
572
|
+
"""Retrieve image data as a chunk.
|
|
573
|
+
|
|
574
|
+
Parameters
|
|
575
|
+
----------
|
|
576
|
+
blc : list of int, optional
|
|
577
|
+
Bottom-left corner of the region to extract. Defaults to `[-1]` (entire image).
|
|
578
|
+
trc : list of int, optional
|
|
579
|
+
Top-right corner of the region to extract. Defaults to `[-1]` (entire image).
|
|
580
|
+
inc : list of int, optional
|
|
581
|
+
Step size for slicing. Defaults to `[1]`.
|
|
582
|
+
|
|
583
|
+
Returns
|
|
584
|
+
-------
|
|
585
|
+
numpy.ndarray
|
|
586
|
+
The extracted data chunk.
|
|
587
|
+
"""
|
|
588
|
+
if blc is None:
|
|
589
|
+
blc = [-1]
|
|
590
|
+
if trc is None:
|
|
591
|
+
trc = [-1]
|
|
592
|
+
if inc is None:
|
|
593
|
+
inc = [1]
|
|
594
|
+
|
|
595
|
+
if self.datatype() == "float":
|
|
596
|
+
return super().getchunk(blc, trc, inc).astype(np.float32)
|
|
597
|
+
else:
|
|
598
|
+
return super().getchunk(blc, trc, inc)
|
|
599
|
+
|
|
600
|
+
def getmask(self, blc=None, trc=None, inc=None):
|
|
601
|
+
"""Retrieve image data as a chunk.
|
|
602
|
+
|
|
603
|
+
Parameters
|
|
604
|
+
----------
|
|
605
|
+
blc : list of int, optional
|
|
606
|
+
Bottom-left corner of the region to extract. Defaults to `[-1]` (entire image).
|
|
607
|
+
trc : list of int, optional
|
|
608
|
+
Top-right corner of the region to extract. Defaults to `[-1]` (entire image).
|
|
609
|
+
inc : list of int, optional
|
|
610
|
+
Step size for slicing. Defaults to `[1]`.
|
|
611
|
+
|
|
612
|
+
Returns
|
|
613
|
+
-------
|
|
614
|
+
numpy.ndarray
|
|
615
|
+
The extracted data chunk.
|
|
616
|
+
"""
|
|
617
|
+
if blc is None:
|
|
618
|
+
blc = [-1]
|
|
619
|
+
if trc is None:
|
|
620
|
+
trc = [-1]
|
|
621
|
+
if inc is None:
|
|
622
|
+
inc = [1]
|
|
623
|
+
# note the fliped sign:
|
|
624
|
+
# https://casacore.github.io/python-casacore/casacore_images.html#casacore.images.image.getmask
|
|
625
|
+
return ~super().getchunk(blc, trc, inc, getmask=True)
|
|
626
|
+
|
|
627
|
+
def put(self, masked_array):
|
|
628
|
+
"""Put in data/mask into iatools.
|
|
629
|
+
|
|
630
|
+
Note: for casa mask table, the mask value defination is flipped:
|
|
631
|
+
True (not masked) or False (masked) values
|
|
632
|
+
"""
|
|
633
|
+
self.putregion(masked_array.data.T, ~masked_array.mask.T)
|
|
634
|
+
|
|
635
|
+
def __del__(self):
|
|
636
|
+
"""Ensure proper resource cleanup.
|
|
637
|
+
|
|
638
|
+
This method is automatically called when the object is deleted.
|
|
639
|
+
It ensures that any open resources are properly closed by calling
|
|
640
|
+
`unlock()` and `close()`.
|
|
641
|
+
"""
|
|
642
|
+
|
|
643
|
+
# flushes any outstabding I/O to disk and close the tool instance.
|
|
644
|
+
# the explicut unlock() call is important for multiple-process parallel read downstream
|
|
645
|
+
# as the dask worker process might sometimes consider a freshly written from a different process
|
|
646
|
+
# not valid disk images (even the image dir has been formed).
|
|
647
|
+
|
|
648
|
+
# super().unlock() # taken care from xradio.image._util._casacore.common::_create_new_image
|
|
649
|
+
super().close()
|
|
650
|
+
|
|
651
|
+
def shape(self):
|
|
652
|
+
"""Get the shape of the image.
|
|
653
|
+
|
|
654
|
+
Returns
|
|
655
|
+
-------
|
|
656
|
+
list of int
|
|
657
|
+
The shape of the image, with axes reversed for consistency.
|
|
658
|
+
"""
|
|
659
|
+
return list(map(int, super().shape()[::-1]))
|
|
660
|
+
|
|
661
|
+
def coordinates(self):
|
|
662
|
+
"""Get the coordinate system of the image.
|
|
663
|
+
|
|
664
|
+
Returns
|
|
665
|
+
-------
|
|
666
|
+
casatools.coordinatesystem
|
|
667
|
+
The coordinate system associated with the image.
|
|
668
|
+
"""
|
|
669
|
+
return coordinatesystem(self)
|
|
670
|
+
|
|
671
|
+
def unit(self):
|
|
672
|
+
"""Get the brightness unit of the image.
|
|
673
|
+
|
|
674
|
+
Returns
|
|
675
|
+
-------
|
|
676
|
+
str
|
|
677
|
+
The brightness unit of the image.
|
|
678
|
+
"""
|
|
679
|
+
return self.brightnessunit()
|
|
680
|
+
|
|
681
|
+
def info(self):
|
|
682
|
+
"""Retrieve image metadata including coordinates, misc info, and beam information.
|
|
683
|
+
|
|
684
|
+
Returns
|
|
685
|
+
-------
|
|
686
|
+
dict
|
|
687
|
+
Dictionary containing:
|
|
688
|
+
- 'imageinfo': Flattened image summary.
|
|
689
|
+
- 'coordinates': Coordinate system as a dictionary.
|
|
690
|
+
- 'miscinfo': Miscellaneous metadata.
|
|
691
|
+
"""
|
|
692
|
+
# imageinfo = self.summary(list=False)
|
|
693
|
+
# imageinfo = self._flatten_multibeam(imageinfo)
|
|
694
|
+
|
|
695
|
+
return {
|
|
696
|
+
"imageinfo": self.imageinfo(),
|
|
697
|
+
"coordinates": self.coordsys().torecord(),
|
|
698
|
+
"miscinfo": self.miscinfo(),
|
|
699
|
+
"unit": self.brightnessunit(),
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
def imageinfo(self) -> dict:
|
|
703
|
+
"""Retrieve metadata from the image table.
|
|
704
|
+
|
|
705
|
+
This method accesses the image table associated with the image name
|
|
706
|
+
and attempts to retrieve information stored under the 'imageinfo'
|
|
707
|
+
keyword. If the 'imageinfo' keyword is not found in the table,
|
|
708
|
+
a default dictionary containing basic information is returned.
|
|
709
|
+
|
|
710
|
+
Returns
|
|
711
|
+
-------
|
|
712
|
+
dict
|
|
713
|
+
A dictionary containing image metadata. This is either the
|
|
714
|
+
value associated with the 'imageinfo' keyword in the table,
|
|
715
|
+
or a default dictionary {'imagetype': 'Intensity',
|
|
716
|
+
'objectname': ''} if the keyword is absent.
|
|
717
|
+
"""
|
|
718
|
+
with table(self._imagename) as tb:
|
|
719
|
+
if "imageinfo" in tb.keywordnames():
|
|
720
|
+
image_metadata = tb.getkeyword("imageinfo")
|
|
721
|
+
else:
|
|
722
|
+
image_metadata = {"imagetype": "Intensity", "objectname": ""}
|
|
723
|
+
|
|
724
|
+
return image_metadata
|
|
725
|
+
|
|
726
|
+
def datatype(self):
|
|
727
|
+
return self.pixeltype()
|
|
728
|
+
|
|
729
|
+
def _flatten_multibeam(self, imageinfo):
|
|
730
|
+
"""Flatten the per-plane beam information in the image metadata.
|
|
731
|
+
|
|
732
|
+
This method restructures the `perplanebeams` field in `imageinfo`
|
|
733
|
+
to make it more accessible by flattening the nested structure.
|
|
734
|
+
|
|
735
|
+
Parameters
|
|
736
|
+
----------
|
|
737
|
+
imageinfo : dict
|
|
738
|
+
The image metadata containing per-plane beam information.
|
|
739
|
+
|
|
740
|
+
Returns
|
|
741
|
+
-------
|
|
742
|
+
dict
|
|
743
|
+
Updated `imageinfo` dictionary with flattened per-plane beam data.
|
|
744
|
+
"""
|
|
745
|
+
if "perplanebeams" in imageinfo:
|
|
746
|
+
perplanebeams = imageinfo["perplanebeams"]["beams"]
|
|
747
|
+
perplanebeams_flat = {}
|
|
748
|
+
nchan = imageinfo["perplanebeams"]["nChannels"]
|
|
749
|
+
npol = imageinfo["perplanebeams"]["nStokes"]
|
|
750
|
+
|
|
751
|
+
for c in range(nchan):
|
|
752
|
+
for p in range(npol):
|
|
753
|
+
k = nchan * p + c
|
|
754
|
+
perplanebeams_flat["*" + str(k)] = perplanebeams["*" + str(c)][
|
|
755
|
+
"*" + str(p)
|
|
756
|
+
]
|
|
757
|
+
imageinfo["perplanebeams"].pop("beams", None)
|
|
758
|
+
imageinfo["perplanebeams"].update(perplanebeams_flat)
|
|
759
|
+
|
|
760
|
+
return imageinfo
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
class coordinatesystem(casatools.coordsys):
|
|
764
|
+
"""A wrapper around `casatools.coordsys` that provides python-casacore like methods"""
|
|
765
|
+
|
|
766
|
+
def __init__(self, image=None):
|
|
767
|
+
self._image = image
|
|
768
|
+
if image is None:
|
|
769
|
+
self._cs = casatools.coordsys()
|
|
770
|
+
else:
|
|
771
|
+
self._cs = image.coordsys()
|
|
772
|
+
|
|
773
|
+
def get_axes(self):
|
|
774
|
+
"""Retrieve the names of the coordinate axes.
|
|
775
|
+
|
|
776
|
+
Returns
|
|
777
|
+
-------
|
|
778
|
+
list of str or list of lists
|
|
779
|
+
A list containing the names of each axis, grouped by coordinate type.
|
|
780
|
+
Spectral axes are returned as a single string instead of a list.
|
|
781
|
+
"""
|
|
782
|
+
axes = []
|
|
783
|
+
axis_names = self._cs.names()
|
|
784
|
+
for axis_type in self.get_names():
|
|
785
|
+
axis_inds = self._cs.findcoordinate(axis_type).get("pixel")
|
|
786
|
+
axes_list = [axis_names[idx] for idx in axis_inds[::-1]]
|
|
787
|
+
if axis_type == "spectral":
|
|
788
|
+
axes_list = axes_list[0]
|
|
789
|
+
axes.append(axes_list)
|
|
790
|
+
return axes
|
|
791
|
+
|
|
792
|
+
def get_referencepixel(self):
|
|
793
|
+
"""Get the reference pixel coordinates.
|
|
794
|
+
|
|
795
|
+
Returns
|
|
796
|
+
-------
|
|
797
|
+
list of float
|
|
798
|
+
The numeric reference pixel values, with axes reversed.
|
|
799
|
+
"""
|
|
800
|
+
return self._cs.referencepixel()["numeric"][::-1]
|
|
801
|
+
|
|
802
|
+
def get_referencevalue(self):
|
|
803
|
+
"""Get the reference value at the reference pixel.
|
|
804
|
+
|
|
805
|
+
Returns
|
|
806
|
+
-------
|
|
807
|
+
list of float
|
|
808
|
+
The numeric reference values, with axes reversed.
|
|
809
|
+
"""
|
|
810
|
+
return self._cs.referencevalue()["numeric"][::-1]
|
|
811
|
+
|
|
812
|
+
def get_increment(self):
|
|
813
|
+
"""Get the coordinate increments per pixel.
|
|
814
|
+
|
|
815
|
+
Returns
|
|
816
|
+
-------
|
|
817
|
+
list of float
|
|
818
|
+
The coordinate increment values, with axes reversed.
|
|
819
|
+
"""
|
|
820
|
+
return self._cs.increment()["numeric"][::-1]
|
|
821
|
+
|
|
822
|
+
def get_unit(self):
|
|
823
|
+
"""Get the units of the coordinate axes.
|
|
824
|
+
|
|
825
|
+
Returns
|
|
826
|
+
-------
|
|
827
|
+
list of str
|
|
828
|
+
The units of each axis, with axes reversed.
|
|
829
|
+
"""
|
|
830
|
+
return self._cs.units()[::-1]
|
|
831
|
+
|
|
832
|
+
def get_names(self):
|
|
833
|
+
"""Get the coordinate type names in lowercase.
|
|
834
|
+
|
|
835
|
+
Returns
|
|
836
|
+
-------
|
|
837
|
+
list of str
|
|
838
|
+
The coordinate type names, with axes reversed.
|
|
839
|
+
"""
|
|
840
|
+
return list(map(str.lower, self._cs.coordinatetype()[::-1]))
|
|
841
|
+
|
|
842
|
+
def dict(self):
|
|
843
|
+
"""Convert the coordinate system to a dictionary representation.
|
|
844
|
+
|
|
845
|
+
Returns
|
|
846
|
+
-------
|
|
847
|
+
dict
|
|
848
|
+
The coordinate system in CASA's dictionary format.
|
|
849
|
+
"""
|
|
850
|
+
return self._cs.torecord()
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
class directioncoordinate(coordinatesystem):
|
|
854
|
+
def __init__(self, rec):
|
|
855
|
+
super().__init__()
|
|
856
|
+
self._rec = rec
|
|
857
|
+
|
|
858
|
+
def get_projection(self):
|
|
859
|
+
return self._rec["projection"]
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
class coordinates:
|
|
863
|
+
def __init__(self):
|
|
864
|
+
pass
|
|
865
|
+
|
|
866
|
+
class spectralcoordinate(coordinatesystem):
|
|
867
|
+
def __init__(self, rec):
|
|
868
|
+
super().__init__()
|
|
869
|
+
self._rec = rec
|
|
870
|
+
|
|
871
|
+
def get_restfrequency(self):
|
|
872
|
+
return self._rec["restfreq"]
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
@wrap_class_methods
|
|
876
|
+
class tablerow(casatools.tablerow):
|
|
877
|
+
"""A wrapper for the casatools tablerow object.
|
|
878
|
+
|
|
879
|
+
Parameters
|
|
880
|
+
----------
|
|
881
|
+
table : table
|
|
882
|
+
The table object to wrap.
|
|
883
|
+
columnnames : list of str, optional
|
|
884
|
+
Column names to include or exclude.
|
|
885
|
+
exclude : bool, optional
|
|
886
|
+
Whether to exclude the specified columns.
|
|
887
|
+
"""
|
|
888
|
+
|
|
889
|
+
def __init__(
|
|
890
|
+
self, table: table, columnnames: List[str] = [], exclude: bool = False
|
|
891
|
+
):
|
|
892
|
+
super().__init__(table, columnnames=columnnames, exclude=exclude)
|
|
893
|
+
|
|
894
|
+
@method_wrapper
|
|
895
|
+
def get(self, rownr: int) -> Dict[str, Any]:
|
|
896
|
+
"""Retrieve data for a specific row.
|
|
897
|
+
|
|
898
|
+
Parameters
|
|
899
|
+
----------
|
|
900
|
+
rownr : int
|
|
901
|
+
The row number to retrieve.
|
|
902
|
+
|
|
903
|
+
Returns
|
|
904
|
+
-------
|
|
905
|
+
dict
|
|
906
|
+
A dictionary containing row data.
|
|
907
|
+
"""
|
|
908
|
+
return super().get(rownr)
|
|
909
|
+
|
|
910
|
+
def __getitem__(
|
|
911
|
+
self, key: Union[int, slice]
|
|
912
|
+
) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
|
|
913
|
+
"""Retrieve rows using indexing or slicing.
|
|
914
|
+
|
|
915
|
+
Parameters
|
|
916
|
+
----------
|
|
917
|
+
key : int or slice
|
|
918
|
+
The row index or slice to retrieve.
|
|
919
|
+
|
|
920
|
+
Returns
|
|
921
|
+
-------
|
|
922
|
+
dict or list of dict
|
|
923
|
+
The row(s) corresponding to the key.
|
|
924
|
+
"""
|
|
925
|
+
if isinstance(key, slice):
|
|
926
|
+
return [self.get(irow) for irow in range(*key.indices(len(self)))]
|
|
927
|
+
elif isinstance(key, int):
|
|
928
|
+
return self.get(key)
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
@wrap_class_methods
|
|
932
|
+
class tablecolumn:
|
|
933
|
+
"""A class representing a single column in a table.
|
|
934
|
+
|
|
935
|
+
Provides methods to access values in the column with indexing and slicing.
|
|
936
|
+
|
|
937
|
+
Parameters
|
|
938
|
+
----------
|
|
939
|
+
table : Any
|
|
940
|
+
The table object containing the column.
|
|
941
|
+
columnname : str
|
|
942
|
+
The name of the column in the table.
|
|
943
|
+
|
|
944
|
+
Attributes
|
|
945
|
+
----------
|
|
946
|
+
_table : Any
|
|
947
|
+
The table object containing the column.
|
|
948
|
+
_columnname : str
|
|
949
|
+
The name of the column in the table.
|
|
950
|
+
"""
|
|
951
|
+
|
|
952
|
+
def __init__(self, table: Any, columnname: str):
|
|
953
|
+
self._table = table
|
|
954
|
+
self._columnname = columnname
|
|
955
|
+
|
|
956
|
+
@method_wrapper
|
|
957
|
+
def get(self, irow: int) -> Any:
|
|
958
|
+
"""Get the value at a specific row in the column.
|
|
959
|
+
|
|
960
|
+
Parameters
|
|
961
|
+
----------
|
|
962
|
+
irow : int
|
|
963
|
+
The index of the row to retrieve.
|
|
964
|
+
|
|
965
|
+
Returns
|
|
966
|
+
-------
|
|
967
|
+
Any
|
|
968
|
+
The value in the specified row of the column.
|
|
969
|
+
"""
|
|
970
|
+
return self._table.getcell(self._columnname, irow)
|
|
971
|
+
|
|
972
|
+
def __getitem__(self, key: Union[int, slice]) -> Union[Any, List[Any]]:
|
|
973
|
+
"""Get a value or a list of values from the column using indexing or slicing.
|
|
974
|
+
|
|
975
|
+
Parameters
|
|
976
|
+
----------
|
|
977
|
+
key : int or slice
|
|
978
|
+
The index or slice to retrieve values from the column.
|
|
979
|
+
|
|
980
|
+
Returns
|
|
981
|
+
-------
|
|
982
|
+
Any or list of Any
|
|
983
|
+
The value(s) retrieved from the column.
|
|
984
|
+
|
|
985
|
+
Examples
|
|
986
|
+
--------
|
|
987
|
+
>>> table = MockTable()
|
|
988
|
+
>>> column = TableColumn(table, 'col1')
|
|
989
|
+
>>> column[0] # Get the first row's value
|
|
990
|
+
42
|
|
991
|
+
>>> column[1:3] # Get values from rows 1 to 2
|
|
992
|
+
[43, 44]
|
|
993
|
+
"""
|
|
994
|
+
if isinstance(key, slice):
|
|
995
|
+
return [self.get(irow) for irow in range(*key.indices(self._table.nrows()))]
|
|
996
|
+
elif isinstance(key, int):
|
|
997
|
+
return self.get(key)
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
def tableexists(path):
|
|
1001
|
+
return os.path.isdir(path)
|