scitex 2.7.3__py3-none-any.whl → 2.10.0__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.
- scitex/__init__.py +15 -7
- scitex/__version__.py +1 -2
- scitex/_install_guide.py +250 -0
- scitex/_optional_deps.py +206 -39
- scitex/ai/_gen_ai/_Groq.py +2 -4
- scitex/ai/_gen_ai/_OpenAI.py +5 -2
- scitex/ai/_gen_ai/_Perplexity.py +20 -6
- scitex/audio/__init__.py +24 -15
- scitex/audio/_cross_process_lock.py +139 -0
- scitex/audio/_mcp_handlers.py +256 -0
- scitex/audio/_mcp_tool_schemas.py +203 -0
- scitex/audio/engines/elevenlabs_engine.py +5 -2
- scitex/audio/mcp_server.py +98 -457
- scitex/bridge/__init__.py +30 -19
- scitex/bridge/_figrecipe.py +245 -0
- scitex/bridge/_helpers.py +2 -1
- scitex/bridge/_plt_vis.py +23 -10
- scitex/bridge/_stats_plt.py +18 -5
- scitex/bridge/_stats_vis.py +16 -2
- scitex/browser/__init__.py +84 -44
- scitex/browser/automation/__init__.py +5 -1
- scitex/browser/core/BrowserMixin.py +17 -4
- scitex/browser/core/__init__.py +11 -2
- scitex/browser/remote/CaptchaHandler.py +1 -1
- scitex/browser/remote/ZenRowsAPIClient.py +1 -1
- scitex/capture/grid.py +487 -0
- scitex/capture/mcp_handlers.py +401 -0
- scitex/capture/mcp_tool_defs.py +192 -0
- scitex/capture/mcp_tools.py +241 -0
- scitex/capture/mcp_utils.py +30 -0
- scitex/cli/convert.py +421 -0
- scitex/cli/main.py +6 -4
- scitex/datetime/__init__.py +46 -0
- scitex/datetime/_linspace.py +100 -0
- scitex/datetime/_normalize_timestamp.py +306 -0
- scitex/db/_delete_duplicates.py +4 -4
- scitex/db/_sqlite3/_delete_duplicates.py +11 -2
- scitex/dev/plt/__init__.py +61 -62
- scitex/dev/plt/demo_plotters/__init__.py +0 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
- scitex/dev/plt/mpl/get_dir_ax.py +46 -0
- scitex/dev/plt/mpl/get_signatures.py +176 -0
- scitex/dev/plt/mpl/get_signatures_details.py +522 -0
- scitex/dev/plt/plot_mpl_axhline.py +0 -0
- scitex/dev/plt/plot_mpl_axhspan.py +0 -0
- scitex/dev/plt/plot_mpl_axvline.py +0 -0
- scitex/dev/plt/plot_mpl_axvspan.py +0 -0
- scitex/dev/plt/plot_mpl_bar.py +0 -0
- scitex/dev/plt/plot_mpl_barh.py +0 -0
- scitex/dev/plt/plot_mpl_boxplot.py +0 -0
- scitex/dev/plt/plot_mpl_contour.py +0 -0
- scitex/dev/plt/plot_mpl_contourf.py +0 -0
- scitex/dev/plt/plot_mpl_errorbar.py +0 -0
- scitex/dev/plt/plot_mpl_eventplot.py +0 -0
- scitex/dev/plt/plot_mpl_fill.py +0 -0
- scitex/dev/plt/plot_mpl_fill_between.py +0 -0
- scitex/dev/plt/plot_mpl_hexbin.py +0 -0
- scitex/dev/plt/plot_mpl_hist.py +0 -0
- scitex/dev/plt/plot_mpl_hist2d.py +0 -0
- scitex/dev/plt/plot_mpl_imshow.py +0 -0
- scitex/dev/plt/plot_mpl_pcolormesh.py +0 -0
- scitex/dev/plt/plot_mpl_pie.py +0 -0
- scitex/dev/plt/plot_mpl_plot.py +0 -0
- scitex/dev/plt/plot_mpl_quiver.py +0 -0
- scitex/dev/plt/plot_mpl_scatter.py +0 -0
- scitex/dev/plt/plot_mpl_stackplot.py +0 -0
- scitex/dev/plt/plot_mpl_stem.py +0 -0
- scitex/dev/plt/plot_mpl_step.py +0 -0
- scitex/dev/plt/plot_mpl_violinplot.py +0 -0
- scitex/dev/plt/plot_sns_barplot.py +0 -0
- scitex/dev/plt/plot_sns_boxplot.py +0 -0
- scitex/dev/plt/plot_sns_heatmap.py +0 -0
- scitex/dev/plt/plot_sns_histplot.py +0 -0
- scitex/dev/plt/plot_sns_kdeplot.py +0 -0
- scitex/dev/plt/plot_sns_lineplot.py +0 -0
- scitex/dev/plt/plot_sns_scatterplot.py +0 -0
- scitex/dev/plt/plot_sns_stripplot.py +0 -0
- scitex/dev/plt/plot_sns_swarmplot.py +0 -0
- scitex/dev/plt/plot_sns_violinplot.py +0 -0
- scitex/dev/plt/plot_stx_bar.py +0 -0
- scitex/dev/plt/plot_stx_barh.py +0 -0
- scitex/dev/plt/plot_stx_box.py +0 -0
- scitex/dev/plt/plot_stx_boxplot.py +0 -0
- scitex/dev/plt/plot_stx_conf_mat.py +0 -0
- scitex/dev/plt/plot_stx_contour.py +0 -0
- scitex/dev/plt/plot_stx_ecdf.py +0 -0
- scitex/dev/plt/plot_stx_errorbar.py +0 -0
- scitex/dev/plt/plot_stx_fill_between.py +0 -0
- scitex/dev/plt/plot_stx_fillv.py +0 -0
- scitex/dev/plt/plot_stx_heatmap.py +0 -0
- scitex/dev/plt/plot_stx_image.py +0 -0
- scitex/dev/plt/plot_stx_imshow.py +0 -0
- scitex/dev/plt/plot_stx_joyplot.py +0 -0
- scitex/dev/plt/plot_stx_kde.py +0 -0
- scitex/dev/plt/plot_stx_line.py +0 -0
- scitex/dev/plt/plot_stx_mean_ci.py +0 -0
- scitex/dev/plt/plot_stx_mean_std.py +0 -0
- scitex/dev/plt/plot_stx_median_iqr.py +0 -0
- scitex/dev/plt/plot_stx_raster.py +0 -0
- scitex/dev/plt/plot_stx_rectangle.py +0 -0
- scitex/dev/plt/plot_stx_scatter.py +0 -0
- scitex/dev/plt/plot_stx_shaded_line.py +0 -0
- scitex/dev/plt/plot_stx_violin.py +0 -0
- scitex/dev/plt/plot_stx_violinplot.py +0 -0
- scitex/diagram/README.md +197 -0
- scitex/diagram/__init__.py +48 -0
- scitex/diagram/_compile.py +312 -0
- scitex/diagram/_diagram.py +355 -0
- scitex/diagram/_presets.py +173 -0
- scitex/diagram/_schema.py +182 -0
- scitex/diagram/_split.py +278 -0
- scitex/dict/_pop_keys.py +1 -7
- scitex/dsp/__init__.py +15 -10
- scitex/dsp/add_noise.py +5 -2
- scitex/dsp/example.py +35 -22
- scitex/dsp/filt.py +8 -3
- scitex/dsp/reference.py +3 -2
- scitex/dsp/utils/__init__.py +2 -1
- scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
- scitex/dt/__init__.py +39 -2
- scitex/errors.py +82 -521
- scitex/fig/__init__.py +4 -4
- scitex/fig/editor/__init__.py +5 -2
- scitex/fig/editor/_dearpygui_editor.py +1 -1
- scitex/fig/editor/_mpl_editor.py +1 -1
- scitex/fig/editor/_qt_editor.py +1 -1
- scitex/fig/editor/_tkinter_editor.py +1 -1
- scitex/fig/editor/edit/__init__.py +50 -0
- scitex/fig/editor/edit/backend_detector.py +109 -0
- scitex/fig/editor/edit/bundle_resolver.py +240 -0
- scitex/fig/editor/edit/editor_launcher.py +239 -0
- scitex/fig/editor/edit/manual_handler.py +53 -0
- scitex/fig/editor/edit/panel_loader.py +232 -0
- scitex/fig/editor/edit/path_resolver.py +67 -0
- scitex/fig/editor/flask_editor/_bbox.py +23 -0
- scitex/fig/editor/flask_editor/_core.py +908 -103
- scitex/fig/editor/flask_editor/_renderer.py +74 -0
- scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
- scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
- scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
- scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
- scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
- scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
- scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
- scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
- scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
- scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
- scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
- scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
- scitex/fig/editor/flask_editor/static/css/index.css +31 -0
- scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
- scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
- scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
- scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
- scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
- scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
- scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
- scitex/fig/editor/flask_editor/static/js/main.js +426 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
- scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
- scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
- scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
- scitex/fig/editor/flask_editor/templates/__init__.py +95 -5
- scitex/fig/editor/flask_editor/templates/_html.py +27 -9
- scitex/fig/editor/flask_editor/templates/_scripts.py +1928 -131
- scitex/fig/editor/flask_editor/templates/_styles.py +363 -51
- scitex/fig/io/_bundle.py +104 -19
- scitex/fts/README.md +262 -0
- scitex/fts/TODO.md +66 -0
- scitex/fts/__init__.py +90 -0
- scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
- scitex/fts/_bundle/_FTS.py +657 -0
- scitex/fts/_bundle/__init__.py +38 -0
- scitex/fts/_bundle/_children.py +216 -0
- scitex/fts/_bundle/_conversion/__init__.py +15 -0
- scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
- scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
- scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
- scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
- scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
- scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
- scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
- scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
- scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
- scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
- scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
- scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
- scitex/fts/_bundle/_extractors/__init__.py +32 -0
- scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
- scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
- scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
- scitex/fts/_bundle/_loader.py +134 -0
- scitex/fts/_bundle/_mpl_helpers.py +389 -0
- scitex/fts/_bundle/_saver.py +269 -0
- scitex/fts/_bundle/_storage.py +200 -0
- scitex/fts/_bundle/_utils/__init__.py +55 -0
- scitex/fts/_bundle/_utils/_const.py +26 -0
- scitex/fts/_bundle/_utils/_errors.py +73 -0
- scitex/fts/_bundle/_utils/_generate.py +21 -0
- scitex/fts/_bundle/_utils/_types.py +76 -0
- scitex/fts/_bundle/_validation.py +434 -0
- scitex/fts/_bundle/_zipbundle.py +165 -0
- scitex/fts/_fig/__init__.py +22 -0
- scitex/fts/_fig/_backend/__init__.py +53 -0
- scitex/fts/_fig/_backend/_export.py +165 -0
- scitex/fts/_fig/_backend/_parser.py +188 -0
- scitex/fts/_fig/_backend/_render.py +538 -0
- scitex/fts/_fig/_composite.py +345 -0
- scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
- scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_fig/_dataclasses/__init__.py +47 -0
- scitex/fts/_fig/_editor/__init__.py +14 -0
- scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
- scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
- scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
- scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
- scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
- scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
- scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
- scitex/fts/_fig/_editor/_defaults.py +300 -0
- scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
- scitex/fts/_fig/_models/_Annotations.py +115 -0
- scitex/fts/_fig/_models/_Axes.py +152 -0
- scitex/fts/_fig/_models/_Figure.py +138 -0
- scitex/fts/_fig/_models/_Guides.py +104 -0
- scitex/fts/_fig/_models/_Plot.py +123 -0
- scitex/fts/_fig/_models/_Styles.py +245 -0
- scitex/fts/_fig/_models/__init__.py +80 -0
- scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
- scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
- scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
- scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_fig/_utils/__init__.py +129 -0
- scitex/fts/_fig/_utils/_auto_layout.py +127 -0
- scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
- scitex/fts/_fig/_utils/_const_sizes.py +48 -0
- scitex/fts/_fig/_utils/_convert_coords.py +77 -0
- scitex/fts/_fig/_utils/_get_template.py +178 -0
- scitex/fts/_fig/_utils/_normalize.py +73 -0
- scitex/fts/_fig/_utils/_plot_layout.py +397 -0
- scitex/fts/_fig/_utils/_validate.py +197 -0
- scitex/fts/_kinds/__init__.py +45 -0
- scitex/fts/_kinds/_figure/__init__.py +19 -0
- scitex/fts/_kinds/_figure/_composite.py +345 -0
- scitex/fts/_kinds/_plot/__init__.py +25 -0
- scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
- scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
- scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
- scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
- scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
- scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
- scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
- scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
- scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
- scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
- scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
- scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
- scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
- scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
- scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
- scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
- scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
- scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
- scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
- scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
- scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
- scitex/fts/_kinds/_shape/__init__.py +141 -0
- scitex/fts/_kinds/_stats/__init__.py +56 -0
- scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_kinds/_table/__init__.py +72 -0
- scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
- scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
- scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
- scitex/fts/_kinds/_table/_latex/_export.py +279 -0
- scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
- scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
- scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
- scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
- scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
- scitex/fts/_kinds/_text/__init__.py +77 -0
- scitex/fts/_schemas/data_info.schema.json +75 -0
- scitex/fts/_schemas/encoding.schema.json +90 -0
- scitex/fts/_schemas/node.schema.json +145 -0
- scitex/fts/_schemas/render_manifest.schema.json +62 -0
- scitex/fts/_schemas/stats.schema.json +132 -0
- scitex/fts/_schemas/theme.schema.json +141 -0
- scitex/fts/_stats/__init__.py +48 -0
- scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_tables/__init__.py +65 -0
- scitex/fts/_tables/_latex/__init__.py +93 -0
- scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
- scitex/fts/_tables/_latex/_editor/_app.py +725 -0
- scitex/fts/_tables/_latex/_export.py +279 -0
- scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
- scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
- scitex/fts/_tables/_latex/_table_exporter.py +362 -0
- scitex/fts/_tables/_latex/_utils.py +369 -0
- scitex/fts/_tables/_latex/_validator.py +445 -0
- scitex/gen/__init__.py +66 -25
- scitex/gen/misc.py +28 -0
- scitex/io/__init__.py +47 -20
- scitex/io/_load.py +87 -36
- scitex/io/_load_modules/__init__.py +10 -7
- scitex/io/_load_modules/_pandas.py +6 -1
- scitex/io/_save.py +299 -1556
- scitex/io/_save_modules/__init__.py +76 -19
- scitex/io/_save_modules/_figure_utils.py +90 -0
- scitex/io/_save_modules/_image_csv.py +497 -0
- scitex/io/_save_modules/_legends.py +91 -0
- scitex/io/_save_modules/_pltz_bundle.py +356 -0
- scitex/io/_save_modules/_pltz_stx.py +536 -0
- scitex/io/_save_modules/_stx_bundle.py +104 -0
- scitex/io/_save_modules/_symlink.py +96 -0
- scitex/io/_save_modules/_yaml.py +1 -1
- scitex/io/_save_modules/_zarr.py +64 -18
- scitex/io/bundle/README.md +212 -0
- scitex/io/bundle/__init__.py +110 -0
- scitex/io/{_bundle.py → bundle/_core.py} +219 -89
- scitex/io/bundle/_nested.py +713 -0
- scitex/io/bundle/_types.py +74 -0
- scitex/io/bundle/_zip.py +487 -0
- scitex/io/utils/h5_to_zarr.py +1 -1
- scitex/logging/__init__.py +108 -13
- scitex/logging/_errors.py +508 -0
- scitex/logging/_formatters.py +30 -6
- scitex/logging/_warnings.py +261 -0
- scitex/plt/__init__.py +4 -1
- scitex/plt/_figrecipe.py +236 -0
- scitex/plt/_subplots/_AxisWrapper.py +6 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
- scitex/plt/_subplots/_FigWrapper.py +15 -0
- scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
- scitex/plt/_subplots/_export_as_csv.py +11 -0
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
- scitex/plt/_subplots/_fonts.py +71 -0
- scitex/plt/_subplots/_mm_layout.py +282 -0
- scitex/plt/gallery/__init__.py +99 -2
- scitex/plt/io/_layered_bundle.py +0 -0
- scitex/plt/styles/_plot_postprocess.py +3 -1
- scitex/plt/utils/_configure_mpl.py +16 -19
- scitex/repro/_RandomStateManager.py +13 -8
- scitex/resource/__init__.py +19 -1
- scitex/resource/_utils/_get_env_info.py +13 -25
- scitex/schema/__init__.py +149 -160
- scitex/schema/_encoding.py +273 -0
- scitex/schema/_figure_elements.py +406 -0
- scitex/schema/_plot.py +0 -0
- scitex/schema/_theme.py +360 -0
- scitex/schema/_validation.py +0 -98
- scitex/scholar/__init__.py +56 -14
- scitex/scholar/auth/ScholarAuthManager.py +1 -1
- scitex/scholar/auth/__init__.py +11 -2
- scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
- scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
- scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
- scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
- scitex/scholar/config/ScholarConfig.py +1 -1
- scitex/scholar/core/Scholar.py +1 -1
- scitex/session/_decorator.py +18 -16
- scitex/session/_lifecycle.py +9 -11
- scitex/session/template.py +9 -8
- scitex/sh/test_sh.py +72 -0
- scitex/sh/test_sh_simple.py +61 -0
- scitex/stats/__init__.py +221 -97
- scitex/stats/_schema.py +21 -22
- scitex/stats/descriptive/_circular.py +212 -351
- scitex/stats/descriptive/_describe.py +81 -132
- scitex/stats/descriptive/_nan.py +205 -433
- scitex/stats/descriptive/_real.py +127 -141
- scitex/str/_format_plot_text.py +5 -5
- scitex/str/_latex.py +26 -84
- scitex/str/_latex_fallback.py +53 -47
- scitex/web/_search_pubmed.py +5 -4
- scitex/writer/tests/test_diff_between.py +451 -0
- scitex/writer/tests/test_document_section.py +311 -0
- scitex/writer/tests/test_document_workflow.py +393 -0
- scitex/writer/tests/test_writer.py +361 -0
- scitex/writer/tests/test_writer_integration.py +303 -0
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/METADATA +364 -181
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/RECORD +479 -108
- scitex/fig/editor/_edit.py +0 -751
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/WHEEL +0 -0
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/entry_points.txt +0 -0
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/licenses/LICENSE +0 -0
scitex/fig/editor/_edit.py
DELETED
|
@@ -1,751 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
# File: ./src/scitex/vis/editor/_edit.py
|
|
4
|
-
"""Main edit function for launching visual editor."""
|
|
5
|
-
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Union, Optional, Literal
|
|
8
|
-
import hashlib
|
|
9
|
-
import json
|
|
10
|
-
import warnings
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _print_available_backends():
|
|
14
|
-
"""Print available backends status."""
|
|
15
|
-
backends = {
|
|
16
|
-
"flask": ["flask"],
|
|
17
|
-
"dearpygui": ["dearpygui"],
|
|
18
|
-
"qt": ["PyQt6", "PyQt5", "PySide6", "PySide2"],
|
|
19
|
-
"tkinter": ["tkinter"],
|
|
20
|
-
"mpl": ["matplotlib"],
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
print("\n" + "=" * 50)
|
|
24
|
-
print("SciTeX Visual Editor - Available Backends")
|
|
25
|
-
print("=" * 50)
|
|
26
|
-
|
|
27
|
-
for backend, packages in backends.items():
|
|
28
|
-
available = False
|
|
29
|
-
available_pkg = None
|
|
30
|
-
for pkg in packages:
|
|
31
|
-
try:
|
|
32
|
-
__import__(pkg)
|
|
33
|
-
available = True
|
|
34
|
-
available_pkg = pkg
|
|
35
|
-
break
|
|
36
|
-
except ImportError:
|
|
37
|
-
pass
|
|
38
|
-
|
|
39
|
-
status = f"[OK] {available_pkg}" if available else "[NOT INSTALLED]"
|
|
40
|
-
print(f" {backend:12s}: {status}")
|
|
41
|
-
|
|
42
|
-
print("=" * 50)
|
|
43
|
-
print("Install: pip install scitex[gui]")
|
|
44
|
-
print("=" * 50 + "\n")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def edit(
|
|
48
|
-
path: Union[str, Path],
|
|
49
|
-
backend: Literal["auto", "flask", "dearpygui", "qt", "tkinter", "mpl"] = "auto",
|
|
50
|
-
apply_manual: bool = True,
|
|
51
|
-
port: int = 5050,
|
|
52
|
-
) -> None:
|
|
53
|
-
"""
|
|
54
|
-
Launch interactive editor for figure style/annotation editing.
|
|
55
|
-
|
|
56
|
-
Parameters
|
|
57
|
-
----------
|
|
58
|
-
path : str or Path
|
|
59
|
-
Path to figure file. Can be:
|
|
60
|
-
- .pltz.d directory bundle (recommended for hitmap selection)
|
|
61
|
-
- .pltz ZIP bundle
|
|
62
|
-
- JSON file (figure.json or figure.manual.json)
|
|
63
|
-
- CSV file (figure.csv) - for data-only start
|
|
64
|
-
- PNG file (figure.png)
|
|
65
|
-
Will auto-detect sibling files in same directory or organized subdirectories.
|
|
66
|
-
backend : str, optional
|
|
67
|
-
GUI backend to use (default: "auto"):
|
|
68
|
-
- "auto": Pick best available with graceful degradation
|
|
69
|
-
(flask -> dearpygui -> qt -> tkinter -> mpl)
|
|
70
|
-
- "flask": Browser-based editor (Flask, modern UI)
|
|
71
|
-
- "dearpygui": GPU-accelerated modern GUI (fast, requires dearpygui)
|
|
72
|
-
- "qt": Rich desktop editor (requires PyQt5/6 or PySide2/6)
|
|
73
|
-
- "tkinter": Built-in Python GUI (works everywhere)
|
|
74
|
-
- "mpl": Minimal matplotlib interactive mode (always works)
|
|
75
|
-
apply_manual : bool, optional
|
|
76
|
-
If True, load .manual.json overrides if exists (default: True)
|
|
77
|
-
port : int, optional
|
|
78
|
-
Port number for web-based editors (flask). Default: 5050.
|
|
79
|
-
If port is in use, will attempt to free it or find an alternative.
|
|
80
|
-
|
|
81
|
-
Returns
|
|
82
|
-
-------
|
|
83
|
-
None
|
|
84
|
-
Editor runs in GUI event loop. Changes saved to .manual.json.
|
|
85
|
-
|
|
86
|
-
Examples
|
|
87
|
-
--------
|
|
88
|
-
>>> import scitex as stx
|
|
89
|
-
>>> stx.fig.edit("output/myplot.pltz.d") # Edit pltz bundle with hitmap selection
|
|
90
|
-
>>> stx.fig.edit("output/myplot.pltz") # Also works with ZIP bundles
|
|
91
|
-
>>> stx.fig.edit("output/figure.json") # Auto-select best backend
|
|
92
|
-
>>> stx.fig.edit("output/figure.png", backend="flask") # Force flask editor
|
|
93
|
-
|
|
94
|
-
Notes
|
|
95
|
-
-----
|
|
96
|
-
- Changes are saved to `{basename}.manual.json` alongside the original
|
|
97
|
-
- Manual JSON includes hash of base JSON for staleness detection
|
|
98
|
-
- Original JSON/CSV files are never modified
|
|
99
|
-
- Backend auto-detection order: flask > dearpygui > qt > tkinter > mpl
|
|
100
|
-
- For .pltz bundles, hitmap-based element selection is available
|
|
101
|
-
"""
|
|
102
|
-
path = Path(path)
|
|
103
|
-
spath = str(path)
|
|
104
|
-
parent_str = str(path.parent) if path.is_file() else ""
|
|
105
|
-
|
|
106
|
-
# Panel info for multi-panel figures
|
|
107
|
-
panel_info = None
|
|
108
|
-
|
|
109
|
-
# Check if this is a .figz bundle (multi-panel figure)
|
|
110
|
-
if spath.endswith('.figz.d') or spath.endswith('.figz'):
|
|
111
|
-
json_path, csv_path, png_path, hitmap_path, bundle_spec, panel_info = _resolve_figz_bundle(path)
|
|
112
|
-
# Check if this is a .pltz bundle (single panel) - either the directory or a file inside it
|
|
113
|
-
elif spath.endswith('.pltz.d') or spath.endswith('.pltz') or parent_str.endswith('.pltz.d'):
|
|
114
|
-
# If it's a file inside .pltz.d, use the parent directory
|
|
115
|
-
bundle_path = path.parent if parent_str.endswith('.pltz.d') else path
|
|
116
|
-
json_path, csv_path, png_path, hitmap_path, bundle_spec = _resolve_pltz_bundle(bundle_path)
|
|
117
|
-
# Check if a JSON/CSV/PNG file is inside a .figz.d (passed individual panel file)
|
|
118
|
-
elif parent_str.endswith('.figz.d') or (path.parent.parent and str(path.parent.parent).endswith('.figz.d')):
|
|
119
|
-
# File is inside figz bundle, resolve from figz root
|
|
120
|
-
figz_path = path.parent if parent_str.endswith('.figz.d') else path.parent.parent
|
|
121
|
-
json_path, csv_path, png_path, hitmap_path, bundle_spec, panel_info = _resolve_figz_bundle(figz_path)
|
|
122
|
-
else:
|
|
123
|
-
# Resolve paths (JSON, CSV, PNG)
|
|
124
|
-
json_path, csv_path, png_path = _resolve_figure_paths(path)
|
|
125
|
-
hitmap_path = None
|
|
126
|
-
bundle_spec = None
|
|
127
|
-
|
|
128
|
-
if not json_path.exists():
|
|
129
|
-
raise FileNotFoundError(f"JSON file not found: {json_path}")
|
|
130
|
-
|
|
131
|
-
# Load data
|
|
132
|
-
import scitex as stx
|
|
133
|
-
|
|
134
|
-
metadata = bundle_spec if bundle_spec else stx.io.load(json_path)
|
|
135
|
-
csv_data = None
|
|
136
|
-
if csv_path and csv_path.exists():
|
|
137
|
-
csv_data = stx.io.load(csv_path)
|
|
138
|
-
|
|
139
|
-
# Load manual overrides if exists
|
|
140
|
-
manual_path = json_path.with_suffix(".manual.json")
|
|
141
|
-
manual_overrides = None
|
|
142
|
-
if apply_manual and manual_path.exists():
|
|
143
|
-
manual_data = stx.io.load(manual_path)
|
|
144
|
-
manual_overrides = manual_data.get("overrides", {})
|
|
145
|
-
|
|
146
|
-
# Resolve backend if "auto"
|
|
147
|
-
if backend == "auto":
|
|
148
|
-
backend = _detect_best_backend()
|
|
149
|
-
|
|
150
|
-
# Print status
|
|
151
|
-
_print_available_backends()
|
|
152
|
-
print(f"Launching {backend} editor for: {json_path}")
|
|
153
|
-
|
|
154
|
-
# Launch appropriate backend
|
|
155
|
-
if backend == "flask":
|
|
156
|
-
try:
|
|
157
|
-
from ._flask_editor import WebEditor
|
|
158
|
-
|
|
159
|
-
editor = WebEditor(
|
|
160
|
-
json_path=json_path,
|
|
161
|
-
metadata=metadata,
|
|
162
|
-
csv_data=csv_data,
|
|
163
|
-
png_path=png_path,
|
|
164
|
-
hitmap_path=hitmap_path,
|
|
165
|
-
manual_overrides=manual_overrides,
|
|
166
|
-
port=port,
|
|
167
|
-
panel_info=panel_info,
|
|
168
|
-
)
|
|
169
|
-
editor.run()
|
|
170
|
-
except ImportError as e:
|
|
171
|
-
raise ImportError(
|
|
172
|
-
"Flask backend requires Flask. Install with: pip install flask"
|
|
173
|
-
) from e
|
|
174
|
-
elif backend == "dearpygui":
|
|
175
|
-
try:
|
|
176
|
-
from ._dearpygui_editor import DearPyGuiEditor
|
|
177
|
-
|
|
178
|
-
editor = DearPyGuiEditor(
|
|
179
|
-
json_path=json_path,
|
|
180
|
-
metadata=metadata,
|
|
181
|
-
csv_data=csv_data,
|
|
182
|
-
png_path=png_path,
|
|
183
|
-
manual_overrides=manual_overrides,
|
|
184
|
-
)
|
|
185
|
-
editor.run()
|
|
186
|
-
except ImportError as e:
|
|
187
|
-
raise ImportError(
|
|
188
|
-
"DearPyGui backend requires dearpygui. "
|
|
189
|
-
"Install with: pip install dearpygui"
|
|
190
|
-
) from e
|
|
191
|
-
elif backend == "qt":
|
|
192
|
-
try:
|
|
193
|
-
from ._qt_editor import QtEditor
|
|
194
|
-
|
|
195
|
-
editor = QtEditor(
|
|
196
|
-
json_path=json_path,
|
|
197
|
-
metadata=metadata,
|
|
198
|
-
csv_data=csv_data,
|
|
199
|
-
png_path=png_path,
|
|
200
|
-
manual_overrides=manual_overrides,
|
|
201
|
-
hitmap_path=hitmap_path,
|
|
202
|
-
bundle_spec=bundle_spec,
|
|
203
|
-
)
|
|
204
|
-
editor.run()
|
|
205
|
-
except ImportError as e:
|
|
206
|
-
raise ImportError(
|
|
207
|
-
"Qt backend requires PyQt5/PyQt6 or PySide2/PySide6. "
|
|
208
|
-
"Install with: pip install PyQt6"
|
|
209
|
-
) from e
|
|
210
|
-
elif backend == "tkinter":
|
|
211
|
-
from ._tkinter_editor import TkinterEditor
|
|
212
|
-
|
|
213
|
-
editor = TkinterEditor(
|
|
214
|
-
json_path=json_path,
|
|
215
|
-
metadata=metadata,
|
|
216
|
-
csv_data=csv_data,
|
|
217
|
-
manual_overrides=manual_overrides,
|
|
218
|
-
)
|
|
219
|
-
editor.run()
|
|
220
|
-
elif backend == "mpl":
|
|
221
|
-
from ._mpl_editor import MplEditor
|
|
222
|
-
|
|
223
|
-
editor = MplEditor(
|
|
224
|
-
json_path=json_path,
|
|
225
|
-
metadata=metadata,
|
|
226
|
-
csv_data=csv_data,
|
|
227
|
-
manual_overrides=manual_overrides,
|
|
228
|
-
)
|
|
229
|
-
editor.run()
|
|
230
|
-
else:
|
|
231
|
-
raise ValueError(
|
|
232
|
-
f"Unknown backend: {backend}. "
|
|
233
|
-
"Use 'auto', 'flask', 'dearpygui', 'qt', 'tkinter', or 'mpl'."
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def _detect_best_backend() -> str:
|
|
238
|
-
"""
|
|
239
|
-
Detect the best available GUI backend with graceful degradation.
|
|
240
|
-
|
|
241
|
-
Order: flask > dearpygui > qt > tkinter > mpl
|
|
242
|
-
Shows warnings when falling back to less capable backends.
|
|
243
|
-
"""
|
|
244
|
-
import warnings
|
|
245
|
-
|
|
246
|
-
# Try Flask - best for modern UI
|
|
247
|
-
try:
|
|
248
|
-
import flask
|
|
249
|
-
|
|
250
|
-
return "flask"
|
|
251
|
-
except ImportError:
|
|
252
|
-
pass
|
|
253
|
-
|
|
254
|
-
# Try DearPyGui - GPU-accelerated, modern
|
|
255
|
-
try:
|
|
256
|
-
import dearpygui
|
|
257
|
-
|
|
258
|
-
return "dearpygui"
|
|
259
|
-
except ImportError:
|
|
260
|
-
warnings.warn(
|
|
261
|
-
"Flask not available. Consider: pip install flask\nTrying DearPyGui..."
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
# Try DearPyGui
|
|
265
|
-
try:
|
|
266
|
-
import dearpygui
|
|
267
|
-
|
|
268
|
-
return "dearpygui"
|
|
269
|
-
except ImportError:
|
|
270
|
-
pass
|
|
271
|
-
|
|
272
|
-
# Try Qt (richest desktop features)
|
|
273
|
-
qt_available = False
|
|
274
|
-
try:
|
|
275
|
-
import PyQt6
|
|
276
|
-
|
|
277
|
-
qt_available = True
|
|
278
|
-
except ImportError:
|
|
279
|
-
pass
|
|
280
|
-
if not qt_available:
|
|
281
|
-
try:
|
|
282
|
-
import PyQt5
|
|
283
|
-
|
|
284
|
-
qt_available = True
|
|
285
|
-
except ImportError:
|
|
286
|
-
pass
|
|
287
|
-
if not qt_available:
|
|
288
|
-
try:
|
|
289
|
-
import PySide6
|
|
290
|
-
|
|
291
|
-
qt_available = True
|
|
292
|
-
except ImportError:
|
|
293
|
-
pass
|
|
294
|
-
if not qt_available:
|
|
295
|
-
try:
|
|
296
|
-
import PySide2
|
|
297
|
-
|
|
298
|
-
qt_available = True
|
|
299
|
-
except ImportError:
|
|
300
|
-
pass
|
|
301
|
-
|
|
302
|
-
if qt_available:
|
|
303
|
-
warnings.warn(
|
|
304
|
-
"DearPyGui not available. Consider: pip install dearpygui\n"
|
|
305
|
-
"Using Qt backend instead."
|
|
306
|
-
)
|
|
307
|
-
return "qt"
|
|
308
|
-
|
|
309
|
-
# Try Tkinter (built-in, good features)
|
|
310
|
-
try:
|
|
311
|
-
import tkinter
|
|
312
|
-
|
|
313
|
-
warnings.warn(
|
|
314
|
-
"Qt not available. Consider: pip install PyQt6\n"
|
|
315
|
-
"Using Tkinter backend (basic features)."
|
|
316
|
-
)
|
|
317
|
-
return "tkinter"
|
|
318
|
-
except ImportError:
|
|
319
|
-
pass
|
|
320
|
-
|
|
321
|
-
# Fall back to matplotlib interactive (always works)
|
|
322
|
-
warnings.warn(
|
|
323
|
-
"No GUI toolkit found. Using minimal matplotlib editor.\n"
|
|
324
|
-
"For better experience, install: pip install flask (web) or pip install PyQt6 (desktop)"
|
|
325
|
-
)
|
|
326
|
-
return "mpl"
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
def _resolve_figure_paths(path: Path) -> tuple:
|
|
330
|
-
"""
|
|
331
|
-
Resolve JSON, CSV, and PNG paths from any input file path.
|
|
332
|
-
|
|
333
|
-
Handles two patterns:
|
|
334
|
-
1. Flat (sibling): path/to/figure.{json,csv,png}
|
|
335
|
-
2. Organized (subdirs): path/to/{json,csv,png}/figure.{ext}
|
|
336
|
-
|
|
337
|
-
Parameters
|
|
338
|
-
----------
|
|
339
|
-
path : Path
|
|
340
|
-
Input path (can be JSON, CSV, or PNG)
|
|
341
|
-
|
|
342
|
-
Returns
|
|
343
|
-
-------
|
|
344
|
-
tuple
|
|
345
|
-
(json_path, csv_path, png_path) - csv_path/png_path may be None if not found
|
|
346
|
-
"""
|
|
347
|
-
path = Path(path)
|
|
348
|
-
stem = path.stem
|
|
349
|
-
parent = path.parent
|
|
350
|
-
|
|
351
|
-
# Check if this is organized pattern (parent is json/, csv/, png/)
|
|
352
|
-
if parent.name in ("json", "csv", "png"):
|
|
353
|
-
base_dir = parent.parent
|
|
354
|
-
json_path = base_dir / "json" / f"{stem}.json"
|
|
355
|
-
csv_path = base_dir / "csv" / f"{stem}.csv"
|
|
356
|
-
png_path = base_dir / "png" / f"{stem}.png"
|
|
357
|
-
else:
|
|
358
|
-
# Flat pattern - sibling files
|
|
359
|
-
json_path = parent / f"{stem}.json"
|
|
360
|
-
csv_path = parent / f"{stem}.csv"
|
|
361
|
-
png_path = parent / f"{stem}.png"
|
|
362
|
-
|
|
363
|
-
# If input was .manual.json, get base json
|
|
364
|
-
if stem.endswith(".manual"):
|
|
365
|
-
base_stem = stem[:-7] # Remove '.manual'
|
|
366
|
-
if parent.name == "json":
|
|
367
|
-
json_path = parent / f"{base_stem}.json"
|
|
368
|
-
csv_path = parent.parent / "csv" / f"{base_stem}.csv"
|
|
369
|
-
png_path = parent.parent / "png" / f"{base_stem}.png"
|
|
370
|
-
else:
|
|
371
|
-
json_path = parent / f"{base_stem}.json"
|
|
372
|
-
csv_path = parent / f"{base_stem}.csv"
|
|
373
|
-
png_path = parent / f"{base_stem}.png"
|
|
374
|
-
|
|
375
|
-
return (
|
|
376
|
-
json_path,
|
|
377
|
-
csv_path if csv_path.exists() else None,
|
|
378
|
-
png_path if png_path.exists() else None,
|
|
379
|
-
)
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
def _resolve_figz_bundle(path: Path, panel_index: int = 0) -> tuple:
|
|
383
|
-
"""
|
|
384
|
-
Resolve paths from a .figz bundle (multi-panel figure).
|
|
385
|
-
|
|
386
|
-
Parameters
|
|
387
|
-
----------
|
|
388
|
-
path : Path
|
|
389
|
-
Path to .figz bundle (.figz or .figz.d)
|
|
390
|
-
panel_index : int, optional
|
|
391
|
-
Index of panel to open (default: 0 for first panel)
|
|
392
|
-
|
|
393
|
-
Returns
|
|
394
|
-
-------
|
|
395
|
-
tuple
|
|
396
|
-
(json_path, csv_path, png_path, hitmap_path, bundle_spec, panel_info)
|
|
397
|
-
panel_info is a dict with keys: panels, current_index, figz_dir
|
|
398
|
-
"""
|
|
399
|
-
import json as json_module
|
|
400
|
-
import tempfile
|
|
401
|
-
import zipfile
|
|
402
|
-
|
|
403
|
-
spath = str(path)
|
|
404
|
-
|
|
405
|
-
# Handle ZIP vs directory
|
|
406
|
-
if spath.endswith('.figz') and not spath.endswith('.figz.d'):
|
|
407
|
-
# It's a ZIP - extract to temp directory
|
|
408
|
-
if not path.exists():
|
|
409
|
-
raise FileNotFoundError(f"Figz bundle not found: {path}")
|
|
410
|
-
temp_dir = tempfile.mkdtemp(prefix='scitex_edit_figz_')
|
|
411
|
-
with zipfile.ZipFile(path, 'r') as zf:
|
|
412
|
-
zf.extractall(temp_dir)
|
|
413
|
-
bundle_dir = Path(temp_dir)
|
|
414
|
-
for item in bundle_dir.iterdir():
|
|
415
|
-
if item.is_dir() and str(item).endswith('.figz.d'):
|
|
416
|
-
bundle_dir = item
|
|
417
|
-
break
|
|
418
|
-
else:
|
|
419
|
-
bundle_dir = Path(path)
|
|
420
|
-
if not bundle_dir.exists():
|
|
421
|
-
raise FileNotFoundError(f"Figz bundle directory not found: {bundle_dir}")
|
|
422
|
-
|
|
423
|
-
# Find nested pltz bundles
|
|
424
|
-
pltz_dirs = sorted([d for d in bundle_dir.iterdir()
|
|
425
|
-
if d.is_dir() and str(d).endswith('.pltz.d')])
|
|
426
|
-
|
|
427
|
-
if not pltz_dirs:
|
|
428
|
-
raise FileNotFoundError(f"No .pltz.d panels found in figz bundle: {bundle_dir}")
|
|
429
|
-
|
|
430
|
-
# Validate panel index
|
|
431
|
-
if panel_index < 0 or panel_index >= len(pltz_dirs):
|
|
432
|
-
panel_index = 0
|
|
433
|
-
|
|
434
|
-
selected_panel = pltz_dirs[panel_index]
|
|
435
|
-
print(f"Opening panel: {selected_panel.name}")
|
|
436
|
-
if len(pltz_dirs) > 1:
|
|
437
|
-
print(f" (Figz contains {len(pltz_dirs)} panels: {[d.name for d in pltz_dirs]})")
|
|
438
|
-
|
|
439
|
-
# Build panel info for editor
|
|
440
|
-
panel_info = {
|
|
441
|
-
"panels": [d.name for d in pltz_dirs],
|
|
442
|
-
"current_index": panel_index,
|
|
443
|
-
"figz_dir": str(bundle_dir),
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
# Delegate to pltz resolver
|
|
447
|
-
result = _resolve_pltz_bundle(selected_panel)
|
|
448
|
-
# Append panel_info to the result
|
|
449
|
-
return result + (panel_info,)
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
def _resolve_pltz_bundle(path: Path) -> tuple:
|
|
453
|
-
"""
|
|
454
|
-
Resolve paths from a .pltz bundle (directory or ZIP).
|
|
455
|
-
|
|
456
|
-
Supports both:
|
|
457
|
-
- Legacy format (single {basename}.json)
|
|
458
|
-
- Layered format v2.0 (spec.json + style.json + cache/)
|
|
459
|
-
|
|
460
|
-
Parameters
|
|
461
|
-
----------
|
|
462
|
-
path : Path
|
|
463
|
-
Path to .pltz bundle (.pltz or .pltz.d)
|
|
464
|
-
|
|
465
|
-
Returns
|
|
466
|
-
-------
|
|
467
|
-
tuple
|
|
468
|
-
(json_path, csv_path, png_path, hitmap_path, bundle_spec)
|
|
469
|
-
"""
|
|
470
|
-
import json as json_module
|
|
471
|
-
import tempfile
|
|
472
|
-
import zipfile
|
|
473
|
-
|
|
474
|
-
spath = str(path)
|
|
475
|
-
|
|
476
|
-
# Handle ZIP vs directory
|
|
477
|
-
if spath.endswith('.pltz') and not spath.endswith('.pltz.d'):
|
|
478
|
-
# It's a ZIP - extract to temp directory
|
|
479
|
-
if not path.exists():
|
|
480
|
-
raise FileNotFoundError(f"Bundle not found: {path}")
|
|
481
|
-
temp_dir = tempfile.mkdtemp(prefix='scitex_edit_')
|
|
482
|
-
with zipfile.ZipFile(path, 'r') as zf:
|
|
483
|
-
zf.extractall(temp_dir)
|
|
484
|
-
# Find the .pltz.d directory inside
|
|
485
|
-
bundle_dir = Path(temp_dir)
|
|
486
|
-
for item in bundle_dir.iterdir():
|
|
487
|
-
if item.is_dir() and str(item).endswith('.pltz.d'):
|
|
488
|
-
bundle_dir = item
|
|
489
|
-
break
|
|
490
|
-
else:
|
|
491
|
-
# It's already a directory
|
|
492
|
-
bundle_dir = Path(path)
|
|
493
|
-
if not bundle_dir.exists():
|
|
494
|
-
raise FileNotFoundError(f"Bundle directory not found: {bundle_dir}")
|
|
495
|
-
|
|
496
|
-
# Check if this is a layered bundle (v2.0)
|
|
497
|
-
spec_path = bundle_dir / "spec.json"
|
|
498
|
-
if spec_path.exists():
|
|
499
|
-
return _resolve_layered_pltz_bundle(bundle_dir)
|
|
500
|
-
|
|
501
|
-
# === Legacy format below ===
|
|
502
|
-
# Find files by pattern (supports basename-based naming)
|
|
503
|
-
json_path = None
|
|
504
|
-
csv_path = None
|
|
505
|
-
png_path = None
|
|
506
|
-
svg_path = None
|
|
507
|
-
hitmap_path = None
|
|
508
|
-
bundle_spec = None
|
|
509
|
-
|
|
510
|
-
for f in bundle_dir.iterdir():
|
|
511
|
-
name = f.name
|
|
512
|
-
if name.endswith('.json') and not name.endswith('.manual.json'):
|
|
513
|
-
json_path = f
|
|
514
|
-
elif name.endswith('.csv'):
|
|
515
|
-
csv_path = f
|
|
516
|
-
elif name.endswith('_hitmap.png'):
|
|
517
|
-
hitmap_path = f
|
|
518
|
-
elif name.endswith('.svg') and '_hitmap' not in name:
|
|
519
|
-
svg_path = f
|
|
520
|
-
elif name.endswith('.png') and '_hitmap' not in name and '_overview' not in name:
|
|
521
|
-
png_path = f
|
|
522
|
-
|
|
523
|
-
# Prefer SVG for display (contains complete figure with all data)
|
|
524
|
-
# PNG (panel_A.png) is transparent overlay without line data
|
|
525
|
-
# Coordinate mapping is handled by _extract_bboxes_from_metadata with actual display dimensions
|
|
526
|
-
if svg_path:
|
|
527
|
-
png_path = svg_path # Use SVG for display
|
|
528
|
-
|
|
529
|
-
# Load the spec
|
|
530
|
-
if json_path and json_path.exists():
|
|
531
|
-
with open(json_path, 'r') as f:
|
|
532
|
-
bundle_spec = json_module.load(f)
|
|
533
|
-
|
|
534
|
-
return (
|
|
535
|
-
json_path,
|
|
536
|
-
csv_path if csv_path and csv_path.exists() else None,
|
|
537
|
-
png_path if png_path and png_path.exists() else None,
|
|
538
|
-
hitmap_path if hitmap_path and hitmap_path.exists() else None,
|
|
539
|
-
bundle_spec,
|
|
540
|
-
)
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
def _resolve_layered_pltz_bundle(bundle_dir: Path) -> tuple:
|
|
544
|
-
"""
|
|
545
|
-
Resolve paths from a layered .pltz bundle (v2.0 format).
|
|
546
|
-
|
|
547
|
-
Layered format structure:
|
|
548
|
-
plot.pltz.d/
|
|
549
|
-
spec.json # Semantic
|
|
550
|
-
style.json # Appearance
|
|
551
|
-
{basename}.csv # Data
|
|
552
|
-
exports/ # PNG, SVG, hitmap
|
|
553
|
-
cache/ # geometry_px.json
|
|
554
|
-
|
|
555
|
-
Parameters
|
|
556
|
-
----------
|
|
557
|
-
bundle_dir : Path
|
|
558
|
-
Path to .pltz.d bundle directory.
|
|
559
|
-
|
|
560
|
-
Returns
|
|
561
|
-
-------
|
|
562
|
-
tuple
|
|
563
|
-
(json_path, csv_path, png_path, hitmap_path, bundle_spec)
|
|
564
|
-
"""
|
|
565
|
-
import json as json_module
|
|
566
|
-
from scitex.plt.io import load_layered_pltz_bundle, merge_layered_bundle
|
|
567
|
-
|
|
568
|
-
# Load layered bundle
|
|
569
|
-
bundle_data = load_layered_pltz_bundle(bundle_dir)
|
|
570
|
-
basename = bundle_data.get("basename", "plot")
|
|
571
|
-
|
|
572
|
-
# Paths
|
|
573
|
-
spec_path = bundle_dir / "spec.json"
|
|
574
|
-
csv_path = None
|
|
575
|
-
png_path = None
|
|
576
|
-
hitmap_path = None
|
|
577
|
-
|
|
578
|
-
# Find CSV
|
|
579
|
-
for f in bundle_dir.glob("*.csv"):
|
|
580
|
-
csv_path = f
|
|
581
|
-
break
|
|
582
|
-
|
|
583
|
-
# Find exports
|
|
584
|
-
exports_dir = bundle_dir / "exports"
|
|
585
|
-
if exports_dir.exists():
|
|
586
|
-
for f in exports_dir.iterdir():
|
|
587
|
-
name = f.name
|
|
588
|
-
if name.endswith('_hitmap.png'):
|
|
589
|
-
hitmap_path = f
|
|
590
|
-
elif name.endswith('.svg') and '_hitmap' not in name:
|
|
591
|
-
png_path = f # Prefer SVG
|
|
592
|
-
elif name.endswith('.png') and '_hitmap' not in name and png_path is None:
|
|
593
|
-
png_path = f
|
|
594
|
-
|
|
595
|
-
# Merged spec for backward compatibility with editor
|
|
596
|
-
bundle_spec = bundle_data.get("merged", {})
|
|
597
|
-
|
|
598
|
-
# Add hit_regions path reference for editor
|
|
599
|
-
if hitmap_path and "hit_regions" in bundle_spec:
|
|
600
|
-
bundle_spec["hit_regions"]["hit_map"] = str(hitmap_path.name)
|
|
601
|
-
|
|
602
|
-
return (
|
|
603
|
-
spec_path, # Return spec.json as the main JSON path
|
|
604
|
-
csv_path if csv_path and csv_path.exists() else None,
|
|
605
|
-
png_path if png_path and png_path.exists() else None,
|
|
606
|
-
hitmap_path if hitmap_path and hitmap_path.exists() else None,
|
|
607
|
-
bundle_spec,
|
|
608
|
-
)
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
def _load_panel_data(panel_dir: Path) -> Optional[dict]:
|
|
612
|
-
"""
|
|
613
|
-
Load panel data from a .pltz.d directory.
|
|
614
|
-
|
|
615
|
-
Used by switch_panel endpoint to load a different panel's data.
|
|
616
|
-
|
|
617
|
-
Parameters
|
|
618
|
-
----------
|
|
619
|
-
panel_dir : Path
|
|
620
|
-
Path to .pltz.d panel directory
|
|
621
|
-
|
|
622
|
-
Returns
|
|
623
|
-
-------
|
|
624
|
-
dict or None
|
|
625
|
-
Dictionary with keys: json_path, metadata, csv_data, png_path, hitmap_path
|
|
626
|
-
Returns None if panel cannot be loaded
|
|
627
|
-
"""
|
|
628
|
-
import json as json_module
|
|
629
|
-
import scitex as stx
|
|
630
|
-
|
|
631
|
-
if not panel_dir.exists():
|
|
632
|
-
return None
|
|
633
|
-
|
|
634
|
-
# Check for layered vs legacy format
|
|
635
|
-
spec_path = panel_dir / "spec.json"
|
|
636
|
-
if spec_path.exists():
|
|
637
|
-
# Layered format
|
|
638
|
-
from scitex.plt.io import load_layered_pltz_bundle
|
|
639
|
-
bundle_data = load_layered_pltz_bundle(panel_dir)
|
|
640
|
-
metadata = bundle_data.get("merged", {})
|
|
641
|
-
|
|
642
|
-
# Find CSV
|
|
643
|
-
csv_data = None
|
|
644
|
-
for f in panel_dir.glob("*.csv"):
|
|
645
|
-
csv_data = stx.io.load(f)
|
|
646
|
-
break
|
|
647
|
-
|
|
648
|
-
# Find exports - prefer PNG over SVG (PIL can't open SVG)
|
|
649
|
-
png_path = None
|
|
650
|
-
svg_path = None
|
|
651
|
-
hitmap_path = None
|
|
652
|
-
exports_dir = panel_dir / "exports"
|
|
653
|
-
if exports_dir.exists():
|
|
654
|
-
for f in exports_dir.iterdir():
|
|
655
|
-
name = f.name
|
|
656
|
-
if name.endswith('_hitmap.png'):
|
|
657
|
-
hitmap_path = f
|
|
658
|
-
elif name.endswith('.png') and '_hitmap' not in name and '_overview' not in name:
|
|
659
|
-
png_path = f
|
|
660
|
-
elif name.endswith('.svg') and '_hitmap' not in name and svg_path is None:
|
|
661
|
-
svg_path = f
|
|
662
|
-
# Fall back to SVG only if no PNG found (though PIL can't open it)
|
|
663
|
-
if png_path is None:
|
|
664
|
-
png_path = svg_path
|
|
665
|
-
|
|
666
|
-
return {
|
|
667
|
-
"json_path": spec_path,
|
|
668
|
-
"metadata": metadata,
|
|
669
|
-
"csv_data": csv_data,
|
|
670
|
-
"png_path": png_path,
|
|
671
|
-
"hitmap_path": hitmap_path,
|
|
672
|
-
}
|
|
673
|
-
else:
|
|
674
|
-
# Legacy format
|
|
675
|
-
json_path = None
|
|
676
|
-
csv_data = None
|
|
677
|
-
png_path = None
|
|
678
|
-
hitmap_path = None
|
|
679
|
-
|
|
680
|
-
for f in panel_dir.iterdir():
|
|
681
|
-
name = f.name
|
|
682
|
-
if name.endswith('.json') and not name.endswith('.manual.json'):
|
|
683
|
-
json_path = f
|
|
684
|
-
elif name.endswith('.csv'):
|
|
685
|
-
csv_data = stx.io.load(f)
|
|
686
|
-
elif name.endswith('_hitmap.png'):
|
|
687
|
-
hitmap_path = f
|
|
688
|
-
elif name.endswith('.svg') and '_hitmap' not in name:
|
|
689
|
-
png_path = f
|
|
690
|
-
elif name.endswith('.png') and '_hitmap' not in name and '_overview' not in name:
|
|
691
|
-
if png_path is None:
|
|
692
|
-
png_path = f
|
|
693
|
-
|
|
694
|
-
if json_path is None:
|
|
695
|
-
return None
|
|
696
|
-
|
|
697
|
-
with open(json_path, 'r') as f:
|
|
698
|
-
metadata = json_module.load(f)
|
|
699
|
-
|
|
700
|
-
return {
|
|
701
|
-
"json_path": json_path,
|
|
702
|
-
"metadata": metadata,
|
|
703
|
-
"csv_data": csv_data,
|
|
704
|
-
"png_path": png_path,
|
|
705
|
-
"hitmap_path": hitmap_path,
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
def _compute_file_hash(path: Path) -> str:
|
|
710
|
-
"""Compute SHA256 hash of file contents."""
|
|
711
|
-
with open(path, "rb") as f:
|
|
712
|
-
return hashlib.sha256(f.read()).hexdigest()
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
def save_manual_overrides(
|
|
716
|
-
json_path: Path,
|
|
717
|
-
overrides: dict,
|
|
718
|
-
) -> Path:
|
|
719
|
-
"""
|
|
720
|
-
Save manual overrides to .manual.json file.
|
|
721
|
-
|
|
722
|
-
Parameters
|
|
723
|
-
----------
|
|
724
|
-
json_path : Path
|
|
725
|
-
Path to base JSON file
|
|
726
|
-
overrides : dict
|
|
727
|
-
Override settings (styles, annotations, etc.)
|
|
728
|
-
|
|
729
|
-
Returns
|
|
730
|
-
-------
|
|
731
|
-
Path
|
|
732
|
-
Path to saved manual.json file
|
|
733
|
-
"""
|
|
734
|
-
import scitex as stx
|
|
735
|
-
|
|
736
|
-
manual_path = json_path.with_suffix(".manual.json")
|
|
737
|
-
|
|
738
|
-
# Compute hash of base JSON for staleness detection
|
|
739
|
-
base_hash = _compute_file_hash(json_path)
|
|
740
|
-
|
|
741
|
-
manual_data = {
|
|
742
|
-
"base_file": json_path.name,
|
|
743
|
-
"base_hash": base_hash,
|
|
744
|
-
"overrides": overrides,
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
stx.io.save(manual_data, manual_path)
|
|
748
|
-
return manual_path
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
# EOF
|