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.
- cubevis/LICENSE.rst +500 -0
- cubevis/__icons__/20px/fast-backward.svg +13 -0
- cubevis/__icons__/20px/fast-forward.svg +13 -0
- cubevis/__icons__/20px/step-backward.svg +12 -0
- cubevis/__icons__/20px/step-forward.svg +12 -0
- cubevis/__icons__/add-chan.png +0 -0
- cubevis/__icons__/add-chan.svg +84 -0
- cubevis/__icons__/add-cube.png +0 -0
- cubevis/__icons__/add-cube.svg +186 -0
- cubevis/__icons__/drag.png +0 -0
- cubevis/__icons__/drag.svg +109 -0
- cubevis/__icons__/mask-selected.png +0 -0
- cubevis/__icons__/mask.png +0 -0
- cubevis/__icons__/mask.svg +1 -0
- cubevis/__icons__/new-layer-sm-selected.png +0 -0
- cubevis/__icons__/new-layer-sm-selected.svg +88 -0
- cubevis/__icons__/new-layer-sm.png +0 -0
- cubevis/__icons__/new-layer-sm.svg +15 -0
- cubevis/__icons__/reset.png +0 -0
- cubevis/__icons__/reset.svg +11 -0
- cubevis/__icons__/sub-chan.png +0 -0
- cubevis/__icons__/sub-chan.svg +71 -0
- cubevis/__icons__/sub-cube.png +0 -0
- cubevis/__icons__/sub-cube.svg +95 -0
- cubevis/__icons__/zoom-to-fit.png +0 -0
- cubevis/__icons__/zoom-to-fit.svg +21 -0
- cubevis/__init__.py +58 -0
- cubevis/__js__/bokeh-3.6.1.min.js +728 -0
- cubevis/__js__/bokeh-tables-3.6.1.min.js +119 -0
- cubevis/__js__/bokeh-widgets-3.6.1.min.js +141 -0
- cubevis/__js__/casalib.min.js +1 -0
- cubevis/__js__/cubevisjs.min.js +62 -0
- cubevis/__version__.py +1 -0
- cubevis/apps/__init__.py +44 -0
- cubevis/apps/_createmask.py +461 -0
- cubevis/apps/_createregion.py +513 -0
- cubevis/apps/_interactiveclean.py +3260 -0
- cubevis/apps/_interactiveclean_wrappers.py +130 -0
- cubevis/apps/_ms_raster.py +815 -0
- cubevis/apps/_plotants.py +286 -0
- cubevis/apps/_plotbandpass.py +7 -0
- cubevis/bokeh/__init__.py +29 -0
- cubevis/bokeh/annotations/__init__.py +1 -0
- cubevis/bokeh/annotations/_ev_poly_annotation.py +6 -0
- cubevis/bokeh/components/__init__.py +28 -0
- cubevis/bokeh/format/__init__.py +31 -0
- cubevis/bokeh/format/_time_ticks.py +44 -0
- cubevis/bokeh/format/_wcs_ticks.py +45 -0
- cubevis/bokeh/models/__init__.py +4 -0
- cubevis/bokeh/models/_edit_span.py +7 -0
- cubevis/bokeh/models/_ev_text_input.py +6 -0
- cubevis/bokeh/models/_tip.py +37 -0
- cubevis/bokeh/models/_tip_button.py +50 -0
- cubevis/bokeh/sources/__init__.py +35 -0
- cubevis/bokeh/sources/_data_pipe.py +258 -0
- cubevis/bokeh/sources/_image_data_source.py +83 -0
- cubevis/bokeh/sources/_image_pipe.py +581 -0
- cubevis/bokeh/sources/_spectra_data_source.py +55 -0
- cubevis/bokeh/sources/_updatable_data_source.py +189 -0
- cubevis/bokeh/state/__init__.py +34 -0
- cubevis/bokeh/state/_initialize.py +164 -0
- cubevis/bokeh/state/_javascript.py +53 -0
- cubevis/bokeh/state/_palette.py +58 -0
- cubevis/bokeh/state/_session.py +44 -0
- cubevis/bokeh/state/js/bokeh-2.4.1.min.js +596 -0
- cubevis/bokeh/state/js/bokeh-gl-2.4.1.min.js +74 -0
- cubevis/bokeh/state/js/bokeh-tables-2.4.1.min.js +132 -0
- cubevis/bokeh/state/js/bokeh-widgets-2.4.1.min.js +118 -0
- cubevis/bokeh/state/js/casaguijs-v0.0.4.0-b2.4.min.js +49 -0
- cubevis/bokeh/state/js/casaguijs-v0.0.5.0-b2.4.min.js +49 -0
- cubevis/bokeh/state/js/casaguijs-v0.0.6.0-b2.4.min.js +49 -0
- cubevis/bokeh/state/js/casalib-v0.0.1.min.js +1 -0
- cubevis/bokeh/tools/__init__.py +31 -0
- cubevis/bokeh/tools/_cbreset_tool.py +52 -0
- cubevis/bokeh/tools/_drag_tool.py +61 -0
- cubevis/bokeh/utils/__init__.py +35 -0
- cubevis/bokeh/utils/_axes_labels.py +94 -0
- cubevis/bokeh/utils/_svg_icon.py +136 -0
- cubevis/data/__init__.py +1 -0
- cubevis/data/casaimage/__init__.py +114 -0
- cubevis/data/measurement_set/__init__.py +7 -0
- cubevis/data/measurement_set/_ms_data.py +178 -0
- cubevis/data/measurement_set/processing_set/__init__.py +30 -0
- cubevis/data/measurement_set/processing_set/_ps_concat.py +98 -0
- cubevis/data/measurement_set/processing_set/_ps_coords.py +78 -0
- cubevis/data/measurement_set/processing_set/_ps_data.py +213 -0
- cubevis/data/measurement_set/processing_set/_ps_io.py +55 -0
- cubevis/data/measurement_set/processing_set/_ps_raster_data.py +154 -0
- cubevis/data/measurement_set/processing_set/_ps_select.py +91 -0
- cubevis/data/measurement_set/processing_set/_ps_stats.py +218 -0
- cubevis/data/measurement_set/processing_set/_xds_data.py +149 -0
- cubevis/plot/__init__.py +1 -0
- cubevis/plot/ms_plot/__init__.py +29 -0
- cubevis/plot/ms_plot/_ms_plot.py +242 -0
- cubevis/plot/ms_plot/_ms_plot_constants.py +22 -0
- cubevis/plot/ms_plot/_ms_plot_selectors.py +348 -0
- cubevis/plot/ms_plot/_raster_plot.py +292 -0
- cubevis/plot/ms_plot/_raster_plot_inputs.py +116 -0
- cubevis/plot/ms_plot/_xds_plot_axes.py +110 -0
- cubevis/private/__java__/xml-casa-assembly-1.86.jar +0 -0
- cubevis/private/_gclean.py +798 -0
- cubevis/private/casashell/createmask.py +332 -0
- cubevis/private/casashell/iclean.py +4432 -0
- cubevis/private/casatasks/__init__.py +140 -0
- cubevis/private/casatasks/createmask.py +86 -0
- cubevis/private/casatasks/createregion.py +83 -0
- cubevis/private/casatasks/iclean.py +1831 -0
- cubevis/readme.rst +16 -0
- cubevis/remote/__init__.py +10 -0
- cubevis/remote/_gclean.py +61 -0
- cubevis/remote/_local.py +287 -0
- cubevis/remote/_remote_kernel.py +80 -0
- cubevis/toolbox/__init__.py +32 -0
- cubevis/toolbox/_app_context.py +74 -0
- cubevis/toolbox/_cube.py +3457 -0
- cubevis/toolbox/_region_list.py +197 -0
- cubevis/utils/_ResourceManager.py +86 -0
- cubevis/utils/__init__.py +620 -0
- cubevis/utils/_contextmgrchain.py +84 -0
- cubevis/utils/_conversion.py +93 -0
- cubevis/utils/_copydoc.py +55 -0
- cubevis/utils/_docenum.py +25 -0
- cubevis/utils/_import_protected_module.py +35 -0
- cubevis/utils/_logging.py +85 -0
- cubevis/utils/_pkgs.py +77 -0
- cubevis/utils/_regions.py +40 -0
- cubevis/utils/_static.py +66 -0
- cubevis/utils/_tiles.py +167 -0
- cubevis-0.5.2.dist-info/METADATA +151 -0
- cubevis-0.5.2.dist-info/RECORD +132 -0
- cubevis-0.5.2.dist-info/WHEEL +4 -0
- cubevis-0.5.2.dist-info/licenses/LICENSE +504 -0
|
@@ -0,0 +1,258 @@
|
|
|
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
|
+
'''Implementation of a general purpose event manager between Python and
|
|
29
|
+
JavaScript via ``websockets``. This provides a mechanism like the
|
|
30
|
+
``ImagePipe``. The difference is that ``ImagePipe`` is tuned for use
|
|
31
|
+
with CASA images but ``DataPipe`` can have generic messages.'''
|
|
32
|
+
|
|
33
|
+
import inspect
|
|
34
|
+
import threading
|
|
35
|
+
import asyncio
|
|
36
|
+
import traceback
|
|
37
|
+
|
|
38
|
+
from bokeh.models.sources import DataSource
|
|
39
|
+
from bokeh.util.compiler import TypeScript
|
|
40
|
+
from bokeh.core.properties import Tuple, String, Int, Instance, Nullable
|
|
41
|
+
from bokeh.models.callbacks import Callback
|
|
42
|
+
|
|
43
|
+
from ...utils import serialize, deserialize
|
|
44
|
+
from ..state import casalib_url, cubevisjs_url
|
|
45
|
+
|
|
46
|
+
class DataPipe(DataSource):
|
|
47
|
+
"""This class allows for communication between Python and the JavaScript implementation
|
|
48
|
+
running in a browser. It allows Python code to send a message to JavaScript and register
|
|
49
|
+
a callback which will receive the result. JavaScript code can do the same to request
|
|
50
|
+
information from Python. Generally messages are expected to be dictionaries in the
|
|
51
|
+
Python domain and objects in the JavaScript domain. UUIDs are used to keep messages in
|
|
52
|
+
sync, and messages sent with the same UUID are queued until the currently pending
|
|
53
|
+
message reply is recieved. A class can use multipe UUIDs to control queuing behavior.
|
|
54
|
+
|
|
55
|
+
Attributes
|
|
56
|
+
----------
|
|
57
|
+
address: tuple of string and int
|
|
58
|
+
the string is the IP address for the network that should be used and the
|
|
59
|
+
integer is the port number, see ``cubevis.utils.find_ws_address``
|
|
60
|
+
init_script: JavaScript
|
|
61
|
+
this javascript is run when this DataPipe object is initialized. init_script
|
|
62
|
+
is used to run caller JavaScript which needs to be run at initialization time.
|
|
63
|
+
This is optional and does not need to be set.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
init_script = Nullable(Instance(Callback), help="""
|
|
67
|
+
JavaScript to be run during initialization of an instance of an DataPipe object.
|
|
68
|
+
""")
|
|
69
|
+
|
|
70
|
+
address = Tuple( String, Int, help="two integer sequence representing the address and port to use for the websocket" )
|
|
71
|
+
|
|
72
|
+
__javascript__ = [ casalib_url( ), cubevisjs_url( ) ]
|
|
73
|
+
|
|
74
|
+
def __init__( self, *args, abort=None, **kwargs ):
|
|
75
|
+
super( ).__init__( *args, **kwargs )
|
|
76
|
+
self.__send_queue = { }
|
|
77
|
+
self.__pending = { }
|
|
78
|
+
self.__incoming_callbacks = { }
|
|
79
|
+
self.__websocket = None
|
|
80
|
+
self.__lock = threading.Lock( )
|
|
81
|
+
self.__session = None
|
|
82
|
+
self.__abort = abort
|
|
83
|
+
|
|
84
|
+
if self.__abort is not None and not callable(self.__abort):
|
|
85
|
+
raise RuntimeError(f'abort function must be callable ({type(self.__abort)} is not)')
|
|
86
|
+
|
|
87
|
+
def __enqueue_send( self, ident, msg, callback ):
|
|
88
|
+
### it is assumed that this is called AFTER the lock has been aquired
|
|
89
|
+
if ident in self.__send_queue:
|
|
90
|
+
self.__send_queue[ident].insert(0, { 'cb': callback, 'msg': msg })
|
|
91
|
+
else:
|
|
92
|
+
self.__send_queue[ident] = [ { 'cb': callback, 'msg': msg } ]
|
|
93
|
+
def __dequeue_send( self, ident ):
|
|
94
|
+
### it is assumed that this is called AFTER the lock has been aquired
|
|
95
|
+
if ident in self.__send_queue and self.__send_queue[ident]:
|
|
96
|
+
return self.__send_queue[ident].pop( )
|
|
97
|
+
return None
|
|
98
|
+
async def __put_pending( self, ident, callback ):
|
|
99
|
+
### it is assumed that this is called AFTER the lock has been aquired
|
|
100
|
+
## info about request sent to javascript waits in this queue
|
|
101
|
+
## until the javascript reply is received...
|
|
102
|
+
if ident in self.__pending:
|
|
103
|
+
if self.__websocket is not None:
|
|
104
|
+
await self.__websocket.send( { 'id': '', 'message': 'queueing callback, but already one callback waiting', 'direction': 'error' } )
|
|
105
|
+
else:
|
|
106
|
+
self.__pending[ident] = callback
|
|
107
|
+
|
|
108
|
+
def __get_pending( self, ident ):
|
|
109
|
+
### it is assumed that this is called AFTER the lock has been aquired
|
|
110
|
+
## when reply arrives for a request to javascript
|
|
111
|
+
## the callback is retrieved from the waiting queue
|
|
112
|
+
if ident in self.__pending:
|
|
113
|
+
result = self.__pending[ident]
|
|
114
|
+
del self.__pending[ident]
|
|
115
|
+
return result
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
def register( self, ident, callback ):
|
|
119
|
+
"""Register a callback to handle all requests coming from JavaScript. The
|
|
120
|
+
callback will be called whenever a request arrives.
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
ident: string
|
|
125
|
+
UUID which is associated with the messages that should be delivered
|
|
126
|
+
to this callback
|
|
127
|
+
callback: (string) => dictionary
|
|
128
|
+
Callback which receives the message from Python as its sole parameter.
|
|
129
|
+
The return value of this callback is delivered to the JavaScript code
|
|
130
|
+
as the ''reply'' to to JavaScript in response to the ''request'' contained
|
|
131
|
+
in the message.
|
|
132
|
+
"""
|
|
133
|
+
with self.__lock:
|
|
134
|
+
self.__incoming_callbacks[ident] = callback
|
|
135
|
+
|
|
136
|
+
async def send( self, ident, message, callback ):
|
|
137
|
+
"""Send a `message` to JavaScript identified by `ident`. Once the reply is
|
|
138
|
+
received, the `callback` will be called with the reply message.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
ident: string
|
|
143
|
+
UUID to associate with this message. It is used to keep track of the callback
|
|
144
|
+
that should be called when the reply is received.
|
|
145
|
+
message: dictionary
|
|
146
|
+
This dictionary contains the request for the JavaScript code.
|
|
147
|
+
callback: (string) => void
|
|
148
|
+
Callback which receives the message that JavaScript generates in response to
|
|
149
|
+
the request contained in the `message` parameter.
|
|
150
|
+
"""
|
|
151
|
+
with self.__lock:
|
|
152
|
+
if self.__websocket is not None:
|
|
153
|
+
msg = { 'id': ident, 'message': message, 'direction': 'p2j' }
|
|
154
|
+
if ident in self.__pending:
|
|
155
|
+
self.__enqueue_send( ident, msg, callback )
|
|
156
|
+
else:
|
|
157
|
+
if ident in self.__send_queue and self.__send_queue[ident]:
|
|
158
|
+
self.__enqueue_send( ident, msg, callback )
|
|
159
|
+
existing = self.__dequeue_send(ident)
|
|
160
|
+
self.__put_pending(ident, existing['cb'])
|
|
161
|
+
await self.__websocket.send(serialize( existing['msg'] ))
|
|
162
|
+
else:
|
|
163
|
+
await self.__put_pending(ident, callback)
|
|
164
|
+
await self.__websocket.send(serialize( msg ))
|
|
165
|
+
|
|
166
|
+
async def process_messages( self, websocket ):
|
|
167
|
+
"""Process messages related to image display updates.
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
websocket: websocket object
|
|
172
|
+
Websocket serve function passes in the websocket object to use.
|
|
173
|
+
path:
|
|
174
|
+
Websocket serve provides a second parameter.
|
|
175
|
+
"""
|
|
176
|
+
try:
|
|
177
|
+
self.__websocket = websocket
|
|
178
|
+
async for message in websocket:
|
|
179
|
+
msg = deserialize(message)
|
|
180
|
+
if 'session' not in msg:
|
|
181
|
+
await self.__websocket.close( )
|
|
182
|
+
err = RuntimeError(f'session not in: {msg}')
|
|
183
|
+
if self.__abort is not None:
|
|
184
|
+
self.__abort( err )
|
|
185
|
+
else:
|
|
186
|
+
raise err
|
|
187
|
+
return
|
|
188
|
+
elif self.__session != None and self.__session != msg['session']:
|
|
189
|
+
await self.__websocket.close( )
|
|
190
|
+
err = RuntimeError(f"session corruption: {msg['session']} does not equal {self.__session}")
|
|
191
|
+
if self.__abort is not None:
|
|
192
|
+
self.__abort( err )
|
|
193
|
+
else:
|
|
194
|
+
raise err
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
with self.__lock:
|
|
198
|
+
###
|
|
199
|
+
### initialize session identifier
|
|
200
|
+
###
|
|
201
|
+
if self.__session == None:
|
|
202
|
+
self.__session = msg['session']
|
|
203
|
+
if msg['direction'] == 'p2j':
|
|
204
|
+
cb = self.__get_pending(msg['id'])
|
|
205
|
+
outgo = self.__dequeue_send(msg['id'])
|
|
206
|
+
if outgo is not None:
|
|
207
|
+
await websocket.send(serialize(outgo['msg']))
|
|
208
|
+
await self.__put_pending(msg['id'],outgo['cb'])
|
|
209
|
+
if cb is not None:
|
|
210
|
+
if inspect.isawaitable(cb):
|
|
211
|
+
await cb(msg['message'])
|
|
212
|
+
else:
|
|
213
|
+
cb(msg['message'])
|
|
214
|
+
elif msg['id'] != 'initialize':
|
|
215
|
+
###
|
|
216
|
+
### no cb/reply for initialization of session
|
|
217
|
+
###
|
|
218
|
+
if msg['id'] not in self.__incoming_callbacks:
|
|
219
|
+
raise RuntimeError(f'incoming js request with no callback: {msg}')
|
|
220
|
+
result = self.__incoming_callbacks[msg['id']](msg['message'])
|
|
221
|
+
if inspect.isawaitable(result):
|
|
222
|
+
try:
|
|
223
|
+
return_message = "an exception occurred in creating the respone"
|
|
224
|
+
return_message = await result
|
|
225
|
+
await self.__websocket.send( serialize( { 'id': msg['id'],
|
|
226
|
+
'message': return_message,
|
|
227
|
+
'direction': msg['direction'] } ) )
|
|
228
|
+
except Exception as e:
|
|
229
|
+
trace_back = traceback.format_exc().replace('\n','\n ')
|
|
230
|
+
print('************************************************************************************************************************')
|
|
231
|
+
print( f'''EXCEPTION ENCOUNTERED: {repr(e)}''' )
|
|
232
|
+
print( f''' MESSAGE: {repr(msg)}''' )
|
|
233
|
+
print( f''' STACK TRACE: {trace_back}''' )
|
|
234
|
+
print('************************************************************************************************************************')
|
|
235
|
+
await self.__websocket.send( serialize( { 'id': msg['id'],
|
|
236
|
+
'message': { 'error': "exception encountered",
|
|
237
|
+
'errant': str(return_message),
|
|
238
|
+
'exception': repr(e) },
|
|
239
|
+
'direction': str(msg['direction']) } ) )
|
|
240
|
+
else:
|
|
241
|
+
try:
|
|
242
|
+
await self.__websocket.send( serialize( { 'id': msg['id'],
|
|
243
|
+
'message': result,
|
|
244
|
+
'direction': msg['direction'] } ) )
|
|
245
|
+
except Exception as e:
|
|
246
|
+
trace_back = traceback.format_exc().replace('\n','\n ')
|
|
247
|
+
print('************************************************************************************************************************')
|
|
248
|
+
print( f'''EXCEPTION ENCOUNTERED: {repr(e)}''' )
|
|
249
|
+
print( f''' MESSAGE: {repr(msg)}''' )
|
|
250
|
+
print( f''' STACK TRACE: {trace_back}''' )
|
|
251
|
+
print('************************************************************************************************************************')
|
|
252
|
+
await self.__websocket.send( serialize( { 'id': msg['id'],
|
|
253
|
+
'message': { 'error': "exception encountered",
|
|
254
|
+
'errant': str(result),
|
|
255
|
+
'exception': repr(e) },
|
|
256
|
+
'direction': str(msg['direction']) } ) )
|
|
257
|
+
finally:
|
|
258
|
+
self.__websocket = None
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
########################################################################
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021
|
|
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
|
+
'''Specialization of the Bokeh ``DataSource`` for image cube data.'''
|
|
29
|
+
|
|
30
|
+
import numpy as np
|
|
31
|
+
from bokeh.plotting import ColumnDataSource
|
|
32
|
+
from bokeh.util.compiler import TypeScript
|
|
33
|
+
from bokeh.core.properties import Instance, Tuple, Int, Nullable
|
|
34
|
+
from bokeh.models.callbacks import Callback
|
|
35
|
+
from ._image_pipe import ImagePipe
|
|
36
|
+
from ..state import casalib_url, cubevisjs_url
|
|
37
|
+
|
|
38
|
+
class ImageDataSource(ColumnDataSource):
|
|
39
|
+
"""Implementation of a ``ColumnDataSource`` customized for planes from
|
|
40
|
+
`CASA`/`CNGI` image cubes. This is designed to use an `ImagePipe` to
|
|
41
|
+
update the image channel/plane displayed in a browser, app or notebook
|
|
42
|
+
with `bokeh`.
|
|
43
|
+
|
|
44
|
+
Attributes
|
|
45
|
+
----------
|
|
46
|
+
image_source: ImagePipe
|
|
47
|
+
the conduit for updating the channel/plane from the image cube
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
init_script = Nullable(Instance(Callback), help="""
|
|
51
|
+
JavaScript to be run during initialization of an instance of an ImageDataSource object.
|
|
52
|
+
""")
|
|
53
|
+
|
|
54
|
+
image_source = Instance(ImagePipe)
|
|
55
|
+
_mask_contour_source = Nullable(Instance(ColumnDataSource), help='''
|
|
56
|
+
Data source for updating contour polygons. This data source is directly updated
|
|
57
|
+
inside JavaScript.
|
|
58
|
+
''')
|
|
59
|
+
num_chans = Tuple( Int, Int, help="[ num-stokes-planes, num-channels ]" )
|
|
60
|
+
cur_chan = Tuple( Int, Int, help="[ num-stokes-planes, num-channels ]" )
|
|
61
|
+
|
|
62
|
+
__javascript__ = [ casalib_url( ), cubevisjs_url( ) ]
|
|
63
|
+
|
|
64
|
+
def __init__( self, *args, **kwargs ):
|
|
65
|
+
super( ).__init__( *args, **kwargs )
|
|
66
|
+
mask0 = self.image_source.mask0( [0,0] )
|
|
67
|
+
self.data = { 'img': [ self.image_source.channel( [0,0], np.uint8 ) ],
|
|
68
|
+
'msk0': [ mask0 if mask0 is not None else None ] }
|
|
69
|
+
if self.image_source.have_mask( ):
|
|
70
|
+
self.data['msk'] = [ self.image_source.mask( [0,0] ) ]
|
|
71
|
+
self.num_chans = list(self.image_source.shape[-2:])
|
|
72
|
+
self.cur_chan = [ 0, 0 ]
|
|
73
|
+
|
|
74
|
+
def mask_contour_source( self, data ):
|
|
75
|
+
if not self._mask_contour_source:
|
|
76
|
+
self._mask_contour_source = ColumnDataSource( data=data )
|
|
77
|
+
return self._mask_contour_source
|
|
78
|
+
|
|
79
|
+
def pixel_value( self, chan, index ):
|
|
80
|
+
return self.image_source.pixel_value( chan, index )
|
|
81
|
+
|
|
82
|
+
def stokes_labels( self ):
|
|
83
|
+
return self.image_source.stokes_labels( )
|