cubevis 0.5.27__tar.gz → 0.5.28__tar.gz

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 (151) hide show
  1. {cubevis-0.5.27 → cubevis-0.5.28}/PKG-INFO +1 -1
  2. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__js__/cubevisjs.min.js +5 -5
  3. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/sources/_data_pipe.py +128 -2
  4. {cubevis-0.5.27 → cubevis-0.5.28}/pyproject.toml +1 -1
  5. {cubevis-0.5.27 → cubevis-0.5.28}/LICENSE +0 -0
  6. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/LICENSE.rst +0 -0
  7. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/20px/fast-backward.svg +0 -0
  8. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/20px/fast-forward.svg +0 -0
  9. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/20px/step-backward.svg +0 -0
  10. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/20px/step-forward.svg +0 -0
  11. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/add-chan.png +0 -0
  12. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/add-chan.svg +0 -0
  13. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/add-cube.png +0 -0
  14. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/add-cube.svg +0 -0
  15. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/drag.png +0 -0
  16. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/drag.svg +0 -0
  17. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/mask-selected.png +0 -0
  18. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/mask.png +0 -0
  19. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/mask.svg +0 -0
  20. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/new-layer-sm-selected.png +0 -0
  21. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/new-layer-sm-selected.svg +0 -0
  22. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/new-layer-sm.png +0 -0
  23. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/new-layer-sm.svg +0 -0
  24. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/reset.png +0 -0
  25. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/reset.svg +0 -0
  26. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/sub-chan.png +0 -0
  27. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/sub-chan.svg +0 -0
  28. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/sub-cube.png +0 -0
  29. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/sub-cube.svg +0 -0
  30. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/zoom-to-fit.png +0 -0
  31. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__icons__/zoom-to-fit.svg +0 -0
  32. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__init__.py +0 -0
  33. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__js__/bokeh-3.6.1.min.js +0 -0
  34. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__js__/bokeh-tables-3.6.1.min.js +0 -0
  35. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__js__/bokeh-widgets-3.6.1.min.js +0 -0
  36. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/__js__/casalib.min.js +0 -0
  37. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/__init__.py +0 -0
  38. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/annotations/__init__.py +0 -0
  39. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/annotations/_ev_poly_annotation.py +0 -0
  40. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/components/__init__.py +0 -0
  41. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/format/__init__.py +0 -0
  42. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/format/_time_ticks.py +0 -0
  43. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/format/_wcs_ticks.py +0 -0
  44. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/models/__init__.py +0 -0
  45. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/models/_edit_span.py +0 -0
  46. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/models/_ev_text_input.py +0 -0
  47. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/models/_shared_dict.py +0 -0
  48. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/models/_tip.py +0 -0
  49. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/models/_tip_button.py +0 -0
  50. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/sources/__init__.py +0 -0
  51. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/sources/_image_data_source.py +0 -0
  52. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/sources/_image_pipe.py +0 -0
  53. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/sources/_spectra_data_source.py +0 -0
  54. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/sources/_updatable_data_source.py +0 -0
  55. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/state/__init__.py +0 -0
  56. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/state/_initialize.py +0 -0
  57. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/state/_javascript.py +0 -0
  58. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/state/_palette.py +0 -0
  59. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/state/_session.py +0 -0
  60. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/state/js/bokeh-2.4.1.min.js +0 -0
  61. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/state/js/bokeh-gl-2.4.1.min.js +0 -0
  62. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/state/js/bokeh-tables-2.4.1.min.js +0 -0
  63. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/state/js/bokeh-widgets-2.4.1.min.js +0 -0
  64. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/state/js/casaguijs-v0.0.4.0-b2.4.min.js +0 -0
  65. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/state/js/casaguijs-v0.0.5.0-b2.4.min.js +0 -0
  66. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/state/js/casaguijs-v0.0.6.0-b2.4.min.js +0 -0
  67. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/state/js/casalib-v0.0.1.min.js +0 -0
  68. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/tools/__init__.py +0 -0
  69. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/tools/_cbreset_tool.py +0 -0
  70. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/tools/_drag_tool.py +0 -0
  71. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/utils/__init__.py +0 -0
  72. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/utils/_axes_labels.py +0 -0
  73. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/bokeh/utils/_svg_icon.py +0 -0
  74. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/data/__init__.py +0 -0
  75. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/data/casaimage/__init__.py +0 -0
  76. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/exe/__init__.py +0 -0
  77. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/exe/_context.py +0 -0
  78. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/exe/_mode.py +0 -0
  79. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/exe/_setting.py +0 -0
  80. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/exe/_task.py +0 -0
  81. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/_gclean.py +0 -0
  82. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/apps/__init__.py +0 -0
  83. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/apps/_createmask.py +0 -0
  84. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/apps/_createregion.py +0 -0
  85. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/apps/_interactiveclean.mustache +0 -0
  86. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/apps/_interactiveclean.py +0 -0
  87. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/apps/_plotants.py +0 -0
  88. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/apps/_plotbandpass.py +0 -0
  89. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/casashell/createmask.py +0 -0
  90. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/casashell/iclean.py +0 -0
  91. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/casatasks/__init__.py +0 -0
  92. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/casatasks/createmask.py +0 -0
  93. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/casatasks/createregion.py +0 -0
  94. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/private/casatasks/iclean.py +0 -0
  95. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/readme.rst +0 -0
  96. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/remote/__init__.py +0 -0
  97. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/remote/_gclean.py +0 -0
  98. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/remote/_local.py +0 -0
  99. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/remote/_remote_kernel.py +0 -0
  100. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/toolbox/__init__.py +0 -0
  101. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/toolbox/_app_context.py +0 -0
  102. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/toolbox/_cube.py +0 -0
  103. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/toolbox/_interactive_clean_ui.mustache +0 -0
  104. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/toolbox/_interactive_clean_ui.py +0 -0
  105. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/toolbox/_interactiveclean_wrappers.py +0 -0
  106. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/toolbox/_region_list.py +0 -0
  107. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/_ResourceManager.py +0 -0
  108. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/__init__.py +0 -0
  109. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/_browser.py +0 -0
  110. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/_contextmgrchain.py +0 -0
  111. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/_conversion.py +0 -0
  112. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/_copydoc.py +0 -0
  113. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/_docenum.py +0 -0
  114. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/_import_protected_module.py +0 -0
  115. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/_jupyter.py +0 -0
  116. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/_logging.py +0 -0
  117. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/_pkgs.py +0 -0
  118. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/_regions.py +0 -0
  119. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/_static.py +0 -0
  120. {cubevis-0.5.27 → cubevis-0.5.28}/cubevis/utils/_tiles.py +0 -0
  121. {cubevis-0.5.27 → cubevis-0.5.28}/readme.rst +0 -0
  122. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/alma-many-chan/alma-many-chan.py +0 -0
  123. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/basic-websockets-demo/client.html +0 -0
  124. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/basic-websockets-demo/client.py +0 -0
  125. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/basic-websockets-demo/server.py +0 -0
  126. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/createmask-demo/run-createmask.py +0 -0
  127. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/createregion-demo/run-createregion.py +0 -0
  128. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/cubemask-demo/image-slider-spectra-done-stats.py +0 -0
  129. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/cubemask-demo/image-slider-spectra-done.py +0 -0
  130. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/cubemask-demo/image-slider-spectra.py +0 -0
  131. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/cubemask-demo/image-slider.py +0 -0
  132. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/cubemask-demo/image.py +0 -0
  133. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/iclean-demo/m100_interactive.py +0 -0
  134. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/iclean-demo/mask0-iclean.py +0 -0
  135. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/iclean-demo/run-gclean.py +0 -0
  136. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/iclean-demo/run-iclean-obj.py +0 -0
  137. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/iclean-demo/run-iclean.py +0 -0
  138. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/iclean-demo/vla-sim-jet-iclean.py +0 -0
  139. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/iclean-first-look/run-fl-cont.py +0 -0
  140. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/iclean-first-look/run-fl-line.py +0 -0
  141. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/iclean-outlier/run-iclean.py +0 -0
  142. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/iclean-outlier/test_outlier.txt +0 -0
  143. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/iclean-remote/iclean_remote_webserver.py +0 -0
  144. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/large-cube/run-largecube.py +0 -0
  145. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/svg-test.py +0 -0
  146. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/updatable-data-source/direct-plot.py +0 -0
  147. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/updatable-data-source/simple-update.py +0 -0
  148. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/updatable-data-source/updated-plot.py +0 -0
  149. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/uranus-demo/uranus-iclean.py +0 -0
  150. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/websocket-reconnect/client.html +0 -0
  151. {cubevis-0.5.27 → cubevis-0.5.28}/tests/manual/websocket-reconnect/server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cubevis
3
- Version: 0.5.27
3
+ Version: 0.5.28
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>
@@ -42,13 +42,13 @@
42
42
  }
43
43
  })
44
44
  ({
45
- "97397933dc": function _(a,t,e,o,c){o();const i=a("tslib"),n=a("b33e822163");c("DataPipe",n.DataPipe);const s=a("2889e0dd45");c("ImagePipe",s.ImagePipe);const p=a("de65005924");c("ImageDataSource",p.ImageDataSource);const r=a("02e3c3e46c");c("SpectraDataSource",r.SpectraDataSource);const S=a("64b16deff9");c("UpdatableDataSource",S.UpdatableDataSource);const D=a("b6ae454f0d");c("WcsTicks",D.WcsTicks);const T=a("cb7d28d6b3");c("DragTool",T.DragTool);const d=a("01959f25a3");c("CBResetTool",d.CBResetTool);const l=a("e3901fa9f2");c("serialize",l.serialize),c("deserialize",l.deserialize);const u=a("9f961622ce");c("TipButton",u.TipButton);const b=a("ca4c845905");c("Tip",b.Tip);const g=a("50a1e32f01");c("SharedDict",g.SharedDict);const f=a("b55081402e");c("EditSpan",f.EditSpan);const E=a("9144bfc7a5");c("EvTextInput",E.EvTextInput);const I=a("74e0abef8a");c("EvPolyAnnotation",I.EvPolyAnnotation);const P=i.__importStar(a("15b954190c"));e.find=P;(0,a("@bokehjs/base").register_models)({DataPipe:n.DataPipe,ImagePipe:s.ImagePipe,ImageDataSource:p.ImageDataSource,SpectraDataSource:r.SpectraDataSource,UpdatableDataSource:S.UpdatableDataSource,WcsTicks:D.WcsTicks,DragTool:T.DragTool,CBResetTool:d.CBResetTool,Tip:b.Tip,TipButton:u.TipButton,SharedDict:g.SharedDict,EditSpan:f.EditSpan,EvTextInput:E.EvTextInput,EvPolyAnnotation:I.EvPolyAnnotation})},
46
- "b33e822163": function _(e,s,t,i,n){var o;i();const c=e("@bokehjs/models/sources/data_source"),a=e("e3901fa9f2"),d=e("@bokehjs/core/util/callbacks");class l extends c.DataSource{constructor(e){super(e),this.send_queue={},this.connection_queue=[],this.pending={},this.incoming_callbacks={}}initialize(){super.initialize();let e=`ws://${this.address[0]}:${this.address[1]}`;console.log("datapipe url:",e);var s=void 0;document.shutdown_in_progress_=!1;var t=()=>{void 0!==this.websocket&&this.websocket.close(),this.websocket=new WebSocket(e),this.websocket.binaryType="arraybuffer",this.websocket.addEventListener("error",(e=>{console.log("error encountered:",e)})),this.websocket.onmessage=e=>{if("string"==typeof e.data||e.data instanceof String){let s=(0,a.deserialize)(e.data);if("id"in s&&"direction"in s&&"message"in s){let{id:e,message:t,direction:i}=s;if(void 0===t&&console.log("Error, event failure",s),"j2p"==i)if(e in this.pending){let{cb:i}=this.pending[e];if(delete this.pending[e],e in this.send_queue&&this.send_queue[e].length>0){let{cb:s,msg:t}=this.send_queue[e].shift();this.pending[e]={cb:s},this.websocket.send((0,a.serialize)(t))}void 0===t?console.log("DROPPING ERROR FOR NOW (maybe need error callbacks)",s):i(t)}else console.log("message received but could not find id");else if(e in this.incoming_callbacks){let s=this.incoming_callbacks[e](t);this.websocket.send((0,a.serialize)({id:e,direction:i,message:s,session:casalib.object_id(this)}))}}else console.log(`datapipe received message without one of 'id', 'message' or 'direction': ${s}`)}else console.log("datapipe received binary data",e.data.byteLength,"bytes")},this.websocket.onopen=()=>{for(s?0==s.connected&&console.log(`connection reestablished at ${new Date}`):this.websocket.send((0,a.serialize)({id:"initialize",direction:"j2p",session:casalib.object_id(this)})),s=new casalib.ReconnectState;this.connection_queue.length>0;){let e=this.connection_queue.shift();this.send.apply(e[0],e[1])}},this.websocket.onclose=()=>{if(s&&1==s.connected&&(console.log(`connection lost at ${new Date}`),s.connected=!1,!document.shutdown_in_progress_)){console.log(`connection lost at ${new Date}`);var e=s;function i(n){0==s.connected&&(console.log(`${n+1}\treconnection attempt ${new Date}`),t(),e.backoff(),e.retries>0?setTimeout(i,e.timeout,n+1):0==s.connected&&console.log(`aborting reconnection after ${n} attempts ${new Date}`))}i(0)}}};t();(()=>{null!=this.init_script&&(0,d.execute)(this.init_script,this)})()}register(e,s){this.incoming_callbacks[e]=s}send(e,s,t,i=!1){let n={id:e,message:s,direction:"j2p",session:casalib.object_id(this)};if(!this.websocket||e in this.pending)if(e in this.send_queue)if("boolean"==typeof i&&i&&this.send_queue[e].length>0)this.send_queue[e][0].msg=n,this.send_queue[e][0].cb=t;else if("function"==typeof i&&this.send_queue[e].length>0){let o=!1;for(const c of this.send_queue[e])i(c.msg.message)&&(c.msg=n,c.cb=t,o=!0);o||this.send_queue[e].push({cb:t,msg:n})}else this.send_queue[e].push({cb:t,msg:n});else this.send_queue[e]=[{cb:t,msg:n}];else if(this.websocket.readyState===WebSocket.CONNECTING)this.connection_queue.push([this,[e,s,t]]);else if(e in this.send_queue&&this.send_queue[e].length>0){this.send_queue[e].push({cb:t,msg:n});{let{cb:d,msg:l}=this.send_queue[e].shift();if(this.pending[e]={cb:d},this.websocket.readyState===WebSocket.OPEN)this.websocket.send((0,a.serialize)(l));else{let u=20,h=this;function r(){h.websocket.readyState===WebSocket.OPEN?h.websocket.send((0,a.serialize)(l)):(u-=1,u>0&&setTimeout(r,3e3))}setTimeout(r,3e3)}}}else if(this.websocket.readyState===WebSocket.OPEN)this.pending[e]={cb:t},this.websocket.send((0,a.serialize)(n));else{let b=20,g=this;function _(){g.websocket.readyState===WebSocket.OPEN?(g.pending[e]={cb:t},g.websocket.send((0,a.serialize)(n))):(b-=1,b>0&&setTimeout(_,3e3))}setTimeout(_,3e3)}}}t.DataPipe=l,o=l,l.__name__="DataPipe",l.__module__="cubevis.bokeh.sources._data_pipe",o.define((({Any:e,Tuple:s,String:t,Number:i})=>({init_script:[e,null],address:[s(t,i)]})))},
45
+ "97397933dc": function _(a,t,e,o,c){o();const i=a("tslib"),n=a("33e5a43d86");c("DataPipe",n.DataPipe);const s=a("2889e0dd45");c("ImagePipe",s.ImagePipe);const p=a("de65005924");c("ImageDataSource",p.ImageDataSource);const r=a("02e3c3e46c");c("SpectraDataSource",r.SpectraDataSource);const S=a("64b16deff9");c("UpdatableDataSource",S.UpdatableDataSource);const d=a("b6ae454f0d");c("WcsTicks",d.WcsTicks);const D=a("cb7d28d6b3");c("DragTool",D.DragTool);const T=a("01959f25a3");c("CBResetTool",T.CBResetTool);const l=a("e3901fa9f2");c("serialize",l.serialize),c("deserialize",l.deserialize);const u=a("9f961622ce");c("TipButton",u.TipButton);const b=a("ca4c845905");c("Tip",b.Tip);const g=a("50a1e32f01");c("SharedDict",g.SharedDict);const f=a("b55081402e");c("EditSpan",f.EditSpan);const E=a("9144bfc7a5");c("EvTextInput",E.EvTextInput);const I=a("74e0abef8a");c("EvPolyAnnotation",I.EvPolyAnnotation);const P=i.__importStar(a("15b954190c"));e.find=P;(0,a("@bokehjs/base").register_models)({DataPipe:n.DataPipe,ImagePipe:s.ImagePipe,ImageDataSource:p.ImageDataSource,SpectraDataSource:r.SpectraDataSource,UpdatableDataSource:S.UpdatableDataSource,WcsTicks:d.WcsTicks,DragTool:D.DragTool,CBResetTool:T.CBResetTool,Tip:b.Tip,TipButton:u.TipButton,SharedDict:g.SharedDict,EditSpan:f.EditSpan,EvTextInput:E.EvTextInput,EvPolyAnnotation:I.EvPolyAnnotation})},
46
+ "33e5a43d86": function _(e,s,t,i,n){var o;i();const a=e("@bokehjs/models/sources/data_source"),c=e("e3901fa9f2"),r=e("@bokehjs/core/util/callbacks");class d extends a.DataSource{constructor(e){super(e),this.send_queue={},this.connection_queue=[],this.pending={},this.incoming_callbacks={},this.session_id=casalib.object_id(this)}checkSessionConflict(){try{if("undefined"==typeof Storage)return console.warn("localStorage not available, skipping session conflict detection"),!0;const e=localStorage.getItem(this.session_storage_key);if(e){const s=JSON.parse(e);if(s.sessionId!==this.session_id&&Date.now()-s.timestamp<12e4){const e=`CubeVis DataPipe (${this.instance_key}) is already running in another browser window or tab.\n\nPlease close other instances and refresh this page, or\nclose this window to continue using the other instance.`;return alert(e),window.opener||1===window.history.length?window.close():window.location.href="about:blank",!1}}return this.updateSessionHeartbeat(),!0}catch(e){return console.warn("Session conflict detection failed:",e),!0}}updateSessionHeartbeat(){try{"undefined"!=typeof Storage&&localStorage.setItem(this.session_storage_key,JSON.stringify({sessionId:this.session_id,timestamp:Date.now(),instanceKey:this.instance_key}))}catch(e){console.warn("Session heartbeat update failed:",e)}}startHeartbeat(){this.heartbeat_interval=window.setInterval((()=>{this.updateSessionHeartbeat()}),3e4)}stopHeartbeat(){this.heartbeat_interval&&(clearInterval(this.heartbeat_interval),this.heartbeat_interval=void 0)}cleanupSession(){try{if("undefined"!=typeof Storage){const e=localStorage.getItem(this.session_storage_key);if(e){JSON.parse(e).sessionId===this.session_id&&localStorage.removeItem(this.session_storage_key)}}}catch(e){console.warn("Session cleanup failed:",e)}this.stopHeartbeat()}handleSessionConflictMessage(e){console.error("Session conflict detected by server:",e);let s="Session conflict detected by server.";"session_conflict"===e.type?s=e.error||s:"session_corruption"===e.type&&(s=`Session corruption detected.\nExpected: ${e.expected}\nReceived: ${e.received}`),alert(s+"\n\nThis window will be closed to prevent data corruption."),this.cleanupSession();const t=new CustomEvent("cubevis_session_conflict",{detail:{message:e,sessionId:this.session_id}});window.dispatchEvent(t),setTimeout((()=>{window.opener||1===window.history.length?window.close():window.location.href="about:blank"}),2e3)}generateInstanceKey(){return`${this.address[0]}_${this.address[1]}`}initialize(){if(super.initialize(),this.instance_key=this.generateInstanceKey(),this.session_storage_key=`cubevis_datapipe_${this.instance_key}`,!this.checkSessionConflict())return;let e=`ws://${this.address[0]}:${this.address[1]}`;console.log("datapipe url:",e);var s=void 0;document.shutdown_in_progress_=!1;var t=()=>{void 0!==this.websocket&&this.websocket.close(),this.websocket=new WebSocket(e),this.websocket.binaryType="arraybuffer",this.websocket.addEventListener("error",(e=>{console.log("error encountered:",e)})),this.websocket.onmessage=e=>{if("string"==typeof e.data||e.data instanceof String){let s=(0,c.deserialize)(e.data);if("id"in s&&"direction"in s&&"message"in s){let{id:e,message:t,direction:i}=s;if("error"===i&&("session_conflict"===e||e===this.session_id)&&t&&("session_conflict"===t.type||"session_corruption"===t.type||"close_duplicate"===t.action))return void this.handleSessionConflictMessage(t);if(void 0===t&&console.log("Error, event failure",s),"j2p"==i)if(e in this.pending){let{cb:i}=this.pending[e];if(delete this.pending[e],e in this.send_queue&&this.send_queue[e].length>0){let{cb:s,msg:t}=this.send_queue[e].shift();this.pending[e]={cb:s},this.websocket.send((0,c.serialize)(t))}void 0===t?console.log("DROPPING ERROR FOR NOW (maybe need error callbacks)",s):i(t)}else console.log("message received but could not find id");else if(e in this.incoming_callbacks){let s=this.incoming_callbacks[e](t);this.websocket.send((0,c.serialize)({id:e,direction:i,message:s,session:this.session_id}))}}else console.log(`datapipe received message without one of 'id', 'message' or 'direction': ${s}`)}else console.log("datapipe received binary data",e.data.byteLength,"bytes")},this.websocket.onopen=()=>{for(s?0==s.connected&&console.log(`connection reestablished at ${new Date}`):(this.websocket.send((0,c.serialize)({id:"initialize",direction:"j2p",session:this.session_id})),this.startHeartbeat()),s=new casalib.ReconnectState;this.connection_queue.length>0;){let e=this.connection_queue.shift();this.send.apply(e[0],e[1])}},this.websocket.onclose=()=>{if(s&&1==s.connected&&(console.log(`connection lost at ${new Date}`),s.connected=!1,!document.shutdown_in_progress_)){console.log(`connection lost at ${new Date}`);var e=s;function i(n){0==s.connected&&(console.log(`${n+1}\treconnection attempt ${new Date}`),t(),e.backoff(),e.retries>0?setTimeout(i,e.timeout,n+1):0==s.connected&&console.log(`aborting reconnection after ${n} attempts ${new Date}`))}i(0)}}};window.addEventListener("beforeunload",(()=>{this.cleanupSession()})),document.addEventListener("visibilitychange",(()=>{"hidden"===document.visibilityState?this.stopHeartbeat():"visible"===document.visibilityState&&(this.updateSessionHeartbeat(),this.startHeartbeat())})),t();(()=>{null!=this.init_script&&(0,r.execute)(this.init_script,this)})()}register(e,s){this.incoming_callbacks[e]=s}send(e,s,t,i=!1){let n={id:e,message:s,direction:"j2p",session:this.session_id};if(!this.websocket||e in this.pending)if(e in this.send_queue)if("boolean"==typeof i&&i&&this.send_queue[e].length>0)this.send_queue[e][0].msg=n,this.send_queue[e][0].cb=t;else if("function"==typeof i&&this.send_queue[e].length>0){let o=!1;for(const a of this.send_queue[e])i(a.msg.message)&&(a.msg=n,a.cb=t,o=!0);o||this.send_queue[e].push({cb:t,msg:n})}else this.send_queue[e].push({cb:t,msg:n});else this.send_queue[e]=[{cb:t,msg:n}];else if(this.websocket.readyState===WebSocket.CONNECTING)this.connection_queue.push([this,[e,s,t]]);else if(e in this.send_queue&&this.send_queue[e].length>0){this.send_queue[e].push({cb:t,msg:n});{let{cb:r,msg:d}=this.send_queue[e].shift();if(this.pending[e]={cb:r},this.websocket.readyState===WebSocket.OPEN)this.websocket.send((0,c.serialize)(d));else{let l=20,h=this;function u(){h.websocket.readyState===WebSocket.OPEN?h.websocket.send((0,c.serialize)(d)):(l-=1,l>0&&setTimeout(u,3e3))}setTimeout(u,3e3)}}}else if(this.websocket.readyState===WebSocket.OPEN)this.pending[e]={cb:t},this.websocket.send((0,c.serialize)(n));else{let b=20,_=this;function g(){_.websocket.readyState===WebSocket.OPEN?(_.pending[e]={cb:t},_.websocket.send((0,c.serialize)(n))):(b-=1,b>0&&setTimeout(g,3e3))}setTimeout(g,3e3)}}}t.DataPipe=d,o=d,d.__name__="DataPipe",d.__module__="cubevis.bokeh.sources._data_pipe",o.define((({Any:e,Tuple:s,String:t,Number:i})=>({init_script:[e,null],address:[s(t,i)]})))},
47
47
  "e3901fa9f2": function _(e,r,s,i,o){i();const l=e("@bokehjs/base"),a=e("@bokehjs/core/resolvers"),t=e("@bokehjs/core/serialization/deserializer"),n=e("@bokehjs/core/serialization/serializer"),{deserialize:c}=new class{constructor(){this.resolver=new a.ModelResolver(l.default_resolver),this.deserializer=new t.Deserializer(this.resolver),this.deserialize=e=>{try{return this.deserializer.decode(JSON.parse(e))}catch(r){return console.group("deserialize error"),console.log(e),console.log(r),console.groupEnd(),{}}}}};s.deserialize=c;const{serialize:z}=new class{constructor(){this.serializer=new n.Serializer,this.serialize=e=>JSON.stringify(this.serializer.encode(e))}};s.serialize=z},
48
- "2889e0dd45": function _(i,e,s,t,n){var a;t();const o=i("@bokehjs/models/sources/column_data_source"),d=i("b33e822163");class r extends d.DataPipe{constructor(i){super(i),this.position={},this._wcs=null}initialize(){super.initialize(),this.fits_header_json&&(this._wcs=new casalib.coordtxl.WCSTransform(new casalib.coordtxl.MapKeywordProvider(JSON.parse(this.fits_header_json))))}channel(i,e,s){this.position[s]={index:i};let t={action:"channel",index:i,id:s};super.send(this.dataid,t,(i=>{null!=this._histogram_source&&"hist"in i&&"top"in i.hist&&"bottom"in i.hist&&"left"in i.hist&&"right"in i.hist&&(this._histogram_source.data=i.hist),e(i)}))}spectrum(i,e,s,t=!1){let n={action:"spectrum",index:i,id:s};super.send(this.dataid,n,e,t)}adjust_colormap(i,e,s,t,n=!1){const a={action:"adjust-colormap",bounds:i,transfer:e,id:t};super.send(this.dataid,a,s,n)}refresh(i,e,s=[0,0]){let{index:t}=e in this.position?this.position[e]:{index:s};if(2===t.length){let s={action:"channel",index:t,id:e};super.send(this.dataid,s,i)}else if(3===t.length){let s={action:"spectrum",index:t,id:e};super.send(this.dataid,s,i)}}wcs(){return this._wcs}}s.ImagePipe=r,a=r,r.__name__="ImagePipe",r.__module__="cubevis.bokeh.sources._image_pipe",a.define((({Number:i,Nullable:e,String:s,Tuple:t,Ref:n})=>({dataid:[s],shape:[t(i,i,i,i)],fits_header_json:[e(s),null],_histogram_source:[e(n(o.ColumnDataSource)),null]})))},
48
+ "2889e0dd45": function _(i,e,s,t,n){var a;t();const o=i("@bokehjs/models/sources/column_data_source"),d=i("33e5a43d86");class r extends d.DataPipe{constructor(i){super(i),this.position={},this._wcs=null}initialize(){super.initialize(),this.fits_header_json&&(this._wcs=new casalib.coordtxl.WCSTransform(new casalib.coordtxl.MapKeywordProvider(JSON.parse(this.fits_header_json))))}channel(i,e,s){this.position[s]={index:i};let t={action:"channel",index:i,id:s};super.send(this.dataid,t,(i=>{null!=this._histogram_source&&"hist"in i&&"top"in i.hist&&"bottom"in i.hist&&"left"in i.hist&&"right"in i.hist&&(this._histogram_source.data=i.hist),e(i)}))}spectrum(i,e,s,t=!1){let n={action:"spectrum",index:i,id:s};super.send(this.dataid,n,e,t)}adjust_colormap(i,e,s,t,n=!1){const a={action:"adjust-colormap",bounds:i,transfer:e,id:t};super.send(this.dataid,a,s,n)}refresh(i,e,s=[0,0]){let{index:t}=e in this.position?this.position[e]:{index:s};if(2===t.length){let s={action:"channel",index:t,id:e};super.send(this.dataid,s,i)}else if(3===t.length){let s={action:"spectrum",index:t,id:e};super.send(this.dataid,s,i)}}wcs(){return this._wcs}}s.ImagePipe=r,a=r,r.__name__="ImagePipe",r.__module__="cubevis.bokeh.sources._image_pipe",a.define((({Number:i,Nullable:e,String:s,Tuple:t,Ref:n})=>({dataid:[s],shape:[t(i,i,i,i)],fits_header_json:[e(s),null],_histogram_source:[e(n(o.ColumnDataSource)),null]})))},
49
49
  "de65005924": function _(s,a,t,c,i){var o;c();const e=s("@bokehjs/models/sources/column_data_source"),n=s("@bokehjs/core/util/string"),u=s("2889e0dd45"),h=s("@bokehjs/core/util/callbacks");class r extends e.ColumnDataSource{constructor(s){super(s),this.imid=(0,n.uuid4)()}_mask_contour(s){const a=casalib.d3.contours().size(this.image_source.shape.slice(0,2)).thresholds([1])(s[0])[0].coordinates.map((s=>s.map((s=>s.reduce(((s,a)=>(s[0].push(a[0]),s[1].push(a[1]),s)),[[],[]])))));return{xs:[a.map((s=>s.map((s=>s[0]))))],ys:[a.map((s=>s.map((s=>s[1]))))]}}initialize(){if(super.initialize(),null!=this._mask_contour_source&&"msk"in this.data&&this.data.msk.length>0&&this.data.msk[0].length>0){const s=this.data.msk;this._mask_contour_source.data=this._mask_contour(s)}void 0===this.last_chan&&(this.last_chan=[this.cur_chan[0].valueOf(),this.cur_chan[1].valueOf()]);(()=>{null!=this.init_script&&(0,h.execute)(this.init_script,this)})()}channel(s,a=0,t){this.image_source.channel([a,s],(c=>{void 0!==c&&void 0!==c.chan||console.log("ImageDataSource ERROR ENCOUNTERED <1>",c),this.last_chan=[this.cur_chan[0].valueOf(),this.cur_chan[1].valueOf()],this.cur_chan=[a,s],null!=this._mask_contour_source&&"chan"in c&&"msk"in c.chan&&(c.msk_contour=this._mask_contour(c.chan.msk),this._mask_contour_source.data=c.msk_contour),t&&t(c),this.data=c.chan}),this.imid)}adjust_colormap(s,a,t){this.image_source.adjust_colormap(s,a,t,this.imid,!0)}signal_change(){this.change.emit()}refresh(s){this.image_source.refresh((a=>{void 0!==a&&void 0!==a.chan||console.log("ImageDataSource ERROR ENCOUNTERED <2>",a),null!=this._mask_contour_source&&"chan"in a&&"msk"in a.chan&&(a.msk_contour=this._mask_contour(a.chan.msk),this._mask_contour_source.data=a.msk_contour),s&&s(a),this.data=a.chan}),this.imid,[0,0])}wcs(){return this.image_source.wcs()}}t.ImageDataSource=r,o=r,r.__name__="ImageDataSource",r.__module__="cubevis.bokeh.sources._image_data_source",o.define((({Tuple:s,Number:a,Ref:t,Nullable:c,Any:i})=>({init_script:[i,null],image_source:[t(u.ImagePipe)],_mask_contour_source:[c(t(e.ColumnDataSource)),null],num_chans:[s(a,a)],cur_chan:[s(a,a)]})))},
50
50
  "02e3c3e46c": function _(e,s,i,t,r){var a;t();const c=e("@bokehjs/models/sources/column_data_source"),u=e("@bokehjs/core/util/string"),o=e("2889e0dd45");class _ extends c.ColumnDataSource{constructor(e){super(e),this.imid=(0,u.uuid4)()}initialize(){super.initialize()}spectra(e,s,i=0,t=!1){this.image_source.spectrum([e,s,i],(e=>this.data=e.spectrum),this.imid,t)}refresh(){this.image_source.refresh((e=>this.data=e.spectrum),this.imid,[0,0,0])}}i.SpectraDataSource=_,a=_,_.__name__="SpectraDataSource",_.__module__="cubevis.bokeh.sources._spectra_data_source",a.define((({Ref:e})=>({image_source:[e(o.ImagePipe)]})))},
51
- "64b16deff9": function _(e,s,i,t,a){var n;t();const u=e("@bokehjs/models/sources/column_data_source"),l=e("b33e822163"),o=e("@bokehjs/core/util/callbacks");class c extends u.ColumnDataSource{constructor(e){super(e)}send(e,s){this.pipe.send(this.session_id.valueOf(),{action:"callback",message:e},(e=>{s("result"in e?e.result:{error:`expected to find a "result" in "${e}"`,msg:e})}))}initialize(){super.initialize();(()=>{null!=this.js_init&&(0,o.execute)(this.js_init,this)})()}}i.UpdatableDataSource=c,n=c,c.__name__="UpdatableDataSource",c.__module__="cubevis.bokeh.sources._updatable_data_source",n.define((({Ref:e,Any:s,String:i})=>({js_init:[s,null],js_update:[s,null],pipe:[e(l.DataPipe)],session_id:[i]})))},
51
+ "64b16deff9": function _(e,s,i,t,a){var n;t();const u=e("@bokehjs/models/sources/column_data_source"),l=e("33e5a43d86"),o=e("@bokehjs/core/util/callbacks");class c extends u.ColumnDataSource{constructor(e){super(e)}send(e,s){this.pipe.send(this.session_id.valueOf(),{action:"callback",message:e},(e=>{s("result"in e?e.result:{error:`expected to find a "result" in "${e}"`,msg:e})}))}initialize(){super.initialize();(()=>{null!=this.js_init&&(0,o.execute)(this.js_init,this)})()}}i.UpdatableDataSource=c,n=c,c.__name__="UpdatableDataSource",c.__module__="cubevis.bokeh.sources._updatable_data_source",n.define((({Ref:e,Any:s,String:i})=>({js_init:[s,null],js_update:[s,null],pipe:[e(l.DataPipe)],session_id:[i]})))},
52
52
  "b6ae454f0d": function _(s,i,o,t,e){var r;t();const a=s("@bokehjs/models/formatters/tick_formatter"),c=s("de65005924");class l extends a.TickFormatter{constructor(s){super(s),this._axis=null,this._coord="world"}initialize(){super.initialize(),"x"==this.axis||"X"==this.axis||"y"==this.axis||"Y"==this.axis?this._axis="x"==this.axis||"X"==this.axis?"x":"y":console.log("ERROR: WcsTicks formatter created with invalid axis:",this.axis)}doFormat(s){const i=[];if(this._axis&&this.image_source.wcs()&&"world"==this._coord)for(let o=0,t=s.length;o<t;o++)if("x"==this._axis){const t=new casalib.coordtxl.Point2D(Number(s[o]),0);this.image_source.wcs().imageToWorldCoords(t,!1),i.push(new casalib.coordtxl.WorldCoords(t.getX(),t.getY()).format(2e3)[0])}else{const t=new casalib.coordtxl.Point2D(0,Number(s[o]));this.image_source.wcs().imageToWorldCoords(t,!1),i.push(new casalib.coordtxl.WorldCoords(t.getX(),t.getY()).format(2e3)[1])}else for(let o=0,t=s.length;o<t;o++)i.push(""+s[o]);return i}coordinates(s){return s!=this._coord&&("world"!=s&&"pixel"!=s||(this._coord=s)),this._coord}}o.WcsTicks=l,r=l,l.__name__="WcsTicks",l.__module__="cubevis.bokeh.format._wcs_ticks",r.define((({Ref:s,String:i})=>({axis:[i],image_source:[s(c.ImageDataSource)]})))},
53
53
  "cb7d28d6b3": function _(i,e,t,o,s){var d;o();const l=i("@bokehjs/models/tools/gestures/gesture_tool"),r=i("949501ff1c"),_=i("15b954190c"),m=i("@bokehjs/core/util/callbacks");class n extends l.GestureToolView{_pan_start(i){var e;null===(e=this.model.document)||void 0===e||e.interactive_start(this.plot_view.model);const t=(0,_.px_from_sx)(this.plot_view,i.sx),o=(0,_.py_from_sy)(this.plot_view,i.sy),s=(0,_.dx_from_px)(this.plot_view,t),d=(0,_.dy_from_py)(this.plot_view,o),{start:l}=this.model;l?(0,m.execute)(l,this.model,{sx:t,sy:o,x:s,y:d,delta_x:i.dx,delta_y:-i.dy,shift:"modifiers"in i?i.modifiers.shift:void 0,ctrl:"modifiers"in i?i.modifiers.ctrl:void 0,alt:"modifiers"in i?i.modifiers.alt:void 0}):this.model.trigger_event(new r.DragStart(t,o,s,d,i.dx,-i.dy,i.modifiers))}_pan(i){var e;null===(e=this.model.document)||void 0===e||e.interactive_start(this.plot_view.model);const t=(0,_.px_from_sx)(this.plot_view,i.sx),o=(0,_.py_from_sy)(this.plot_view,i.sy),s=(0,_.dx_from_px)(this.plot_view,t),d=(0,_.dy_from_py)(this.plot_view,o),{move:l}=this.model;l?(0,m.execute)(l,this.model,{sx:t,sy:o,x:s,y:d,delta_x:i.dx,delta_y:-i.dy,shift:"modifiers"in i?i.modifiers.shift:void 0,ctrl:"modifiers"in i?i.modifiers.ctrl:void 0,alt:"modifiers"in i?i.modifiers.alt:void 0}):this.model.trigger_event(new r.Drag(t,o,s,d,i.dx,-i.dy,i.modifiers))}_pan_end(i){const e=(0,_.px_from_sx)(this.plot_view,i.sx),t=(0,_.py_from_sy)(this.plot_view,i.sy),o=(0,_.dx_from_px)(this.plot_view,e),s=(0,_.dy_from_py)(this.plot_view,t),{end:d}=this.model;d?(0,m.execute)(d,this.model,{sx:e,sy:t,x:o,y:s,delta_x:i.dx,delta_y:-i.dy,shift:"modifiers"in i?i.modifiers.shift:void 0,ctrl:"modifiers"in i?i.modifiers.ctrl:void 0,alt:"modifiers"in i?i.modifiers.alt:void 0}):this.model.trigger_event(new r.DragEnd(e,t,o,s,i.dx,-i.dy,i.modifiers))}}t.DragToolView=n,n.__name__="DragToolView";class a extends l.GestureTool{constructor(i){super(i),this.tool_name="Drag",this.event_type="pan",this.default_order=10}}t.DragTool=a,d=a,a.__name__="DragTool",a.__module__="cubevis.bokeh.tools._drag_tool",d.prototype.default_view=n,d.define((({Any:i,Nullable:e})=>({start:[e(i),null],move:[e(i),null],end:[e(i),null]})))},
54
54
  "949501ff1c": function _(e,t,a,s,n){s();const _=e("@bokehjs/core/bokeh_events");class r extends _.Pan{}a.Drag=r,r.__name__="Drag";class l extends _.PanStart{constructor(e,t,a,s,n,_,r){super(e,t,a,s,r),this.delta_x=n,this.delta_y=_}get event_values(){const{delta_x:e,delta_y:t}=this;return Object.assign(Object.assign({},super.event_values),{delta_x:e,delta_y:t})}}a.DragStart=l,l.__name__="DragStart";class d extends _.PanEnd{constructor(e,t,a,s,n,_,r){super(e,t,a,s,r),this.delta_x=n,this.delta_y=_}get event_values(){const{delta_x:e,delta_y:t}=this;return Object.assign(Object.assign({},super.event_values),{delta_x:e,delta_y:t})}}a.DragEnd=d,d.__name__="DragEnd"},
@@ -60,4 +60,4 @@
60
60
  "b55081402e": function _(e,n,t,_,s){var a;_();const o=e("@bokehjs/models/annotations/span"),p=e("@bokehjs/core/bokeh_events");class r extends o.SpanView{on_pan_start(e){const n=super.on_pan_start(e);return this.model.trigger_event(new p.LODStart),n}on_pan(e){super.on_pan(e)}on_pan_end(e){super.on_pan_end(e),this.model.trigger_event(new p.LODEnd)}}t.EditSpanView=r,r.__name__="EditSpanView";class d extends o.Span{constructor(e){super(e)}}t.EditSpan=d,a=d,d.__name__="EditSpan",d.__module__="cubevis.bokeh.models._edit_span",a.prototype.default_view=r},
61
61
  "9144bfc7a5": function _(e,t,s,n,r){var i;n();const l=e("@bokehjs/models/widgets/text_input"),o=e("@bokehjs/core/dom"),u=e("@bokehjs/core/bokeh_events");class _ extends l.TextInputView{stylesheets(){return[...super.stylesheets(),new o.InlineStyleSheet(".bk-input-prefix { padding: 0 var(--padding-vertical); }")]}connect_signals(){super.connect_signals(),this.el.addEventListener("mouseenter",(e=>{this.model.trigger_event(new u.MouseEnter(e.screenX,e.screenY,e.x,e.y,{shift:e.shiftKey,ctrl:e.ctrlKey,alt:e.altKey}))})),this.el.addEventListener("mouseleave",(e=>{this.model.trigger_event(new u.MouseLeave(e.screenX,e.screenY,e.x,e.y,{shift:e.shiftKey,ctrl:e.ctrlKey,alt:e.altKey}))}))}render(){super.render()}}s.EvTextInputView=_,_.__name__="EvTextInputView";class c extends l.TextInput{constructor(e){super(e)}}s.EvTextInput=c,i=c,c.__name__="EvTextInput",c.__module__="cubevis.bokeh.models._ev_text_input",i.prototype.default_view=_},
62
62
  "74e0abef8a": function _(e,t,n,s,o){var i;s();const r=e("@bokehjs/models/annotations/poly_annotation"),a=e("@bokehjs/core/bokeh_events");class _ extends r.PolyAnnotationView{on_enter(e){const{x_scale:t,y_scale:n}=this.plot_view.frame,s=new a.MouseEnter(e.sx,e.sy,t.invert(e.sx),n.invert(e.sy),{shift:e.modifiers.shift,ctrl:e.modifiers.ctrl,alt:e.modifiers.alt}),o=super.on_enter(e);return this.model.trigger_event(s),o}on_leave(e){const{x_scale:t,y_scale:n}=this.plot_view.frame,s=new a.MouseLeave(e.sx,e.sy,t.invert(e.sx),n.invert(e.sy),{shift:e.modifiers.shift,ctrl:e.modifiers.ctrl,alt:e.modifiers.alt});super.on_leave(e),this.model.trigger_event(s)}on_pan_start(e){const{x_scale:t,y_scale:n}=this.plot_view.frame,s=new a.PanStart(e.sx,e.sy,t.invert(e.sx),n.invert(e.sy),{shift:e.modifiers.shift,ctrl:e.modifiers.ctrl,alt:e.modifiers.alt}),o=super.on_pan_start(e);return this.model.trigger_event(s),o}on_pan_end(e){const{x_scale:t,y_scale:n}=this.plot_view.frame,s=new a.PanEnd(e.sx,e.sy,t.invert(e.sx),n.invert(e.sy),{shift:e.modifiers.shift,ctrl:e.modifiers.ctrl,alt:e.modifiers.alt});super.on_pan_end(e),this.model.trigger_event(s)}on_pan(e){super.on_pan(e);const t=new a.RangesUpdate(e.sx,e.sx+e.dx,e.sy,e.sy+e.dy);this.model.trigger_event(t)}}n.EvPolyAnnotationView=_,_.__name__="EvPolyAnnotationView";class l extends r.PolyAnnotation{constructor(e){super(e)}}n.EvPolyAnnotation=l,i=l,l.__name__="EvPolyAnnotation",l.__module__="cubevis.bokeh.annotations._ev_poly_annotation",i.prototype.default_view=_},
63
- }, "97397933dc", {"index":"97397933dc","src/bokeh/sources/data_pipe":"b33e822163","src/bokeh/util/conversions":"e3901fa9f2","src/bokeh/sources/image_pipe":"2889e0dd45","src/bokeh/sources/image_data_source":"de65005924","src/bokeh/sources/spectra_data_source":"02e3c3e46c","src/bokeh/sources/updatable_data_source":"64b16deff9","src/bokeh/format/wcs_ticks":"b6ae454f0d","src/bokeh/tools/drag_tool":"cb7d28d6b3","src/bokeh/events":"949501ff1c","src/bokeh/util/find":"15b954190c","src/bokeh/tools/cbreset_tool":"01959f25a3","src/bokeh/models/tip_button":"9f961622ce","src/bokeh/models/tip":"ca4c845905","src/bokeh/models/shared_dict":"50a1e32f01","src/bokeh/models/edit_span":"b55081402e","src/bokeh/models/ev_text_input":"9144bfc7a5","src/bokeh/annotations/ev_poly_annotation":"74e0abef8a"}, {});});
63
+ }, "97397933dc", {"index":"97397933dc","src/bokeh/sources/data_pipe":"33e5a43d86","src/bokeh/util/conversions":"e3901fa9f2","src/bokeh/sources/image_pipe":"2889e0dd45","src/bokeh/sources/image_data_source":"de65005924","src/bokeh/sources/spectra_data_source":"02e3c3e46c","src/bokeh/sources/updatable_data_source":"64b16deff9","src/bokeh/format/wcs_ticks":"b6ae454f0d","src/bokeh/tools/drag_tool":"cb7d28d6b3","src/bokeh/events":"949501ff1c","src/bokeh/util/find":"15b954190c","src/bokeh/tools/cbreset_tool":"01959f25a3","src/bokeh/models/tip_button":"9f961622ce","src/bokeh/models/tip":"ca4c845905","src/bokeh/models/shared_dict":"50a1e32f01","src/bokeh/models/edit_span":"b55081402e","src/bokeh/models/ev_text_input":"9144bfc7a5","src/bokeh/annotations/ev_poly_annotation":"74e0abef8a"}, {});});
@@ -34,6 +34,8 @@ import inspect
34
34
  import threading
35
35
  import asyncio
36
36
  import traceback
37
+ import time
38
+ import json
37
39
 
38
40
  from bokeh.models.sources import DataSource
39
41
  from bokeh.util.compiler import TypeScript
@@ -69,6 +71,10 @@ class DataPipe(DataSource):
69
71
 
70
72
  address = Tuple( String, Int, help="two integer sequence representing the address and port to use for the websocket" )
71
73
 
74
+ # Class-level session tracking to prevent multiple connections
75
+ _active_sessions = {} # session_id -> {'websocket': ws, 'timestamp': time, 'datapipe': instance}
76
+ _session_lock = threading.Lock()
77
+
72
78
  __javascript__ = [ casalib_url( ), cubevisjs_url( ) ]
73
79
 
74
80
  def __init__( self, *args, abort=None, **kwargs ):
@@ -115,6 +121,73 @@ class DataPipe(DataSource):
115
121
  return result
116
122
  return None
117
123
 
124
+ @classmethod
125
+ async def __is_websocket_alive(cls, websocket):
126
+ """Check if a websocket connection is still alive"""
127
+ try:
128
+ # Try to ping the websocket with a short timeout
129
+ await asyncio.wait_for(websocket.ping(), timeout=2.0)
130
+ return True
131
+ except (asyncio.TimeoutError, ConnectionError, Exception):
132
+ return False
133
+
134
+ @classmethod
135
+ def __cleanup_dead_sessions(cls):
136
+ """Remove dead sessions from tracking"""
137
+ current_time = time.time()
138
+ dead_sessions = []
139
+
140
+ for session_id, session_info in cls._active_sessions.items():
141
+ # Remove sessions older than 5 minutes (stale cleanup)
142
+ if current_time - session_info['timestamp'] > 300:
143
+ dead_sessions.append(session_id)
144
+
145
+ for session_id in dead_sessions:
146
+ del cls._active_sessions[session_id]
147
+
148
+ async def __handle_session_conflict(self, websocket, existing_session_info, new_session_id):
149
+ """Handle session conflict by checking if existing connection is alive"""
150
+ existing_ws = existing_session_info['websocket']
151
+ existing_datapipe = existing_session_info['datapipe']
152
+
153
+ # Check if existing websocket is still alive
154
+ if await self.__is_websocket_alive(existing_ws):
155
+ # Existing connection is alive - send conflict message to BOTH connections
156
+ conflict_msg = {
157
+ 'id': 'session_conflict',
158
+ 'message': {
159
+ 'type': 'session_conflict',
160
+ 'error': 'Multiple windows/tabs detected. Please use only one browser window.',
161
+ 'action': 'close_duplicate'
162
+ },
163
+ 'direction': 'error'
164
+ }
165
+
166
+ try:
167
+ # Send to existing connection
168
+ await existing_ws.send(serialize(conflict_msg))
169
+
170
+ # Send to new connection
171
+ await websocket.send(serialize(conflict_msg))
172
+
173
+ # Close the new connection
174
+ await websocket.close(code=1008, reason='Session conflict')
175
+
176
+ # Call abort on the existing DataPipe instance
177
+ if existing_datapipe.__abort is not None:
178
+ err = RuntimeError(f"Session conflict detected: New connection attempted with session {new_session_id}")
179
+ existing_datapipe.__abort(err)
180
+
181
+ return False # Reject new connection
182
+
183
+ except Exception as e:
184
+ print(f"Error handling session conflict: {e}")
185
+ # If we can't communicate, treat existing as dead
186
+ pass
187
+
188
+ # Existing connection is dead - replace it
189
+ return True # Allow new connection
190
+
118
191
  def register( self, ident, callback ):
119
192
  """Register a callback to handle all requests coming from JavaScript. The
120
193
  callback will be called whenever a request arrives.
@@ -175,6 +248,8 @@ class DataPipe(DataSource):
175
248
  """
176
249
  try:
177
250
  self.__websocket = websocket
251
+ session_established = False
252
+
178
253
  async for message in websocket:
179
254
  msg = deserialize(message)
180
255
  if 'session' not in msg:
@@ -185,8 +260,53 @@ class DataPipe(DataSource):
185
260
  else:
186
261
  raise err
187
262
  return
263
+
264
+ # Handle session initialization with conflict detection
265
+ if not session_established:
266
+ new_session_id = msg['session']
267
+
268
+ with self._session_lock:
269
+ # Clean up any stale sessions first
270
+ self.__cleanup_dead_sessions()
271
+
272
+ # Check if session already exists
273
+ if new_session_id in self._active_sessions:
274
+ existing_session_info = self._active_sessions[new_session_id]
275
+
276
+ # Handle the conflict
277
+ if not await self.__handle_session_conflict(websocket, existing_session_info, new_session_id):
278
+ return # Connection was rejected
279
+
280
+ # Register this session as active
281
+ self._active_sessions[new_session_id] = {
282
+ 'websocket': websocket,
283
+ 'timestamp': time.time(),
284
+ 'datapipe': self
285
+ }
286
+ self.__session = new_session_id
287
+ session_established = True
288
+
289
+ # Existing session validation (your original logic)
188
290
  elif self.__session != None and self.__session != msg['session']:
189
- await self.__websocket.close( )
291
+ conflict_msg = {
292
+ 'id': self.__session,
293
+ 'message': {
294
+ 'type': 'session_corruption',
295
+ 'error': 'Session corruption detected',
296
+ 'expected': self.__session,
297
+ 'received': msg['session']
298
+ },
299
+ 'direction': 'error'
300
+ }
301
+
302
+ await self.__websocket.send(serialize(conflict_msg))
303
+ await self.__websocket.close()
304
+
305
+ # Clean up from active sessions
306
+ with self._session_lock:
307
+ if self.__session in self._active_sessions:
308
+ del self._active_sessions[self.__session]
309
+
190
310
  err = RuntimeError(f"session corruption: {msg['session']} does not equal {self.__session}")
191
311
  if self.__abort is not None:
192
312
  self.__abort( err )
@@ -196,10 +316,11 @@ class DataPipe(DataSource):
196
316
 
197
317
  with self.__lock:
198
318
  ###
199
- ### initialize session identifier
319
+ ### initialize session identifier (pre-state-corruption fixes)
200
320
  ###
201
321
  if self.__session == None:
202
322
  self.__session = msg['session']
323
+
203
324
  if msg['direction'] == 'p2j':
204
325
  cb = self.__get_pending(msg['id'])
205
326
  outgo = self.__dequeue_send(msg['id'])
@@ -255,4 +376,9 @@ class DataPipe(DataSource):
255
376
  'exception': repr(e) },
256
377
  'direction': str(msg['direction']) } ) )
257
378
  finally:
379
+ # Clean up when connection closes
380
+ if self.__session is not None:
381
+ with self._session_lock:
382
+ if self.__session in self._active_sessions:
383
+ del self._active_sessions[self.__session]
258
384
  self.__websocket = None
@@ -16,7 +16,7 @@ dependencies = [
16
16
  ]
17
17
  requires-python = ">=3.10"
18
18
  readme = "readme.rst"
19
- version = "0.5.27"
19
+ version = "0.5.28"
20
20
 
21
21
  [project.license]
22
22
  text = "LGPL"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes