cubevis 1.0.0__py3-none-any.whl → 1.0.3__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/__init__.py +13 -24
- cubevis/__js__/bokeh-3.6/cubevisjs.min.js +64 -0
- cubevis/__js__/{cubevisjs.min.js → bokeh-3.7/cubevisjs.min.js} +3 -3
- cubevis/__js__/bokeh-3.8/cubevisjs.min.js +64 -0
- cubevis/__js__/casalib.min.js +1 -1
- cubevis/__version__.py +1 -1
- cubevis/bokeh/__init__.py +16 -0
- cubevis/bokeh/annotations/_ev_poly_annotation.py +2 -1
- cubevis/bokeh/format/_wcs_ticks.py +6 -2
- cubevis/bokeh/models/__init__.py +1 -0
- cubevis/bokeh/models/_showable.py +352 -0
- cubevis/bokeh/models/_tip.py +2 -1
- cubevis/bokeh/models/_tip_button.py +2 -3
- cubevis/bokeh/sources/_data_pipe.py +6 -2
- cubevis/bokeh/sources/_image_data_source.py +6 -2
- cubevis/bokeh/sources/_image_pipe.py +4 -1
- cubevis/bokeh/sources/_spectra_data_source.py +6 -3
- cubevis/bokeh/sources/_updatable_data_source.py +6 -2
- cubevis/bokeh/state/__init__.py +4 -3
- cubevis/bokeh/state/_current.py +34 -0
- cubevis/bokeh/state/_initialize.py +282 -116
- cubevis/bokeh/state/_javascript.py +95 -21
- cubevis/bokeh/tools/_cbreset_tool.py +2 -1
- cubevis/bokeh/tools/_drag_tool.py +2 -1
- cubevis/bokeh/utils/__init__.py +0 -1
- cubevis/exe/_setting.py +1 -0
- cubevis/private/apps/__init__.py +4 -2
- cubevis/private/apps/_interactiveclean.mustache +6 -2
- cubevis/private/apps/_interactiveclean.py +6 -2
- cubevis/private/apps/_interactivecleanjpy.mustache +112 -0
- cubevis/private/apps/_interactivecleanjpy.py +1874 -0
- cubevis/private/casatasks/__init__.py +1 -0
- cubevis/private/casatasks/icleanjpy.py +1831 -0
- cubevis/toolbox/_app_context.py +5 -9
- cubevis/toolbox/_cube.py +6 -2
- cubevis/toolbox/_interactive_clean_ui.mustache +20 -31
- cubevis/toolbox/_interactive_clean_ui.py +20 -31
- cubevis/utils/__init__.py +120 -18
- cubevis/utils/_git.py +36 -0
- cubevis/utils/_jupyter.py +12 -0
- {cubevis-1.0.0.dist-info → cubevis-1.0.3.dist-info}/METADATA +3 -3
- {cubevis-1.0.0.dist-info → cubevis-1.0.3.dist-info}/RECORD +44 -39
- cubevis/__js__/bokeh-3.6.1.min.js +0 -728
- cubevis/__js__/bokeh-tables-3.6.1.min.js +0 -119
- cubevis/__js__/bokeh-widgets-3.6.1.min.js +0 -141
- {cubevis-1.0.0.dist-info → cubevis-1.0.3.dist-info}/WHEEL +0 -0
- {cubevis-1.0.0.dist-info → cubevis-1.0.3.dist-info}/licenses/LICENSE +0 -0
cubevis/toolbox/_app_context.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
########################################################################
|
|
2
2
|
#
|
|
3
|
-
# Copyright (C) 2024
|
|
3
|
+
# Copyright (C) 2024,2025
|
|
4
4
|
# Associated Universities, Inc. Washington DC, USA.
|
|
5
5
|
#
|
|
6
6
|
# This script is free software; you can redistribute it and/or modify it
|
|
@@ -25,13 +25,14 @@
|
|
|
25
25
|
# Charlottesville, VA 22903-2475 USA
|
|
26
26
|
#
|
|
27
27
|
########################################################################
|
|
28
|
-
from cubevis.bokeh.state import initialize_bokeh
|
|
29
28
|
from tempfile import TemporaryDirectory
|
|
30
29
|
from bokeh.io import output_file
|
|
31
30
|
from os.path import join
|
|
32
31
|
import unicodedata
|
|
33
32
|
import re
|
|
34
33
|
|
|
34
|
+
from ..utils import is_interactive_jupyter
|
|
35
|
+
|
|
35
36
|
class AppContext:
|
|
36
37
|
|
|
37
38
|
def _slugify(self, value, allow_unicode=False):
|
|
@@ -53,18 +54,13 @@ class AppContext:
|
|
|
53
54
|
|
|
54
55
|
def __init__( self, title, prefix=None, init_bokeh=True ):
|
|
55
56
|
|
|
56
|
-
###
|
|
57
|
-
### Setup up Bokeh paths, inject cubevis libraries into Bokeh HTML output
|
|
58
|
-
###
|
|
59
|
-
if init_bokeh:
|
|
60
|
-
initialize_bokeh( )
|
|
61
|
-
|
|
62
57
|
if prefix is None:
|
|
63
58
|
## create a prefix from the title
|
|
64
59
|
prefix = self._slugify(title)[:10]
|
|
65
60
|
self.__workdir = TemporaryDirectory(prefix=prefix)
|
|
66
61
|
self.__htmlpath = join( self.__workdir.name, f'''{self._slugify(title)}.html''' )
|
|
67
|
-
|
|
62
|
+
if not is_interactive_jupyter( ):
|
|
63
|
+
output_file( self.__htmlpath, title=title )
|
|
68
64
|
|
|
69
65
|
def __del__( self ):
|
|
70
66
|
### remove work directory and its contents
|
cubevis/toolbox/_cube.py
CHANGED
|
@@ -100,7 +100,7 @@ class CubeMask:
|
|
|
100
100
|
self.COUNT = 1
|
|
101
101
|
self.CCOUNT = 1
|
|
102
102
|
|
|
103
|
-
self._is_notebook = is_interactive_jupyter()
|
|
103
|
+
##self._is_notebook = is_interactive_jupyter()
|
|
104
104
|
#self._color = '#00FF00' # anti-green user feedback (issue #40 2024-05-02 13:08:32)
|
|
105
105
|
self._region_style=dict( fill_alpha=0, hover_fill_alpha=0.3,
|
|
106
106
|
fill_color='white', hover_fill_color='white',
|
|
@@ -1505,7 +1505,11 @@ class CubeMask:
|
|
|
1505
1505
|
function done_close_window( msg ) {
|
|
1506
1506
|
if ( msg.result === 'stopped' ) {""" +
|
|
1507
1507
|
# Don't close tab if running in a jupyter notebook
|
|
1508
|
-
("""console.log("Running in jupyter notebook. Not closing window.")""" if self._is_notebook else
|
|
1508
|
+
##("""console.log("Running in jupyter notebook. Not closing window.")""" if self._is_notebook else
|
|
1509
|
+
## """console.log("Running from script/terminal. Closing window.")
|
|
1510
|
+
## window.close()"""
|
|
1511
|
+
##) +
|
|
1512
|
+
("""console.log("Running in jupyter notebook. Not closing window.")""" if is_interactive_jupyter( ) else
|
|
1509
1513
|
"""console.log("Running from script/terminal. Closing window.")
|
|
1510
1514
|
window.close()"""
|
|
1511
1515
|
) +
|
|
@@ -29,15 +29,6 @@
|
|
|
29
29
|
'''implementation of the ``InteractiveCleanUI`` application for interactive control
|
|
30
30
|
of tclean'''
|
|
31
31
|
|
|
32
|
-
###
|
|
33
|
-
### Useful for debugging
|
|
34
|
-
###
|
|
35
|
-
###from cubevis.bokeh.state import initialize_bokeh
|
|
36
|
-
###initialize_bokeh( bokehjs_subst=".../bokeh-3.2.2.js" )
|
|
37
|
-
###
|
|
38
|
-
|
|
39
|
-
from pprint import pprint
|
|
40
|
-
|
|
41
32
|
import os
|
|
42
33
|
import sys
|
|
43
34
|
import copy
|
|
@@ -137,8 +128,8 @@ class InteractiveCleanUI:
|
|
|
137
128
|
ports.append( imdetails['gui']['cube']._pipe['control'].address[1] )
|
|
138
129
|
|
|
139
130
|
# Also forward http port if serving webpage
|
|
140
|
-
if not self._is_notebook:
|
|
141
|
-
|
|
131
|
+
#if not self._is_notebook:
|
|
132
|
+
# ports.append(self._http_port)
|
|
142
133
|
|
|
143
134
|
cmd = 'ssh'
|
|
144
135
|
for port in ports:
|
|
@@ -187,7 +178,7 @@ class InteractiveCleanUI:
|
|
|
187
178
|
###
|
|
188
179
|
### whether or not the session is being run from a jupyter notebook or script
|
|
189
180
|
###
|
|
190
|
-
self._is_notebook = is_interactive_jupyter()
|
|
181
|
+
#self._is_notebook = is_interactive_jupyter()
|
|
191
182
|
|
|
192
183
|
##
|
|
193
184
|
## the http port for serving GUI in webpage if not running in script
|
|
@@ -520,7 +511,7 @@ class InteractiveCleanUI:
|
|
|
520
511
|
'flux_axis': flux_axis
|
|
521
512
|
}
|
|
522
513
|
|
|
523
|
-
def
|
|
514
|
+
def _build_bokeh( self ):
|
|
524
515
|
'''create and show GUI
|
|
525
516
|
'''
|
|
526
517
|
###
|
|
@@ -625,7 +616,7 @@ class InteractiveCleanUI:
|
|
|
625
616
|
icw = SharedWidgets( )
|
|
626
617
|
toolbars = [ ]
|
|
627
618
|
for imid, imdetails in self._clean_targets.items( ):
|
|
628
|
-
imdetails['gui']['stats'] = imdetails['gui']['cube'].statistics( )
|
|
619
|
+
imdetails['gui']['stats'] = imdetails['gui']['cube'].statistics( name=f"{imid} stats" )
|
|
629
620
|
imdetails['image-channels'] = imdetails['gui']['cube'].shape( )[3]
|
|
630
621
|
|
|
631
622
|
status_line = imdetails['gui']['stopcode'] = imdetails['gui']['cube'].status_text( "<p>initial residual image</p>" if imdetails['image-channels'] > 1 else "<p>initial <b>single-channel</b> residual image</p>", width=230, reuse=status_line )
|
|
@@ -969,15 +960,15 @@ class InteractiveCleanUI:
|
|
|
969
960
|
itergroups[document._casa_image_name].active = document._casa_last_control_tab''' ) )
|
|
970
961
|
|
|
971
962
|
# Change display type depending on runtime environment
|
|
972
|
-
if self._is_notebook:
|
|
973
|
-
|
|
974
|
-
else:
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
###
|
|
978
|
-
|
|
963
|
+
#if self._is_notebook:
|
|
964
|
+
# output_notebook()
|
|
965
|
+
#else:
|
|
966
|
+
# ### Directory is created when an HTTP server is running
|
|
967
|
+
# ### (MAX)
|
|
968
|
+
### # output_file(self._imagename+'_webpage/index.html')
|
|
969
|
+
# pass
|
|
979
970
|
|
|
980
|
-
|
|
971
|
+
return self._fig['layout']
|
|
981
972
|
|
|
982
973
|
def _create_colormap_adjust( self, imdetails ):
|
|
983
974
|
palette = imdetails['gui']['cube'].palette( reuse=self._cube_palette )
|
|
@@ -1015,7 +1006,7 @@ class InteractiveCleanUI:
|
|
|
1015
1006
|
title='Colormap' ),
|
|
1016
1007
|
TabPanel( child=column( *imdetails['gui']['stats'] ),
|
|
1017
1008
|
title='Statistics' ) ] + imdetails['gui']['auto-masking-panel'],
|
|
1018
|
-
|
|
1009
|
+
sizing_mode='stretch_height', tabs_location='below' )
|
|
1019
1010
|
|
|
1020
1011
|
if not hasattr(self,'_image_control_tab_groups'):
|
|
1021
1012
|
self._image_control_tab_groups = { }
|
|
@@ -1046,7 +1037,7 @@ class InteractiveCleanUI:
|
|
|
1046
1037
|
height_policy='max', width_policy='max' ),
|
|
1047
1038
|
height_policy='max', width_policy='max' ), title=imid )
|
|
1048
1039
|
|
|
1049
|
-
def __call__( self,
|
|
1040
|
+
def __call__( self, exec_context, task_id=None ):
|
|
1050
1041
|
'''Display GUI and process events until the user stops the application.
|
|
1051
1042
|
|
|
1052
1043
|
Example:
|
|
@@ -1075,13 +1066,12 @@ class InteractiveCleanUI:
|
|
|
1075
1066
|
|
|
1076
1067
|
###
|
|
1077
1068
|
### cubevis.exe subpkg supports adding a stop condition to allow for interrupt,
|
|
1078
|
-
### but it is not needed for synchronous execution
|
|
1069
|
+
### but it is not needed for synchronous execution, e.g.
|
|
1070
|
+
### self._exec['stop-condition'], self._exec['id'] = exec_context.create_stop_condition(task_id)
|
|
1079
1071
|
###
|
|
1080
1072
|
self._exec = { 'stop-condition': None }
|
|
1081
|
-
#self._exec['stop-condition'], self._exec['id'] = exec_context.create_stop_condition(id)
|
|
1082
1073
|
|
|
1083
|
-
return exe.Task( self._task_server )
|
|
1084
|
-
# , stop_condition=self._exec['stop-condition'] )
|
|
1074
|
+
return self._build_bokeh( ), exe.Task( self._task_server )
|
|
1085
1075
|
|
|
1086
1076
|
async def _task_server( self ):
|
|
1087
1077
|
"""Wrapper for your serve() context manager"""
|
|
@@ -1131,8 +1121,6 @@ class InteractiveCleanUI:
|
|
|
1131
1121
|
|
|
1132
1122
|
httpd.serve_forever()
|
|
1133
1123
|
|
|
1134
|
-
self._launch_gui( )
|
|
1135
|
-
|
|
1136
1124
|
async with CMC( *( [ ctx for img in self._clean_targets.keys( ) for ctx in
|
|
1137
1125
|
[
|
|
1138
1126
|
self._clean_targets[img]['gui']['cube'].serve(self.__stop),
|
|
@@ -1296,6 +1284,7 @@ class InteractiveCleanUI:
|
|
|
1296
1284
|
### -- document is used storing state --
|
|
1297
1285
|
### --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
|
|
1298
1286
|
'initialize': '''if ( ! document._casa_initialized ) {
|
|
1287
|
+
console.log(`casalib version: ${casalib.version}`)
|
|
1299
1288
|
document._casa_image_name = initial_image
|
|
1300
1289
|
document._casa_initialized = true
|
|
1301
1290
|
document._casa_window_closed = false
|
|
@@ -1303,7 +1292,7 @@ class InteractiveCleanUI:
|
|
|
1303
1292
|
function (e) {
|
|
1304
1293
|
// if the window is already closed this message is never
|
|
1305
1294
|
// delivered (unless interactive clean is called again then
|
|
1306
|
-
// the event shows up in the newly created control pipe
|
|
1295
|
+
// the event shows up in the newly created control pipe)
|
|
1307
1296
|
if ( document._casa_window_closed == false ) {
|
|
1308
1297
|
ctrl_pipe.send( ids['stop'],
|
|
1309
1298
|
{ action: 'stop', value: { } },
|
|
@@ -28,15 +28,6 @@
|
|
|
28
28
|
'''implementation of the ``InteractiveCleanUI`` application for interactive control
|
|
29
29
|
of tclean'''
|
|
30
30
|
|
|
31
|
-
###
|
|
32
|
-
### Useful for debugging
|
|
33
|
-
###
|
|
34
|
-
###from cubevis.bokeh.state import initialize_bokeh
|
|
35
|
-
###initialize_bokeh( bokehjs_subst=".../bokeh-3.2.2.js" )
|
|
36
|
-
###
|
|
37
|
-
|
|
38
|
-
from pprint import pprint
|
|
39
|
-
|
|
40
31
|
import os
|
|
41
32
|
import sys
|
|
42
33
|
import copy
|
|
@@ -136,8 +127,8 @@ class InteractiveCleanUI:
|
|
|
136
127
|
ports.append( imdetails['gui']['cube']._pipe['control'].address[1] )
|
|
137
128
|
|
|
138
129
|
# Also forward http port if serving webpage
|
|
139
|
-
if not self._is_notebook:
|
|
140
|
-
|
|
130
|
+
#if not self._is_notebook:
|
|
131
|
+
# ports.append(self._http_port)
|
|
141
132
|
|
|
142
133
|
cmd = 'ssh'
|
|
143
134
|
for port in ports:
|
|
@@ -186,7 +177,7 @@ class InteractiveCleanUI:
|
|
|
186
177
|
###
|
|
187
178
|
### whether or not the session is being run from a jupyter notebook or script
|
|
188
179
|
###
|
|
189
|
-
self._is_notebook = is_interactive_jupyter()
|
|
180
|
+
#self._is_notebook = is_interactive_jupyter()
|
|
190
181
|
|
|
191
182
|
##
|
|
192
183
|
## the http port for serving GUI in webpage if not running in script
|
|
@@ -519,7 +510,7 @@ class InteractiveCleanUI:
|
|
|
519
510
|
'flux_axis': flux_axis
|
|
520
511
|
}
|
|
521
512
|
|
|
522
|
-
def
|
|
513
|
+
def _build_bokeh( self ):
|
|
523
514
|
'''create and show GUI
|
|
524
515
|
'''
|
|
525
516
|
###
|
|
@@ -624,7 +615,7 @@ class InteractiveCleanUI:
|
|
|
624
615
|
icw = SharedWidgets( )
|
|
625
616
|
toolbars = [ ]
|
|
626
617
|
for imid, imdetails in self._clean_targets.items( ):
|
|
627
|
-
imdetails['gui']['stats'] = imdetails['gui']['cube'].statistics( )
|
|
618
|
+
imdetails['gui']['stats'] = imdetails['gui']['cube'].statistics( name=f"{imid} stats" )
|
|
628
619
|
imdetails['image-channels'] = imdetails['gui']['cube'].shape( )[3]
|
|
629
620
|
|
|
630
621
|
status_line = imdetails['gui']['stopcode'] = imdetails['gui']['cube'].status_text( "<p>initial residual image</p>" if imdetails['image-channels'] > 1 else "<p>initial <b>single-channel</b> residual image</p>", width=230, reuse=status_line )
|
|
@@ -968,15 +959,15 @@ class InteractiveCleanUI:
|
|
|
968
959
|
itergroups[document._casa_image_name].active = document._casa_last_control_tab''' ) )
|
|
969
960
|
|
|
970
961
|
# Change display type depending on runtime environment
|
|
971
|
-
if self._is_notebook:
|
|
972
|
-
|
|
973
|
-
else:
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
###
|
|
977
|
-
|
|
962
|
+
#if self._is_notebook:
|
|
963
|
+
# output_notebook()
|
|
964
|
+
#else:
|
|
965
|
+
# ### Directory is created when an HTTP server is running
|
|
966
|
+
# ### (MAX)
|
|
967
|
+
### # output_file(self._imagename+'_webpage/index.html')
|
|
968
|
+
# pass
|
|
978
969
|
|
|
979
|
-
|
|
970
|
+
return self._fig['layout']
|
|
980
971
|
|
|
981
972
|
def _create_colormap_adjust( self, imdetails ):
|
|
982
973
|
palette = imdetails['gui']['cube'].palette( reuse=self._cube_palette )
|
|
@@ -1014,7 +1005,7 @@ class InteractiveCleanUI:
|
|
|
1014
1005
|
title='Colormap' ),
|
|
1015
1006
|
TabPanel( child=column( *imdetails['gui']['stats'] ),
|
|
1016
1007
|
title='Statistics' ) ] + imdetails['gui']['auto-masking-panel'],
|
|
1017
|
-
|
|
1008
|
+
sizing_mode='stretch_height', tabs_location='below' )
|
|
1018
1009
|
|
|
1019
1010
|
if not hasattr(self,'_image_control_tab_groups'):
|
|
1020
1011
|
self._image_control_tab_groups = { }
|
|
@@ -1045,7 +1036,7 @@ class InteractiveCleanUI:
|
|
|
1045
1036
|
height_policy='max', width_policy='max' ),
|
|
1046
1037
|
height_policy='max', width_policy='max' ), title=imid )
|
|
1047
1038
|
|
|
1048
|
-
def __call__( self,
|
|
1039
|
+
def __call__( self, exec_context, task_id=None ):
|
|
1049
1040
|
'''Display GUI and process events until the user stops the application.
|
|
1050
1041
|
|
|
1051
1042
|
Example:
|
|
@@ -1074,13 +1065,12 @@ class InteractiveCleanUI:
|
|
|
1074
1065
|
|
|
1075
1066
|
###
|
|
1076
1067
|
### cubevis.exe subpkg supports adding a stop condition to allow for interrupt,
|
|
1077
|
-
### but it is not needed for synchronous execution
|
|
1068
|
+
### but it is not needed for synchronous execution, e.g.
|
|
1069
|
+
### self._exec['stop-condition'], self._exec['id'] = exec_context.create_stop_condition(task_id)
|
|
1078
1070
|
###
|
|
1079
1071
|
self._exec = { 'stop-condition': None }
|
|
1080
|
-
#self._exec['stop-condition'], self._exec['id'] = exec_context.create_stop_condition(id)
|
|
1081
1072
|
|
|
1082
|
-
return exe.Task( self._task_server )
|
|
1083
|
-
# , stop_condition=self._exec['stop-condition'] )
|
|
1073
|
+
return self._build_bokeh( ), exe.Task( self._task_server )
|
|
1084
1074
|
|
|
1085
1075
|
async def _task_server( self ):
|
|
1086
1076
|
"""Wrapper for your serve() context manager"""
|
|
@@ -1130,8 +1120,6 @@ class InteractiveCleanUI:
|
|
|
1130
1120
|
|
|
1131
1121
|
httpd.serve_forever()
|
|
1132
1122
|
|
|
1133
|
-
self._launch_gui( )
|
|
1134
|
-
|
|
1135
1123
|
async with CMC( *( [ ctx for img in self._clean_targets.keys( ) for ctx in
|
|
1136
1124
|
[
|
|
1137
1125
|
self._clean_targets[img]['gui']['cube'].serve(self.__stop),
|
|
@@ -1295,6 +1283,7 @@ class InteractiveCleanUI:
|
|
|
1295
1283
|
### -- document is used storing state --
|
|
1296
1284
|
### --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
|
|
1297
1285
|
'initialize': '''if ( ! document._casa_initialized ) {
|
|
1286
|
+
console.log(`casalib version: ${casalib.version}`)
|
|
1298
1287
|
document._casa_image_name = initial_image
|
|
1299
1288
|
document._casa_initialized = true
|
|
1300
1289
|
document._casa_window_closed = false
|
|
@@ -1302,7 +1291,7 @@ class InteractiveCleanUI:
|
|
|
1302
1291
|
function (e) {
|
|
1303
1292
|
// if the window is already closed this message is never
|
|
1304
1293
|
// delivered (unless interactive clean is called again then
|
|
1305
|
-
// the event shows up in the newly created control pipe
|
|
1294
|
+
// the event shows up in the newly created control pipe)
|
|
1306
1295
|
if ( document._casa_window_closed == false ) {
|
|
1307
1296
|
ctrl_pipe.send( ids['stop'],
|
|
1308
1297
|
{ action: 'stop', value: { } },
|
cubevis/utils/__init__.py
CHANGED
|
@@ -45,6 +45,7 @@ from ._copydoc import copydoc
|
|
|
45
45
|
from ._pkgs import find_pkg, load_pkg
|
|
46
46
|
from ._jupyter import is_interactive_jupyter
|
|
47
47
|
from ._browser import have_firefox
|
|
48
|
+
from ._git import max_git_version
|
|
48
49
|
|
|
49
50
|
from astropy import units
|
|
50
51
|
from regions import PixCoord
|
|
@@ -167,28 +168,129 @@ def warn_import(package):
|
|
|
167
168
|
error_msg("warning, %s" % warn_import.msgs[package].format(package=package))
|
|
168
169
|
|
|
169
170
|
|
|
170
|
-
@static_vars(url='http://clients3.google.com/generate_204'
|
|
171
|
+
@static_vars(url='http://clients3.google.com/generate_204',
|
|
172
|
+
cached_result=None,
|
|
173
|
+
cache_timeout=30) # Cache result for 30 seconds
|
|
171
174
|
def have_network():
|
|
172
|
-
'''
|
|
173
|
-
is available.
|
|
175
|
+
'''Check to see if an active network with general internet connectivity
|
|
176
|
+
is available. Returns ``True`` if we have internet connectivity and
|
|
174
177
|
``False`` if we do not.
|
|
178
|
+
|
|
179
|
+
Uses Google's connectivity check endpoint and caches the result briefly
|
|
180
|
+
to avoid repeated network calls.
|
|
175
181
|
'''
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
182
|
+
import time
|
|
183
|
+
|
|
184
|
+
# Check cache first
|
|
185
|
+
current_time = time.time()
|
|
186
|
+
if (hasattr(have_network, 'last_check_time') and
|
|
187
|
+
have_network.cached_result is not None and
|
|
188
|
+
current_time - have_network.last_check_time < have_network.cache_timeout):
|
|
189
|
+
return have_network.cached_result
|
|
190
|
+
|
|
191
|
+
def check_connectivity():
|
|
192
|
+
try:
|
|
193
|
+
# Set a reasonable timeout to avoid hanging
|
|
194
|
+
request = urllib.request.Request(
|
|
195
|
+
have_network.url,
|
|
196
|
+
headers={'User-Agent': 'Mozilla/5.0 (compatible; connectivity-check)'}
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
with urllib.request.urlopen(request, timeout=5) as response:
|
|
200
|
+
return response.status == 204
|
|
201
|
+
|
|
202
|
+
except urllib.error.HTTPError as e:
|
|
203
|
+
# Some proxies might return different status codes but still have connectivity
|
|
204
|
+
if e.code in [200, 301, 302]:
|
|
205
|
+
return True
|
|
206
|
+
return False
|
|
207
|
+
except (urllib.error.ContentTooShortError,
|
|
208
|
+
urllib.error.URLError,
|
|
209
|
+
socket.timeout,
|
|
210
|
+
socket.gaierror, # DNS resolution errors
|
|
211
|
+
OSError) as e:
|
|
212
|
+
return False
|
|
213
|
+
except Exception:
|
|
214
|
+
# Catch any other unexpected errors
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
# Perform the check
|
|
218
|
+
result = check_connectivity()
|
|
219
|
+
|
|
220
|
+
# Cache the result
|
|
221
|
+
have_network.cached_result = result
|
|
222
|
+
have_network.last_check_time = current_time
|
|
223
|
+
|
|
224
|
+
return result
|
|
191
225
|
|
|
226
|
+
# Alternative version with fallback URLs
|
|
227
|
+
@static_vars(urls=['http://clients3.google.com/generate_204',
|
|
228
|
+
'http://detectportal.firefox.com/success.txt',
|
|
229
|
+
'http://www.msftconnecttest.com/connecttest.txt'],
|
|
230
|
+
cached_result=None,
|
|
231
|
+
cache_timeout=30)
|
|
232
|
+
def have_network_with_fallback():
|
|
233
|
+
'''Check network connectivity using multiple fallback endpoints.
|
|
234
|
+
|
|
235
|
+
Tries multiple well-known connectivity check endpoints to increase
|
|
236
|
+
reliability in different network environments.
|
|
237
|
+
'''
|
|
238
|
+
import time
|
|
239
|
+
|
|
240
|
+
# Check cache first
|
|
241
|
+
current_time = time.time()
|
|
242
|
+
if (hasattr(have_network_with_fallback, 'last_check_time') and
|
|
243
|
+
have_network_with_fallback.cached_result is not None and
|
|
244
|
+
current_time - have_network_with_fallback.last_check_time < have_network_with_fallback.cache_timeout):
|
|
245
|
+
return have_network_with_fallback.cached_result
|
|
246
|
+
|
|
247
|
+
def try_url(url, expected_status=None, expected_content=None):
|
|
248
|
+
try:
|
|
249
|
+
request = urllib.request.Request(
|
|
250
|
+
url,
|
|
251
|
+
headers={'User-Agent': 'Mozilla/5.0 (compatible; connectivity-check)'}
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
with urllib.request.urlopen(request, timeout=5) as response:
|
|
255
|
+
if expected_status and response.status != expected_status:
|
|
256
|
+
return False
|
|
257
|
+
|
|
258
|
+
if expected_content:
|
|
259
|
+
content = response.read().decode('utf-8').strip()
|
|
260
|
+
return content == expected_content
|
|
261
|
+
|
|
262
|
+
# For Google's endpoint, expect 204 No Content
|
|
263
|
+
if 'generate_204' in url:
|
|
264
|
+
return response.status == 204
|
|
265
|
+
|
|
266
|
+
# For other endpoints, any successful response is good
|
|
267
|
+
return 200 <= response.status < 400
|
|
268
|
+
|
|
269
|
+
except Exception:
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
# Try each URL until one succeeds
|
|
273
|
+
for url in have_network_with_fallback.urls:
|
|
274
|
+
if url == 'http://detectportal.firefox.com/success.txt':
|
|
275
|
+
if try_url(url, expected_content='success'):
|
|
276
|
+
result = True
|
|
277
|
+
break
|
|
278
|
+
elif url == 'http://www.msftconnecttest.com/connecttest.txt':
|
|
279
|
+
if try_url(url, expected_content='Microsoft Connect Test'):
|
|
280
|
+
result = True
|
|
281
|
+
break
|
|
282
|
+
else: # Google's endpoint
|
|
283
|
+
if try_url(url):
|
|
284
|
+
result = True
|
|
285
|
+
break
|
|
286
|
+
else:
|
|
287
|
+
result = False
|
|
288
|
+
|
|
289
|
+
# Cache the result
|
|
290
|
+
have_network_with_fallback.cached_result = result
|
|
291
|
+
have_network_with_fallback.last_check_time = current_time
|
|
292
|
+
|
|
293
|
+
return result
|
|
192
294
|
|
|
193
295
|
def ranges(iterable, order=sorted, key=lambda x: x):
|
|
194
296
|
'''collect elements of ``iterable`` into tuple ranges where each tuple represents
|
cubevis/utils/_git.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from os import path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from packaging import version
|
|
5
|
+
|
|
6
|
+
_VERSION = None
|
|
7
|
+
def max_git_version() -> Optional[str]:
|
|
8
|
+
"""Get maximum version from all git tags"""
|
|
9
|
+
global _VERSION
|
|
10
|
+
package_parent = path.dirname(path.dirname(path.dirname(path.realpath(__file__))))
|
|
11
|
+
if _VERSION is None and path.basename( package_parent ) == 'cubevis' and \
|
|
12
|
+
path.isdir(path.join( package_parent, '.git' )):
|
|
13
|
+
result = subprocess.run(
|
|
14
|
+
['git', 'tag'],
|
|
15
|
+
cwd=package_parent,
|
|
16
|
+
capture_output=True,
|
|
17
|
+
text=True,
|
|
18
|
+
check=True
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Filter tags matching version pattern v#.#.#
|
|
22
|
+
tags = [
|
|
23
|
+
tag[1:] for tag in result.stdout.strip().split('\n')
|
|
24
|
+
if tag.startswith('v') and all(part.isdigit() for part in tag[1:].split('.'))
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
if not tags:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
# Sort by version number and get maximum
|
|
31
|
+
try:
|
|
32
|
+
max_tag = max(tags, key=lambda v: version.parse(v))
|
|
33
|
+
_VERSION = max_tag
|
|
34
|
+
except: pass
|
|
35
|
+
|
|
36
|
+
return _VERSION
|
cubevis/utils/_jupyter.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger(__name__)
|
|
1
4
|
|
|
2
5
|
#def is_notebook() -> bool:
|
|
3
6
|
# try:
|
|
@@ -31,10 +34,12 @@ def is_interactive_jupyter( ) -> bool:
|
|
|
31
34
|
ipython = get_ipython()
|
|
32
35
|
|
|
33
36
|
if ipython is None:
|
|
37
|
+
logger.debug(f"\tis_interactive_jupyter<1>: False")
|
|
34
38
|
return False
|
|
35
39
|
|
|
36
40
|
# Check if we're in a ZMQ-based shell (kernel)
|
|
37
41
|
if ipython.__class__.__name__ != 'ZMQInteractiveShell':
|
|
42
|
+
logger.debug(f"\tis_interactive_jupyter<2>: False")
|
|
38
43
|
return False
|
|
39
44
|
|
|
40
45
|
# Check for active frontend connection
|
|
@@ -45,12 +50,14 @@ def is_interactive_jupyter( ) -> bool:
|
|
|
45
50
|
if hasattr(kernel, 'shell_socket') and kernel.shell_socket is not None:
|
|
46
51
|
# For newer Jupyter versions, check connection count
|
|
47
52
|
if hasattr(kernel, 'connection_count'):
|
|
53
|
+
logger.debug(f"\tis_interactive_jupyter<3>: {kernel.connection_count > 0}")
|
|
48
54
|
return kernel.connection_count > 0
|
|
49
55
|
|
|
50
56
|
# For older versions, check if socket is connected
|
|
51
57
|
try:
|
|
52
58
|
# Try to get socket state - if it fails, likely no frontend
|
|
53
59
|
socket_state = kernel.shell_socket.closed
|
|
60
|
+
logger.debug(f"\tis_interactive_jupyter<4>: {not socket_state}")
|
|
54
61
|
return not socket_state
|
|
55
62
|
except AttributeError:
|
|
56
63
|
pass
|
|
@@ -60,12 +67,14 @@ def is_interactive_jupyter( ) -> bool:
|
|
|
60
67
|
try:
|
|
61
68
|
parent = kernel.get_parent()
|
|
62
69
|
# If there's a parent message, we're likely in interactive mode
|
|
70
|
+
logger.debug(f"\tis_interactive_jupyter<5>: {parent is not None and len(parent) > 0}")
|
|
63
71
|
return parent is not None and len(parent) > 0
|
|
64
72
|
except Exception:
|
|
65
73
|
pass
|
|
66
74
|
|
|
67
75
|
# Method 3: Check for execution context
|
|
68
76
|
if hasattr(kernel, '_parent_ident') and kernel._parent_ident:
|
|
77
|
+
logger.debug(f"\tis_interactive_jupyter<6>: True")
|
|
69
78
|
return True
|
|
70
79
|
|
|
71
80
|
# Fallback: Check for common Jupyter notebook environment indicators
|
|
@@ -82,11 +91,14 @@ def is_interactive_jupyter( ) -> bool:
|
|
|
82
91
|
try:
|
|
83
92
|
import IPython.display
|
|
84
93
|
# If we can import display and have env indicators, likely interactive
|
|
94
|
+
logger.debug(f"\tis_interactive_jupyter<7>: True")
|
|
85
95
|
return True
|
|
86
96
|
except ImportError:
|
|
87
97
|
pass
|
|
88
98
|
|
|
99
|
+
logger.debug(f"\tis_interactive_jupyter<8>: False")
|
|
89
100
|
return False
|
|
90
101
|
|
|
91
102
|
except ImportError:
|
|
103
|
+
logger.debug(f"\tis_interactive_jupyter<9>: False")
|
|
92
104
|
return False
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cubevis
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: visualization toolkit and apps for casa
|
|
5
5
|
License: LGPL
|
|
6
6
|
Author-email: Darrell Schiebel <darrell@schiebel.us>,Pam Harris <pharris@nrao.edu>
|
|
7
|
-
Requires-Python: >=3.
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
8
|
Requires-Dist: astropy>=5.1
|
|
9
9
|
Requires-Dist: bokeh==3.6.1
|
|
10
10
|
Requires-Dist: certifi
|
|
11
11
|
Requires-Dist: matplotlib
|
|
12
|
-
Requires-Dist: regions>=0.
|
|
12
|
+
Requires-Dist: regions>=0.8
|
|
13
13
|
Requires-Dist: websockets>=10.3
|
|
14
14
|
Project-URL: Bug Tracker, https://github.com/casangi/cubevis/issues
|
|
15
15
|
Project-URL: Homepage, https://github.com/casangi/cubevis?tab=readme-ov-file#cubevis---visualization-tools-for-casa-images
|