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
cubevis/readme.rst ADDED
@@ -0,0 +1,16 @@
1
+ Visualization for the CASA package
2
+ ==================================
3
+
4
+ This package includes the visualization elements of the CASA package for
5
+ processing data collected from radio telescopes. More information about CASA
6
+ can be found on the `CASA home page <https://casa.nrao.edu/>`_.
7
+
8
+ cubevis is based on `Bokeh <https://bokeh.org/>`_. It provides visualization
9
+ elements for three different interaction settings:
10
+
11
+ * Python command line with display to a web browser
12
+ * Jupyter notebook
13
+ * `Electron <https://www.electronjs.org/>`_ app
14
+
15
+ However not all elements support all interaction settings.
16
+
@@ -0,0 +1,10 @@
1
+ '''Remote Jupyter kernel execution capabilities. These are the dependencies that **may** be installed::
2
+
3
+ Successfully installed Send2Trash-1.8.2 anyio-3.7.1 argon2-cffi-23.1.0 argon2-cffi-bindings-21.2.0 babel-2.14.0 beautifulsoup4-4.12.3 bleach-6.1.0 cffi-1.16.0 charset-normalizer-3.3.2 comm-0.2.1 debugpy-1.8.1 defusedxml-0.7.1 deprecation-2.1.0 fastjsonschema-2.19.1 ipykernel-6.29.3 ipython-genutils-0.2.0 json5-0.9.20 jsonschema-4.21.1 jsonschema-specifications-2023.12.1 jupyter-client-6.1.12 jupyter-core-5.7.1 jupyter-packaging-0.12.3 jupyter-server-1.24.0 jupyterlab-3.0.18 jupyterlab-pygments-0.3.0 jupyterlab-server-2.25.3 mistune-3.0.2 nbclassic-0.5.6 nbclient-0.9.0 nbconvert-7.16.2 nbformat-5.9.2 nest-asyncio-1.6.0 notebook-shim-0.2.4 pandocfilters-1.5.1 platformdirs-4.2.0 prometheus-client-0.20.0 psutil-5.9.8 pycparser-2.21 pyzmq-25.1.2 referencing-0.33.0 requests-2.31.0 rpds-py-0.18.0 sniffio-1.3.1 soupsieve-2.5 ssh-ipykernel-interrupt-1.1.2 ssh_ipykernel-1.2.3 terminado-0.18.0 tinycss2-1.2.1 tomlkit-0.12.4 tornado-6.2 urllib3-2.2.1 webencodings-0.5.1 websocket-client-1.7.0
4
+
5
+ when installing ssh_ipykernel.
6
+ '''
7
+
8
+ from ._local import *
9
+ from ._remote_kernel import TestProc
10
+ from ._gclean import gclean_local
@@ -0,0 +1,61 @@
1
+ ### Remote gclean execution
2
+ import os
3
+ import sys
4
+ sys.path.insert(0, os.path.abspath('../'))
5
+ ##>>>import casaremote
6
+
7
+ # Remote functions needed
8
+ # finalize, update(), reset
9
+
10
+ # TODO: How to handle dictionaries? """?
11
+ def kwarg_string(*args, **kwargs):
12
+ ret = []
13
+ for arg in args:
14
+ if isinstance(arg, str):
15
+ ret.append(f"'{arg}'")
16
+ else:
17
+ ret.append(f"{arg}")
18
+ for key, value in kwargs.items():
19
+ if isinstance(value, str):
20
+ value = f"'{value}'"
21
+ ret.append(f"{key}={value}")
22
+ return ", ".join(ret)
23
+
24
+ class gclean_local:
25
+
26
+ # def _tclean( self, *args, **kwargs ):
27
+ # return
28
+
29
+ # def _deconvolve( self, *args, **kwargs ):
30
+ # return
31
+ # # return ( *args, **kwargs )
32
+
33
+ def cmds( self ):
34
+ cmd = casaremote.create_cmd_json("self.gclean_cmds")
35
+ gclean_id = casaremote.exe_remote_subproc(self.kc, cmd)
36
+ ret = casaremote.get_subproc_return_val(self.kc, gclean_id)
37
+ return ret
38
+
39
+ def __init__(self, kc, *args, **kwargs):
40
+ self.kc = kc
41
+ print("In remote clean init \n")
42
+ cmd = "t._clean = gclean(" + kwarg_string(*args, **kwargs) + ")"
43
+ casaremote.exe_and_print(kc, cmd)
44
+
45
+ def __next__( self ):
46
+ cmd = casaremote.create_cmd_json("self.gclean_next")
47
+ gclean_id = casaremote.exe_remote_subproc(self.kc, cmd)
48
+ ret = casaremote.get_subproc_return_val(self.kc, gclean_id)
49
+ return ret
50
+
51
+ def restore(self):
52
+ cmd = casaremote.create_cmd_json("self.gclean_restore")
53
+ gclean_id = casaremote.exe_remote_subproc(self.kc, cmd)
54
+ ret = casaremote.get_subproc_return_val(self.kc, gclean_id)
55
+ return ret
56
+
57
+ def has_next(self):
58
+ cmd = casaremote.create_cmd_json("self.gclean_has_next")
59
+ gclean_id = casaremote.exe_remote_subproc(self.kc, cmd)
60
+ ret = casaremote.get_subproc_return_val(self.kc, gclean_id)
61
+ return ret
@@ -0,0 +1,287 @@
1
+ '''Functions for local execution to create, tunnel, and connect to remote Jupyter kernels.'''
2
+ import jupyter_client
3
+ ##>>>from ssh_ipykernel.kernel import SshKernel
4
+ ##>>>from ipykernel import get_connection_info
5
+ from queue import Empty
6
+ import json
7
+ import os
8
+ import asyncio
9
+ import websockets
10
+ import time
11
+ from uuid import uuid4
12
+ from ast import literal_eval
13
+
14
+ '''Build ssh tunnel forwarding pattern and return list of arguments'''
15
+ def build_ssh_tunnel(host, ports, ip="localhost", verbose=False, quiet=True, ssh_config=""):
16
+ # Build ssh command with all flags and tunnels
17
+ # TODO: Handle non-matching remote and local ports
18
+ ssh_tunnels = []
19
+ for port in ports:
20
+ ssh_tunnels += [
21
+ "-L",
22
+ "{local_port}:{ip}:{remote_port}".format(
23
+ local_port=port,
24
+ ip=ip,
25
+ remote_port=port,
26
+ ),
27
+ ]
28
+
29
+ if quiet:
30
+ kernel_args = ["-q"]
31
+ elif verbose:
32
+ kernel_args = ["-v"]
33
+ else:
34
+ kernel_args = []
35
+
36
+ kernel_args += ["-f", "-N", "-t"]
37
+ if ssh_config != "":
38
+ kernel_args += ["-F", str(ssh_config)]
39
+
40
+ # Build rest of ssh command and redirect output
41
+ kernel_args += ssh_tunnels + [host]
42
+ ssh_tunnel_cmd = "ssh " + " ".join(kernel_args) + " &>/dev/null"
43
+
44
+ return ssh_tunnel_cmd
45
+
46
+
47
+ '''Tunnel the necessary ports to access a remote jupyter kernel locally'''
48
+ def tunnel_remote_kernel(connect_info_file, host):
49
+ connect_info_file_local = jupyter_client.find_connection_file(filename=connect_info_file)
50
+ connect_info_local = json.loads(get_connection_info(connect_info_file_local))
51
+
52
+ # Get ports to tunnel
53
+ tunnel_ports = []
54
+ for port_name in ['shell_port', 'iopub_port', 'stdin_port', 'control_port', 'hb_port']:
55
+ tunnel_ports.append(connect_info_local[port_name])
56
+
57
+ ssh_tunnel_cmd = build_ssh_tunnel(host, tunnel_ports)
58
+
59
+ # # TODO: Check that command was successful
60
+ os.system(ssh_tunnel_cmd)
61
+
62
+ '''Start a jupyter console connected to a remote kernel session in the current process'''
63
+ def connect_console(connect_info_file):
64
+ console_start_cmd = "jupyter-console --existing=" + connect_info_file
65
+ print("\nConsole start command:\n", console_start_cmd)
66
+ os.system(console_start_cmd)
67
+
68
+ '''Start a remote jupyter kernel and return the full path to the connection info file'''
69
+ def start_remote_kernel(kernel_name, sudo=False, timeout=5, env="", backchannel_port=5667):
70
+ # Get details of selected kernel (if exists)
71
+ kern_spec_man = jupyter_client.kernelspec.KernelSpecManager()
72
+ # TODO: Check that kernel name exists
73
+ ic_kernel = kern_spec_man.get_kernel_spec(kernel_name)
74
+ connect_info_file_local = ""
75
+
76
+ connect_info_file_local = jupyter_client.find_connection_file()
77
+ connect_info_local = json.loads(get_connection_info(connect_info_file_local))
78
+
79
+ # Get remote connection info
80
+ kernel_args = ic_kernel.argv
81
+ host = kernel_args[kernel_args.index('--host') + 1]
82
+ remote_python_path = kernel_args[kernel_args.index('--python') + 1]
83
+
84
+ # Create remote kernel information structure
85
+ kernel = SshKernel(host, connect_info_local, remote_python_path, sudo, timeout, env)
86
+ kernel.create_remote_connection_info()
87
+
88
+ # Build remote kernel launch command
89
+ sudo = "sudo " if kernel.sudo else ""
90
+ if kernel.env is not None:
91
+ env = " ".join(kernel.env)
92
+ kernel_start_cmd = "{sudo} {env} {python} -m ipykernel_launcher -f {fname}".format(
93
+ sudo=sudo, env=env, python=kernel.python_full_path, fname=kernel.fname
94
+ )
95
+
96
+ # Build ssh command with all flags and tunnels
97
+ tunnel_ports = [backchannel_port]
98
+ for port_name in kernel.remote_ports.keys():
99
+ tunnel_ports += [kernel.remote_ports[port_name]]
100
+
101
+ ssh_tunnel_cmd = build_ssh_tunnel(host,
102
+ tunnel_ports,
103
+ verbose=kernel.verbose,
104
+ quiet=kernel.quiet,
105
+ ssh_config=kernel.ssh_config)
106
+ # Create tunnels
107
+ print("\nSSH Tunnel Command: \n", ssh_tunnel_cmd)
108
+ ret = os.system(ssh_tunnel_cmd)
109
+ # return kernel
110
+
111
+ # Local connection info file location
112
+ connect_info_file_local_full = os.path.abspath(connect_info_file_local)
113
+
114
+ # Create and run command to start remote jupyter kernel
115
+ remote_kernel_start_command = 'ssh {host} "nohup {cmd} >/dev/null 2>&1 &" &>/dev/null'.format(host=kernel.host, cmd=kernel_start_cmd)
116
+ print("\nRemote Kernel Start Command: \n", remote_kernel_start_command)
117
+ os.system(remote_kernel_start_command)
118
+
119
+ # Copy the remote kernel connection file to local machine
120
+ scp_cmd = "scp {host}:{fname} . &>/dev/null".format(host=kernel.host, fname=kernel.fname)
121
+ print("\nSCP Command:\n", scp_cmd)
122
+ os.system(scp_cmd)
123
+ connect_info_file_local_full = os.path.abspath(kernel.fname[5:])
124
+
125
+ tunnel_remote_kernel(connect_info_file_local_full, kernel.host)
126
+
127
+ # Print command to attach
128
+ console_start_cmd = "jupyter-console --existing=" + connect_info_file_local_full
129
+ print("\nConsole start command:\n", console_start_cmd)
130
+ os.system(ssh_tunnel_cmd)
131
+
132
+ return connect_info_file_local_full
133
+
134
+
135
+ '''Connect to a remote Jupyter Kernel'''
136
+ def connect_remote(connect_info_file, host):
137
+ kc = jupyter_client.BlockingKernelClient(connection_file = connect_info_file)
138
+ kc.load_connection_file()
139
+ # Start channels if connecting for the first time
140
+ if(not kc.channels_running):
141
+ kc.start_channels()
142
+
143
+ # Unsure why this is the case
144
+ try:
145
+ kc.get_iopub_msg(timeout=1)
146
+ except Empty:
147
+ print("Empty IOPub Message Queue...")
148
+
149
+ tunnel_remote_kernel(kc.connection_file, host)
150
+ return kc
151
+
152
+ '''Print the waiting content of messages on the iopub channel.
153
+ Kernel responds to execution requests with potentially many responses.'''
154
+ def get_io_msgs(kc):
155
+ state='busy'
156
+ data={}
157
+ while state!='idle' and kc.is_alive():
158
+ try:
159
+ msg=kc.get_iopub_msg(timeout=1)
160
+ # print(msg)
161
+ if not 'content' in msg:
162
+ continue
163
+ content = msg['content']
164
+ print(content)
165
+ # pretty_print_jupyter(content)
166
+ if 'data' in content:
167
+ data=content['data']
168
+ if 'execution_state' in content:
169
+ state=content['execution_state']
170
+ except Empty:
171
+ pass
172
+
173
+ '''Helper function for testing'''
174
+ def exe_and_print(kc, cmd):
175
+ print("\nExecuting: {cmd}".format(cmd=cmd))
176
+ kc.execute(cmd)
177
+ get_io_msgs(kc)
178
+
179
+ '''Execute a command in a subprocess started from a remote Jupyter kernel'''
180
+ def exe_in_subproc(kc, cmd):
181
+ # Generate a UUID associated with this command and subprocess instance
182
+ id = str(uuid4())
183
+ # Put UUID into command JSON
184
+ cmd['id'] = id
185
+ exe_and_print("run_subproc_cmd('" + json.dumps(cmd) + "')")
186
+ return id
187
+
188
+ '''Helper function to send a remote request to a kernel's backchannel websocket'''
189
+ async def send_remote_request(request, backchannel_port):
190
+ uri = "ws://localhost:"+str(backchannel_port)
191
+ print("Remote request URI: " + uri)
192
+ async with websockets.connect(uri, ping_interval=None) as websocket:
193
+ await websocket.send(request)
194
+ response = await websocket.recv()
195
+ return response
196
+
197
+ '''Send a remote request to a kernel's backchannel websocket'''
198
+ def remote_request(request, port=5667):
199
+ print("Sending remote request to port " + str(port) + " with request " + str(request))
200
+ response = asyncio.get_event_loop().run_until_complete(send_remote_request(request, port))
201
+ print(f"Response: {response}")
202
+
203
+ '''Convert arguments to an explicit, stringified representation for remote execution'''
204
+ def kwarg_string(*args, **kwargs):
205
+ ret = []
206
+ for arg in args:
207
+ if isinstance(arg, str):
208
+ ret.append(f"'{arg}'")
209
+ else:
210
+ ret.append(f"{arg}")
211
+ for key, value in kwargs.items():
212
+ if isinstance(value, str):
213
+ value = f"'{value}'"
214
+ ret.append(f"{key}={value}")
215
+ return ", ".join(ret)
216
+
217
+ '''Create a json representation of a remote subprocess command to execute'''
218
+ def create_cmd_json(cmd, *args, **kwargs):
219
+ json_cmd = {}
220
+ json_cmd['id'] = str(uuid4())
221
+ json_cmd['cmd'] = cmd
222
+ json_cmd['args'] = args
223
+ json_cmd['kwargs'] = kwargs
224
+ return json.dumps(json_cmd)
225
+
226
+ '''Execute a remote command in a subprocess'''
227
+ def exe_remote_subproc(kernel_client, json_cmd):
228
+ json_cmd = json.loads(json_cmd)
229
+ id = str(uuid4())
230
+ json_cmd['id'] = id
231
+
232
+ # TODO: Properly check output of subproc status
233
+ # exe_and_print(kernel_client, "t.subproc_is_free()")
234
+ # TODO: Figure out how to handle TestProc selection/naming/referencing
235
+ exe_and_print(kernel_client, "t.add_subproc_cmd('"+str(json.dumps(json_cmd))+"')")
236
+ return id
237
+
238
+ '''Properly print kernel-formatted'''
239
+ def pretty_print_jupyter(raw_out):
240
+ if 'text' in raw_out:
241
+ print(raw_out['text'])
242
+
243
+ if 'data' in raw_out:
244
+ data = raw_out['data']
245
+ if 'text/plain' in data:
246
+ print(data['text/plain'])
247
+
248
+ '''Get the returned value from remote kernel output'''
249
+ def get_return_val(kc):
250
+ state='busy'
251
+ data={}
252
+ ret = None
253
+ while state!='idle' and kc.is_alive():
254
+ try:
255
+ msg=kc.get_iopub_msg(timeout=1)
256
+ # print(msg)
257
+ if not 'content' in msg:
258
+ continue
259
+ content = msg['content']
260
+ print(content)
261
+ # pretty_print_jupyter(content)
262
+ if 'data' in content:
263
+ data=content['data']
264
+ ret = literal_eval(data['text/plain'])
265
+ if 'execution_state' in content:
266
+ state=content['execution_state']
267
+ except Empty:
268
+ pass
269
+ return ret
270
+
271
+ '''Get the returned value from a remote subprocess'''
272
+ def get_subproc_return_val(kc, id):
273
+ wait_done(kc, id)
274
+ kc.execute("t.get_return_val('" + id + "')")
275
+ ret = get_return_val(kc)
276
+ print("Return value: " + str(ret))
277
+ return ret
278
+
279
+ '''Wait for the remote subprocess identified by 'id' to be finished'''
280
+ def wait_done(kc, id, interval=1):
281
+ is_running = 1
282
+
283
+ while is_running:
284
+ kc.execute("t.is_running('" + id + "')")
285
+ is_running = get_return_val(kc)
286
+ print("Ret in wait done: " + str(is_running))
287
+ time.sleep(interval)
@@ -0,0 +1,80 @@
1
+ # Functions for long-running process running in a remote jupyter kernel
2
+
3
+ from multiprocessing import Process, Pipe
4
+ import sys
5
+ import os
6
+ import json
7
+ from time import sleep
8
+ from websockets.server import serve
9
+ import asyncio
10
+
11
+ class TestProc:
12
+ _backchannel_task = None # Maintain strong reference to backchannel task
13
+
14
+ def __init__(self, port, loop):
15
+ self.loop = loop
16
+ self.port = port
17
+ self.subproc_cmd_pend = []
18
+
19
+ async def echo(self, websocket):
20
+ async for message in websocket:
21
+ response = json.dumps({"request": message, "response": eval("self."+message)})
22
+ await websocket.send(response)
23
+
24
+ async def backchannel_process(self, port):
25
+ async with serve(self.echo, "localhost", port, ping_interval=None):
26
+ await asyncio.Future() # run forever
27
+
28
+ def start_backchannel(self):
29
+ assert self.loop.is_running()
30
+ self._backchannel_task = asyncio.create_task(self.backchannel_process(self.port))
31
+ return
32
+
33
+ '''Check subprocess command to see if it's free (1) or busy (0)'''
34
+ def subproc_is_free(self):
35
+ if len(self.subproc_cmd_pend) == 0:
36
+ return 1
37
+
38
+ if any(process[1].is_alive() for process in self.subproc_cmd_pend):
39
+ print("Busy")
40
+ sleep(1)
41
+ return 0
42
+ else:
43
+ print('All processes done')
44
+ return 1
45
+
46
+ def add_subproc_cmd(self, cmd_json_str):
47
+ # Check that a subprocess isn't currently running
48
+ if not self.subproc_is_free():
49
+ print("Error: Subprocess is still running. Cannot start new process.")
50
+ return -1
51
+
52
+ print(cmd_json_str)
53
+ cmd_json = json.loads(cmd_json_str)
54
+ parent_conn, child_conn = Pipe()
55
+ p = Process(target=eval(cmd_json['cmd']),
56
+ args=(child_conn,) + tuple(cmd_json['args']),
57
+ kwargs=cmd_json['kwargs'])
58
+
59
+ subproc_details = (cmd_json['id'], p, parent_conn)
60
+ self.subproc_cmd_pend.append(subproc_details)
61
+ p.start()
62
+ return
63
+
64
+ # Return the requested process' (identified by the UUID) return value
65
+ def get_return_val(self, id):
66
+ # Check that id is valid
67
+ for process in self.subproc_cmd_pend:
68
+ if id == process[0]:
69
+ print("placeholder")
70
+ # Check that process is done
71
+ # TODO
72
+
73
+ # Check parent connection for waiting message
74
+ # ret_obj = process[3].recv() ???
75
+
76
+ # Check that ret_obj valid and that didn't timeout
77
+ # return ret_obj
78
+
79
+ # Else return ID not found
80
+ return
@@ -0,0 +1,32 @@
1
+ ########################################################################
2
+ #
3
+ # Copyright (C) 2022,2024,2025
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
+ '''Common tools used in creating applications.'''
29
+
30
+ from ._cube import CubeMask
31
+ from ._app_context import AppContext
32
+ from ._region_list import RegionList
@@ -0,0 +1,74 @@
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
+ from cubevis.bokeh.state import initialize_bokeh
29
+ from tempfile import TemporaryDirectory
30
+ from bokeh.io import output_file
31
+ from os.path import join
32
+ import unicodedata
33
+ import re
34
+
35
+ class AppContext:
36
+
37
+ def _slugify(self, value, allow_unicode=False):
38
+ """
39
+ Taken from https://github.com/django/django/blob/master/django/utils/text.py
40
+ Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated
41
+ dashes to single dashes. Remove characters that aren't alphanumerics,
42
+ underscores, or hyphens. Convert to lowercase. Also strip leading and
43
+ trailing whitespace, dashes, and underscores.
44
+ https://stackoverflow.com/a/295466/2903943
45
+ """
46
+ value = str(value)
47
+ if allow_unicode:
48
+ value = unicodedata.normalize('NFKC', value)
49
+ else:
50
+ value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
51
+ value = re.sub(r'[^\w\s-]', '', value.lower())
52
+ return re.sub(r'[-\s]+', '-', value).strip('-_')
53
+
54
+ def __init__( self, title, prefix=None, init_bokeh=True ):
55
+
56
+ ###
57
+ ### Setup up Bokeh paths, inject cubevis libraries into Bokeh HTML output
58
+ ###
59
+ if init_bokeh:
60
+ initialize_bokeh( )
61
+
62
+ if prefix is None:
63
+ ## create a prefix from the title
64
+ prefix = self._slugify(title)[:10]
65
+ self.__workdir = TemporaryDirectory(prefix=prefix)
66
+ self.__htmlpath = join( self.__workdir.name, f'''{self._slugify(title)}.html''' )
67
+ output_file( self.__htmlpath, title=title )
68
+
69
+ def __del__( self ):
70
+ ### remove work directory and its contents
71
+ self.__workdir.cleanup( )
72
+
73
+ def workdir( self ):
74
+ return self.__workdir.name