cubevis 0.5.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.
Files changed (132) hide show
  1. cubevis/LICENSE.rst +500 -0
  2. cubevis/__icons__/20px/fast-backward.svg +13 -0
  3. cubevis/__icons__/20px/fast-forward.svg +13 -0
  4. cubevis/__icons__/20px/step-backward.svg +12 -0
  5. cubevis/__icons__/20px/step-forward.svg +12 -0
  6. cubevis/__icons__/add-chan.png +0 -0
  7. cubevis/__icons__/add-chan.svg +84 -0
  8. cubevis/__icons__/add-cube.png +0 -0
  9. cubevis/__icons__/add-cube.svg +186 -0
  10. cubevis/__icons__/drag.png +0 -0
  11. cubevis/__icons__/drag.svg +109 -0
  12. cubevis/__icons__/mask-selected.png +0 -0
  13. cubevis/__icons__/mask.png +0 -0
  14. cubevis/__icons__/mask.svg +1 -0
  15. cubevis/__icons__/new-layer-sm-selected.png +0 -0
  16. cubevis/__icons__/new-layer-sm-selected.svg +88 -0
  17. cubevis/__icons__/new-layer-sm.png +0 -0
  18. cubevis/__icons__/new-layer-sm.svg +15 -0
  19. cubevis/__icons__/reset.png +0 -0
  20. cubevis/__icons__/reset.svg +11 -0
  21. cubevis/__icons__/sub-chan.png +0 -0
  22. cubevis/__icons__/sub-chan.svg +71 -0
  23. cubevis/__icons__/sub-cube.png +0 -0
  24. cubevis/__icons__/sub-cube.svg +95 -0
  25. cubevis/__icons__/zoom-to-fit.png +0 -0
  26. cubevis/__icons__/zoom-to-fit.svg +21 -0
  27. cubevis/__init__.py +58 -0
  28. cubevis/__js__/bokeh-3.6.1.min.js +728 -0
  29. cubevis/__js__/bokeh-tables-3.6.1.min.js +119 -0
  30. cubevis/__js__/bokeh-widgets-3.6.1.min.js +141 -0
  31. cubevis/__js__/casalib.min.js +1 -0
  32. cubevis/__js__/cubevisjs.min.js +62 -0
  33. cubevis/__version__.py +1 -0
  34. cubevis/apps/__init__.py +44 -0
  35. cubevis/apps/_createmask.py +461 -0
  36. cubevis/apps/_createregion.py +513 -0
  37. cubevis/apps/_interactiveclean.py +3260 -0
  38. cubevis/apps/_interactiveclean_wrappers.py +130 -0
  39. cubevis/apps/_ms_raster.py +815 -0
  40. cubevis/apps/_plotants.py +286 -0
  41. cubevis/apps/_plotbandpass.py +7 -0
  42. cubevis/bokeh/__init__.py +29 -0
  43. cubevis/bokeh/annotations/__init__.py +1 -0
  44. cubevis/bokeh/annotations/_ev_poly_annotation.py +6 -0
  45. cubevis/bokeh/components/__init__.py +28 -0
  46. cubevis/bokeh/format/__init__.py +31 -0
  47. cubevis/bokeh/format/_time_ticks.py +44 -0
  48. cubevis/bokeh/format/_wcs_ticks.py +45 -0
  49. cubevis/bokeh/models/__init__.py +4 -0
  50. cubevis/bokeh/models/_edit_span.py +7 -0
  51. cubevis/bokeh/models/_ev_text_input.py +6 -0
  52. cubevis/bokeh/models/_tip.py +37 -0
  53. cubevis/bokeh/models/_tip_button.py +50 -0
  54. cubevis/bokeh/sources/__init__.py +35 -0
  55. cubevis/bokeh/sources/_data_pipe.py +258 -0
  56. cubevis/bokeh/sources/_image_data_source.py +83 -0
  57. cubevis/bokeh/sources/_image_pipe.py +581 -0
  58. cubevis/bokeh/sources/_spectra_data_source.py +55 -0
  59. cubevis/bokeh/sources/_updatable_data_source.py +189 -0
  60. cubevis/bokeh/state/__init__.py +34 -0
  61. cubevis/bokeh/state/_initialize.py +164 -0
  62. cubevis/bokeh/state/_javascript.py +53 -0
  63. cubevis/bokeh/state/_palette.py +58 -0
  64. cubevis/bokeh/state/_session.py +44 -0
  65. cubevis/bokeh/state/js/bokeh-2.4.1.min.js +596 -0
  66. cubevis/bokeh/state/js/bokeh-gl-2.4.1.min.js +74 -0
  67. cubevis/bokeh/state/js/bokeh-tables-2.4.1.min.js +132 -0
  68. cubevis/bokeh/state/js/bokeh-widgets-2.4.1.min.js +118 -0
  69. cubevis/bokeh/state/js/casaguijs-v0.0.4.0-b2.4.min.js +49 -0
  70. cubevis/bokeh/state/js/casaguijs-v0.0.5.0-b2.4.min.js +49 -0
  71. cubevis/bokeh/state/js/casaguijs-v0.0.6.0-b2.4.min.js +49 -0
  72. cubevis/bokeh/state/js/casalib-v0.0.1.min.js +1 -0
  73. cubevis/bokeh/tools/__init__.py +31 -0
  74. cubevis/bokeh/tools/_cbreset_tool.py +52 -0
  75. cubevis/bokeh/tools/_drag_tool.py +61 -0
  76. cubevis/bokeh/utils/__init__.py +35 -0
  77. cubevis/bokeh/utils/_axes_labels.py +94 -0
  78. cubevis/bokeh/utils/_svg_icon.py +136 -0
  79. cubevis/data/__init__.py +1 -0
  80. cubevis/data/casaimage/__init__.py +114 -0
  81. cubevis/data/measurement_set/__init__.py +7 -0
  82. cubevis/data/measurement_set/_ms_data.py +178 -0
  83. cubevis/data/measurement_set/processing_set/__init__.py +30 -0
  84. cubevis/data/measurement_set/processing_set/_ps_concat.py +98 -0
  85. cubevis/data/measurement_set/processing_set/_ps_coords.py +78 -0
  86. cubevis/data/measurement_set/processing_set/_ps_data.py +213 -0
  87. cubevis/data/measurement_set/processing_set/_ps_io.py +55 -0
  88. cubevis/data/measurement_set/processing_set/_ps_raster_data.py +154 -0
  89. cubevis/data/measurement_set/processing_set/_ps_select.py +91 -0
  90. cubevis/data/measurement_set/processing_set/_ps_stats.py +218 -0
  91. cubevis/data/measurement_set/processing_set/_xds_data.py +149 -0
  92. cubevis/plot/__init__.py +1 -0
  93. cubevis/plot/ms_plot/__init__.py +29 -0
  94. cubevis/plot/ms_plot/_ms_plot.py +242 -0
  95. cubevis/plot/ms_plot/_ms_plot_constants.py +22 -0
  96. cubevis/plot/ms_plot/_ms_plot_selectors.py +348 -0
  97. cubevis/plot/ms_plot/_raster_plot.py +292 -0
  98. cubevis/plot/ms_plot/_raster_plot_inputs.py +116 -0
  99. cubevis/plot/ms_plot/_xds_plot_axes.py +110 -0
  100. cubevis/private/__java__/xml-casa-assembly-1.86.jar +0 -0
  101. cubevis/private/_gclean.py +798 -0
  102. cubevis/private/casashell/createmask.py +332 -0
  103. cubevis/private/casashell/iclean.py +4432 -0
  104. cubevis/private/casatasks/__init__.py +140 -0
  105. cubevis/private/casatasks/createmask.py +86 -0
  106. cubevis/private/casatasks/createregion.py +83 -0
  107. cubevis/private/casatasks/iclean.py +1831 -0
  108. cubevis/readme.rst +16 -0
  109. cubevis/remote/__init__.py +10 -0
  110. cubevis/remote/_gclean.py +61 -0
  111. cubevis/remote/_local.py +287 -0
  112. cubevis/remote/_remote_kernel.py +80 -0
  113. cubevis/toolbox/__init__.py +32 -0
  114. cubevis/toolbox/_app_context.py +74 -0
  115. cubevis/toolbox/_cube.py +3457 -0
  116. cubevis/toolbox/_region_list.py +197 -0
  117. cubevis/utils/_ResourceManager.py +86 -0
  118. cubevis/utils/__init__.py +620 -0
  119. cubevis/utils/_contextmgrchain.py +84 -0
  120. cubevis/utils/_conversion.py +93 -0
  121. cubevis/utils/_copydoc.py +55 -0
  122. cubevis/utils/_docenum.py +25 -0
  123. cubevis/utils/_import_protected_module.py +35 -0
  124. cubevis/utils/_logging.py +85 -0
  125. cubevis/utils/_pkgs.py +77 -0
  126. cubevis/utils/_regions.py +40 -0
  127. cubevis/utils/_static.py +66 -0
  128. cubevis/utils/_tiles.py +167 -0
  129. cubevis-0.5.2.dist-info/METADATA +151 -0
  130. cubevis-0.5.2.dist-info/RECORD +132 -0
  131. cubevis-0.5.2.dist-info/WHEEL +4 -0
  132. cubevis-0.5.2.dist-info/licenses/LICENSE +504 -0
@@ -0,0 +1,93 @@
1
+ ########################################################################
2
+ #
3
+ # Copyright (C) 2021,2023
4
+ # Associated Universities, Inc. Washington DC, USA.
5
+ #
6
+ # This script is free software; you can redistribute it and/or modify it
7
+ # under the terms of the GNU Library General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or (at your
9
+ # option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful, but WITHOUT
12
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14
+ # License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Library General Public License
17
+ # along with this library; if not, write to the Free Software Foundation,
18
+ # Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
19
+ #
20
+ # Correspondence concerning AIPS++ should be adressed as follows:
21
+ # Internet email: casa-feedback@nrao.edu.
22
+ # Postal address: AIPS++ Project Office
23
+ # National Radio Astronomy Observatory
24
+ # 520 Edgemont Road
25
+ # Charlottesville, VA 22903-2475 USA
26
+ #
27
+ ########################################################################
28
+ '''Contains for conversion of data passed between Python and JavaScript
29
+ via websockets'''
30
+
31
+ import json
32
+ import numpy as np
33
+ from bokeh.util.serialization import transform_array
34
+ from bokeh.core.serialization import Serializer, Deserializer
35
+ from bokeh.core.json_encoder import serialize_json
36
+ from ._static import static_vars
37
+
38
+ def strip_arrays( val ):
39
+ '''convert all numpy arrays contained within val to lists
40
+ '''
41
+ if isinstance( val, dict ):
42
+ result = { }
43
+ for k, v in val.items( ):
44
+ result[k] = strip_arrays(v)
45
+ return result
46
+ if isinstance( val, np.ndarray ):
47
+ return val.tolist( )
48
+ if isinstance( val, range ):
49
+ return list(val)
50
+ return val
51
+
52
+ @static_vars( encoder=Serializer(deferred=False) )
53
+ def serialize( val ):
54
+ '''convert python values to a string that can be sent via websockets
55
+ '''
56
+ return serialize_json(serialize.encoder.serialize(val))
57
+
58
+ @static_vars( decoder=Deserializer( ) )
59
+ def deserialize( val ):
60
+ '''convert an encoded value received from websockets
61
+ '''
62
+ value = json.loads(val)
63
+ return deserialize.decoder.deserialize(value)
64
+
65
+ def pack_arrays( val ):
66
+ """Convert `numpy` N dimensional arrays stored within a dictionary to
67
+ a format that can be converted into the multi-dimensional arrays that
68
+ are usable for Bokeh data.
69
+
70
+ Parameters
71
+ ----------
72
+ val: value
73
+
74
+ Returns
75
+ -------
76
+ value
77
+ return value is identical to `val` parameter except that any
78
+ N dimensional `numpy` arrays are converted to Bokeh compatible
79
+ format
80
+ """
81
+ if isinstance( val, dict ):
82
+ result = { }
83
+ for k, v in val.items( ):
84
+ result[k] = pack_arrays(v)
85
+ return result
86
+ if isinstance( val, np.ndarray ):
87
+ if isinstance(val, np.ma.MaskedArray):
88
+ return transform_array(val.filled(0))
89
+ else:
90
+ return transform_array(val)
91
+ if isinstance( val, range ):
92
+ return list(val)
93
+ return val
@@ -0,0 +1,55 @@
1
+ ########################################################################
2
+ #
3
+ # Copyright (C) 2024
4
+ # Associated Universities, Inc. Washington DC, USA.
5
+ #
6
+ # This script is free software; you can redistribute it and/or modify it
7
+ # under the terms of the GNU Library General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or (at your
9
+ # option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful, but WITHOUT
12
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14
+ # License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Library General Public License
17
+ # along with this library; if not, write to the Free Software Foundation,
18
+ # Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
19
+ #
20
+ # Correspondence concerning AIPS++ should be adressed as follows:
21
+ # Internet email: casa-feedback@nrao.edu.
22
+ # Postal address: AIPS++ Project Office
23
+ # National Radio Astronomy Observatory
24
+ # 520 Edgemont Road
25
+ # Charlottesville, VA 22903-2475 USA
26
+ #
27
+ ########################################################################
28
+ '''Reuse doc string'''
29
+ import os
30
+
31
+ #--------------------------------------------------
32
+ # ParamSpec is available with Python >= 3.10
33
+ #--------------------------------------------------
34
+ #from typing import Callable, TypeVar, ParamSpec
35
+ #
36
+ #P = ParamSpec("P")
37
+ #T = TypeVar("T")
38
+ #
39
+ #def copydoc(src: Callable[P, T]):
40
+ # """Copy documentation from src"""
41
+ #
42
+ # def decorator(dest: Callable) -> Callable[P, T]:
43
+ # dest.__doc__ = src.__doc__
44
+ # return dest
45
+ #
46
+ # return decorator
47
+
48
+ def copydoc(src):
49
+ """Copy documentation from src"""
50
+
51
+ def decorator(dest):
52
+ dest.__doc__ = src.__doc__
53
+ return dest
54
+
55
+ return decorator
@@ -0,0 +1,25 @@
1
+ from enum import Enum
2
+
3
+ class DocEnum(Enum):
4
+ '''Allows for documenting individual enum values::
5
+
6
+ In [6]: class MaskMode(DocEnum):
7
+ ...: 'Different masking modes available in addition to a user supplied mask'
8
+ ...: PB = 1, 'primary beam mask'
9
+ ...: AUTOMT = 2, 'multi-threshold auto masking'
10
+ ...:
11
+
12
+ In [7]: MaskMode.PB?
13
+ Type: MaskMode
14
+ String form: MaskMode.PB
15
+ Docstring: primary beam mask
16
+ Class docstring: Different masking modes available in addition to a user supplied mask
17
+
18
+ In [8]:
19
+ '''
20
+ def __new__(cls, value, doc=None):
21
+ self = object.__new__(cls) # calling super().__new__(value) here would fail
22
+ self._value_ = value
23
+ if doc is not None:
24
+ self.__doc__ = doc
25
+ return self
@@ -0,0 +1,35 @@
1
+ import inspect
2
+ import importlib
3
+ import sys
4
+ import types
5
+
6
+ def ImportProtectedModule( name, file_mapping ):
7
+
8
+ class module_import(types.ModuleType):
9
+ """Import objects, functions and classes for export while avoiding requiring user to
10
+ provide the sub-package name for the file contining the imported object, for example
11
+ allowing:
12
+ from cubevis.apps import iclean
13
+
14
+ instead of:
15
+ from cubevis.app._interactive_clean import iclean
16
+
17
+ while also not importing '_interactive_clean' (inside of __init__.py) regardless of
18
+ whether the user actually import 'iclean' or not. This is done to allow for some
19
+ dependencies to not be available but for the imports to still work as long as
20
+ the dependencies which are needed for the imported classes are available.
21
+ """
22
+
23
+ _module = name
24
+ _mapping = file_mapping
25
+
26
+ def __getattr__(self, name):
27
+ if name in self._mapping:
28
+ m = importlib.import_module(self._mapping[name], self._module) # import submodule
29
+ o = getattr(m, name) # find the required member
30
+ setattr(sys.modules[self._module], name, o) # bind it into the package
31
+ return o
32
+ else:
33
+ raise AttributeError(f'module {__name__} has no attribute {name}')
34
+
35
+ return module_import
@@ -0,0 +1,85 @@
1
+ """ Use the python logging interface for cubevis logs.
2
+ If casalog is available, then send log messages to it.
3
+ Otherwise, use the base python logging mechanisms.
4
+ """
5
+ import logging
6
+
7
+ try:
8
+ from casatasks import casalog
9
+ __casalog_avail = True
10
+ except ImportError:
11
+ __casalog_avail = False
12
+
13
+ class _Logging:
14
+ # singleton instance
15
+ logger = None
16
+ log_handler = None
17
+
18
+ class CasalogHandler(logging.Handler):
19
+ """ Logs to casalog, as appropriate """
20
+ # map from python logging levels to casalog priorities
21
+ level_to_priority = {
22
+ "DEBUG": "DEBUG",
23
+ "INFO": "INFO",
24
+ "WARNING": "WARN",
25
+ "ERROR": "SEVERE",
26
+ "CRITICAL": "SEVERE"
27
+ }
28
+
29
+ def _level_to_levelname(self, level):
30
+ if (level >= 50):
31
+ return "CRITICAL"
32
+ if (level >= 40):
33
+ return "ERROR"
34
+ if (level >= 30):
35
+ return "WARNING"
36
+ if (level >= 20):
37
+ return "INFO"
38
+ return "DEBUG"
39
+
40
+ def setLevel(self, level_or_levelname):
41
+ super().setLevel(level_or_levelname)
42
+ if type(level_or_levelname) != str:
43
+ levelname = self._level_to_levelname(level_or_levelname)
44
+ else:
45
+ levelname = level_or_levelname
46
+ casalog.filter(CasalogHandler.level_to_priority[levelname])
47
+
48
+ def emit(self, record):
49
+ # promote log level for casalog
50
+ levelname = self._level_to_levelname(_Logging.logger.getEffectiveLevel())
51
+ if levelname == "DEBUG":
52
+ casalog.filter(CasalogHandler.level_to_priority["DEBUG"])
53
+
54
+ # get log properties
55
+ origin = (record.filename) if (record.filename != None) else ("")
56
+ casalog.origin(origin)
57
+ priority = CasalogHandler.level_to_priority[record.levelname]
58
+ self.format(record) # populates record.message
59
+
60
+ # emit the log
61
+ casalog.post(message=record.message, priority=priority, origin=record.funcName)
62
+
63
+ class CasalogLogger(logging.getLoggerClass()):
64
+ def setLevel(self, level):
65
+ super().setLevel(level)
66
+ _Logging.log_handler.setLevel(level)
67
+
68
+ def get_logger():
69
+ """ Returns the singleton logger instance. """
70
+ if _Logging.logger != None:
71
+ return _Logging.logger
72
+
73
+ if __casalog_avail:
74
+ _Logging.log_handler = CasalogHandler()
75
+
76
+ curr_logger_class = logging.getLoggerClass()
77
+ logging.setLoggerClass(CasalogLogger)
78
+ _Logging.logger = logging.getLogger(__name__)
79
+ logging.setLoggerClass(curr_logger_class)
80
+
81
+ _Logging.logger.addHandler(_Logging.log_handler)
82
+ _Logging.logger.propagate = False
83
+ else:
84
+ _Logging.logger = logging.getLogger(__name__)
85
+ return _Logging.logger
cubevis/utils/_pkgs.py ADDED
@@ -0,0 +1,77 @@
1
+ import os
2
+ import sys
3
+ import importlib.util
4
+ from os.path import join, exists, realpath
5
+
6
+ def find_pkg( name, extra_dirs=[] ):
7
+ """
8
+ Returns list of paths which contain the ``name`` pkg. It is assumed that the
9
+ ``name`` will be the name of a directory (containing the package) or that
10
+ ``name.py(c)`` will be a file that exists. This does not attempt to import the
11
+ package.
12
+
13
+ Parameters
14
+ ----------
15
+ name: string
16
+ the name of the package in python "dot" notation to be searched for
17
+ extra_dirs: list of strings
18
+ directories to be searched in addition to those included in ``sys.path``
19
+
20
+ Returns
21
+ -------
22
+ list of import specifications
23
+ specifications of ``name`` found by search ``sys.path`` and ``extra_dirs``
24
+ """
25
+ result = [ ]
26
+ filename = (name.split('.'))
27
+ for dir in sys.path[:] + extra_dirs:
28
+ path = join( dir, *filename )
29
+ if exists(path):
30
+ spec = importlib.util.spec_from_file_location(name,path)
31
+ if spec:
32
+ result.append(spec)
33
+ else:
34
+ path = f"{path}.py"
35
+ if exists(path):
36
+ spec = importlib.util.spec_from_file_location(name,path)
37
+ if spec:
38
+ result.append(spec)
39
+ else:
40
+ path = f"{path}c"
41
+ if exists(path):
42
+ spec = importlib.util.spec_from_file_location(name,path)
43
+ if spec:
44
+ result.append(spec)
45
+ return result
46
+
47
+ def load_pkg( spec ):
48
+ """
49
+ Loads and returns the module specified by ``spec`` (e.g. as returned from ``find_pkg``)
50
+ If ``spec.name`` does not already exist in ``sys.modules``, it is added.
51
+
52
+ Parameters
53
+ ----------
54
+ spec: ModuleSpec
55
+ the module to be loaded
56
+
57
+ Returns
58
+ -------
59
+ module
60
+ the loaded module
61
+ """
62
+
63
+ import _frozen_importlib
64
+ if not isinstance(spec,_frozen_importlib.ModuleSpec):
65
+ raise ImportError(f"Expected ModuleSpec instead of {type(spec)}")
66
+
67
+ pkg = importlib.util.module_from_spec(spec)
68
+
69
+ try:
70
+ spec.loader.exec_module(pkg)
71
+ except:
72
+ raise ImportError(f"Could not load {spec.origin}")
73
+
74
+ if spec.name not in sys.modules:
75
+ sys.modules[spec.name] = pkg
76
+
77
+ return pkg
@@ -0,0 +1,40 @@
1
+
2
+ from itertools import product
3
+ from matplotlib.path import Path
4
+ from math import floor,ceil
5
+
6
+ def polygon_indexes( xs, ys, shape ):
7
+ '''
8
+ Returns indexes for a 2D array of the given ``shape`` which
9
+ lie within the polygon specified by ``xs`` and ``ys``.
10
+
11
+ Parameters
12
+ ----------
13
+ xs: list of numbers
14
+ the X coordinates for the vertices of the polygon
15
+ ys: list of numbers
16
+ the Y coordinates for the vertices of the polygon
17
+ shape: ( int, int )
18
+ the shape of the plane in which the polygon is found
19
+
20
+ Returns
21
+ -------
22
+ generator of tuples:
23
+ the stream of tuples that is returned will be the indexes
24
+ of the elements which lie within the polygon
25
+ '''
26
+ assert len(shape) == 2, 'contains only works for 2D shapes, so "shape" should have length equal to two'
27
+ assert len(xs) == len(ys), 'to specify a polygon the number of X values must equal the number of Y values'
28
+
29
+ if len(xs) == 4 and len(ys) == 4 :
30
+ uniqx = sorted(set(xs))
31
+ uniqy = sorted(set(ys))
32
+ if len(uniqx) == 2 and len(uniqy) == 2:
33
+ ### we have a proper box, the Path.contains_point implementation seems to
34
+ ### err slightly with very small regions...
35
+ return product(range(max(floor(uniqx[0]),0),min(ceil(uniqx[1]),shape[0]-1)),range(max(floor(uniqy[0]),0),min(ceil(uniqy[1]),shape[1]-1)))
36
+
37
+ path = Path(list(zip(xs,ys)))
38
+ xmin, xmax = max([0,min(xs)-1]), max(xs)+1
39
+ ymin, ymax = max([0,min(ys)-1]), max(ys)+1
40
+ return filter( path.contains_point, product(range(max(floor(xmin),0),min(ceil(xmax),shape[0]-1)), range(max(floor(ymin),0),min(ceil(ymax),shape[1]-1))) )
@@ -0,0 +1,66 @@
1
+ ########################################################################
2
+ #
3
+ # Copyright (C) 2021,2023
4
+ # Associated Universities, Inc. Washington DC, USA.
5
+ #
6
+ # This script is free software; you can redistribute it and/or modify it
7
+ # under the terms of the GNU Library General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or (at your
9
+ # option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful, but WITHOUT
12
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14
+ # License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Library General Public License
17
+ # along with this library; if not, write to the Free Software Foundation,
18
+ # Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
19
+ #
20
+ # Correspondence concerning AIPS++ should be adressed as follows:
21
+ # Internet email: casa-feedback@nrao.edu.
22
+ # Postal address: AIPS++ Project Office
23
+ # National Radio Astronomy Observatory
24
+ # 520 Edgemont Road
25
+ # Charlottesville, VA 22903-2475 USA
26
+ #
27
+ ########################################################################
28
+ '''Contains functions for attaching static value as attributes of functions'''
29
+
30
+ def static_vars(**kwargs):
31
+ '''Initialize static function variables to for use within a function.
32
+
33
+ This function is used as a decorator which allows for the initialization of
34
+ static local variables for use within a function. It is used like:
35
+
36
+ @static_vars(counter=0)
37
+ def foo():
38
+ foo.counter += 1
39
+ print "Counter is %d" % foo.counter
40
+
41
+ This is used from:
42
+ https://stackoverflow.com/questions/279561/what-is-the-python-equivalent-of-static-variables-inside-a-function?rq=1
43
+
44
+ Parameters
45
+ ----------
46
+ Initialized static local variables.
47
+ '''
48
+
49
+ def decorate(func):
50
+ for k, v in kwargs.items():
51
+ setattr(func, k, v)
52
+ return func
53
+
54
+ return decorate
55
+
56
+
57
+ def static_dir(func):
58
+ '''return a list of static variables associated with ``func``
59
+
60
+ Parameters
61
+ ----------
62
+ func: function
63
+ function with static variables
64
+ '''
65
+ return [a for a in dir(func) if a[0] != '_']
66
+
@@ -0,0 +1,167 @@
1
+ ########################################################################
2
+ #
3
+ # Copyright (C) 2023
4
+ # Associated Universities, Inc. Washington DC, USA.
5
+ #
6
+ # This script is free software; you can redistribute it and/or modify it
7
+ # under the terms of the GNU Library General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or (at your
9
+ # option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful, but WITHOUT
12
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14
+ # License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Library General Public License
17
+ # along with this library; if not, write to the Free Software Foundation,
18
+ # Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
19
+ #
20
+ # Correspondence concerning AIPS++ should be adressed as follows:
21
+ # Internet email: casa-feedback@nrao.edu.
22
+ # Postal address: AIPS++ Project Office
23
+ # National Radio Astronomy Observatory
24
+ # 520 Edgemont Road
25
+ # Charlottesville, VA 22903-2475 USA
26
+ #
27
+ ########################################################################
28
+ '''tiling utilities'''
29
+
30
+ from math import ceil, log
31
+
32
+ class TMSTiles(object):
33
+ ###
34
+ ### This class is based upon https://github.com/OSGeo/gdal/blob/master/swig/python/gdal-utils/osgeo_utils/gdal2tiles.py
35
+ ### written by: Klokan Petr Pridal (klokan at klokan dot cz)
36
+ ### Even Rouault (even dot rouault at spatialys.com)
37
+ ### Idan Miara (idan@miara.com)
38
+ ### It is a small subset of the original source code, and it is highly modified to only
39
+ ### handle raster profile tiling. The original source code has many useful details and
40
+ ### supports mercator and geodetic data. The subset of gdal2tiles.py used here is less
41
+ ### than 3% of the original file (42 lines out of 1697). While this is not considered
42
+ ### a substantial portion, it was invaluable for understanding the TMS tile layout.
43
+ ###
44
+ def __init__( self, dimension, title='' ):
45
+ '''
46
+ Construct a tiling object which will provide the coordinates of tiles, the size to
47
+ be read, the size of the scaled tile and the write offset within the :code:`tile_size`
48
+ output tile.
49
+
50
+ Parameters
51
+ ----------
52
+ dimension: list or tuple
53
+ should contain at least two elements (X,Y)
54
+ '''
55
+
56
+ def calculate_indexes( tz, result={ } ):
57
+
58
+ ti = 0
59
+ tile_details = []
60
+ tminx, tminy, tmaxx, tmaxy = self.tminmax[tz]
61
+
62
+ for ty in range(tmaxy, tminy - 1, -1):
63
+ for tx in range(tminx, tmaxx + 1):
64
+ ti += 1
65
+ ytile = ty
66
+ index = ( tz, tx, ty )
67
+
68
+ tsize = int(self.tsize[tz]) # tile_size in raster coordinates for actual zoom
69
+ xsize = self.__dimension[0]
70
+ ysize = self.__dimension[1]
71
+ querysize = self.__tile_size
72
+
73
+ rx = tx * tsize
74
+ rxsize = 0
75
+ if tx == tmaxx:
76
+ rxsize = xsize % tsize
77
+ if rxsize == 0:
78
+ rxsize = tsize
79
+
80
+ ry = ty * tsize
81
+ rysize = 0
82
+ if ty == tmaxy:
83
+ rysize = ysize % tsize
84
+ if rysize == 0:
85
+ rysize = tsize
86
+
87
+ wx, wy = 0, 0
88
+ wxsize = int(rxsize / float(tsize) * self.__tile_size)
89
+ wysize = int(rysize / float(tsize) * self.__tile_size)
90
+
91
+ ry = ysize - (ty * tsize) - rysize
92
+ if wysize != self.__tile_size:
93
+ wy = self.__tile_size - wysize
94
+
95
+ result[index] = dict( src=dict( idx=(rx, ry), dim=(rxsize, rysize),
96
+ t=(( 0, 0 if rysize == tsize else tsize - rysize ), tsize ) ),
97
+ dst=dict( idx=(wx, wy), dim=(wxsize, wysize) ) )
98
+ return result
99
+
100
+ self.__title = title
101
+ self.__tile_size = 256
102
+ self.__dimension = dimension
103
+ self.__zoom = (0, max( 0, int( max( ceil(log(dimension[0] / float(self.__tile_size), 2 )),
104
+ ceil( log(dimension[1] / float(self.__tile_size), 2 )) ) ) ) )
105
+ native_zoom = self.__zoom[1]
106
+
107
+ # Generate table with min max tile coordinates for all zoomlevels
108
+ self.tminmax = list(range(self.__zoom[0], self.__zoom[1] + 1))
109
+ self.tsize = list(range(self.__zoom[0], self.__zoom[1] + 1))
110
+ for tz in range(self.__zoom[0], self.__zoom[1] + 1):
111
+ tsize = 2.0**(native_zoom - tz) * self.__tile_size
112
+ tminx, tminy = 0, 0
113
+ tmaxx = int(ceil(dimension[0] / tsize)) - 1
114
+ tmaxy = int(ceil(dimension[1] / tsize)) - 1
115
+ self.tsize[tz] = ceil(tsize)
116
+ self.tminmax[tz] = (tminx, tminy, tmaxx, tmaxy)
117
+
118
+ self.__units_per_pixel = { }
119
+ for z in range(self.__zoom[0], self.__zoom[1] + 1):
120
+ self.__units_per_pixel[z] = 2**(self.__zoom[1] - z)
121
+
122
+ self.__tile_details = { }
123
+ for z in range(self.__zoom[0], self.__zoom[1] + 1):
124
+ calculate_indexes( z, self.__tile_details )
125
+
126
+ def profile( self ):
127
+ return "raster"
128
+
129
+ def dim( self ):
130
+ return self.__dimension
131
+
132
+ def tile_size( self ):
133
+ return self.__tile_size
134
+
135
+ def tile( self, z, x=None, y=None ):
136
+ try:
137
+ if (type(z) == tuple or type(z) == list) and len(z) == 3 and x is None and y is None:
138
+ return self.__tile_details[ (int(z[0]),int(z[1]),int(z[2])) ]
139
+ elif type(x) is not None and type(y) is not None:
140
+ return self.__tile_details[ (int(z),int(x),int(y)) ]
141
+ else:
142
+ raise RuntimeError( 'tile requires 3 integer parameters or 1 tuple[3] parameter' )
143
+ except KeyError:
144
+ return None
145
+
146
+ def zoom_levels( self, reverse=False ):
147
+ return sorted( list(self.__units_per_pixel.keys( )), reverse=reverse )
148
+
149
+ def units_per_pixel( self, zoom_level ):
150
+ if zoom_level not in self.__units_per_pixel:
151
+ raise RuntimeError(f'''{zoom_level} is not an existing zoom level''')
152
+ return self.__units_per_pixel[zoom_level]
153
+
154
+ def __str__( self ):
155
+ return f'''<?xml version="1.0" encoding="utf-8"?>
156
+ <TileMap version="1.0.0" tilemapservice="http://tms.osgeo.org/1.0.0">
157
+ <Title>{self.__title}</Title>
158
+ <Abstract></Abstract>
159
+ <SRS></SRS>
160
+ <BoundingBox minx="0.0000" miny="0.0000" maxx="{self.__dimension[0]}.0000" maxy="{self.__dimension[1]}.0000"/>
161
+ <Origin x="0.0000" y="0.0000"/>
162
+ <TileFormat width="256" height="256" mime-type="image/png" extension="png"/>
163
+ <TileSets profile="raster">''' + "\n" + '\n'.join( map( lambda z: f''' <TileSet href="{z}" units-per-pixel="{self.units_per_pixel(z)}.0000" order="{z}"/>''',
164
+ self.zoom_levels( ) ) ) + f'''
165
+ </TileSets>
166
+ </TileMap>
167
+ '''