scitex 2.7.0__py3-none-any.whl → 2.8.1__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 +6 -2
- scitex/__version__.py +1 -1
- scitex/audio/README.md +52 -0
- scitex/audio/__init__.py +384 -0
- scitex/audio/__main__.py +129 -0
- scitex/audio/_tts.py +334 -0
- scitex/audio/engines/__init__.py +44 -0
- scitex/audio/engines/base.py +275 -0
- scitex/audio/engines/elevenlabs_engine.py +143 -0
- scitex/audio/engines/gtts_engine.py +162 -0
- scitex/audio/engines/pyttsx3_engine.py +131 -0
- scitex/audio/mcp_server.py +757 -0
- scitex/bridge/_helpers.py +1 -1
- scitex/bridge/_plt_vis.py +1 -1
- scitex/bridge/_stats_vis.py +1 -1
- scitex/dev/plt/__init__.py +272 -0
- scitex/dev/plt/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/plot_mpl_bar.py +29 -0
- scitex/dev/plt/plot_mpl_barh.py +29 -0
- scitex/dev/plt/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/plot_mpl_contour.py +31 -0
- scitex/dev/plt/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/plot_mpl_fill.py +30 -0
- scitex/dev/plt/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/plot_mpl_hist.py +28 -0
- scitex/dev/plt/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/plot_mpl_pie.py +29 -0
- scitex/dev/plt/plot_mpl_plot.py +29 -0
- scitex/dev/plt/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/plot_mpl_stem.py +29 -0
- scitex/dev/plt/plot_mpl_step.py +29 -0
- scitex/dev/plt/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/plot_sns_barplot.py +29 -0
- scitex/dev/plt/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/plot_sns_histplot.py +29 -0
- scitex/dev/plt/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/plot_stx_bar.py +29 -0
- scitex/dev/plt/plot_stx_barh.py +29 -0
- scitex/dev/plt/plot_stx_box.py +28 -0
- scitex/dev/plt/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/plot_stx_contour.py +31 -0
- scitex/dev/plt/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/plot_stx_fillv.py +28 -0
- scitex/dev/plt/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/plot_stx_image.py +28 -0
- scitex/dev/plt/plot_stx_imshow.py +28 -0
- scitex/dev/plt/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/plot_stx_kde.py +28 -0
- scitex/dev/plt/plot_stx_line.py +28 -0
- scitex/dev/plt/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/plot_stx_raster.py +28 -0
- scitex/dev/plt/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/plot_stx_scatter.py +29 -0
- scitex/dev/plt/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/plot_stx_violin.py +28 -0
- scitex/dev/plt/plot_stx_violinplot.py +28 -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/fig/__init__.py +352 -0
- scitex/{vis → fig}/backend/_parser.py +1 -1
- scitex/{vis → fig}/canvas.py +1 -1
- scitex/{vis → fig}/editor/__init__.py +5 -2
- scitex/{vis → fig}/editor/_dearpygui_editor.py +1 -1
- scitex/{vis → fig}/editor/_defaults.py +70 -5
- scitex/{vis → fig}/editor/_mpl_editor.py +1 -1
- scitex/{vis → fig}/editor/_qt_editor.py +182 -2
- scitex/{vis → 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 +1299 -0
- scitex/fig/editor/flask_editor/_core.py +1429 -0
- scitex/{vis → fig}/editor/flask_editor/_plotter.py +38 -4
- scitex/fig/editor/flask_editor/_renderer.py +813 -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 +123 -0
- scitex/fig/editor/flask_editor/templates/_html.py +852 -0
- scitex/fig/editor/flask_editor/templates/_scripts.py +4933 -0
- scitex/fig/editor/flask_editor/templates/_styles.py +1658 -0
- scitex/{vis → fig}/io/__init__.py +13 -1
- scitex/fig/io/_bundle.py +1058 -0
- scitex/{vis → fig}/io/_canvas.py +1 -1
- scitex/{vis → fig}/io/_data.py +1 -1
- scitex/{vis → fig}/io/_export.py +1 -1
- scitex/{vis → fig}/io/_load.py +1 -1
- scitex/{vis → fig}/io/_panel.py +1 -1
- scitex/{vis → fig}/io/_save.py +1 -1
- scitex/{vis → fig}/model/__init__.py +1 -1
- scitex/{vis → fig}/model/_annotations.py +1 -1
- scitex/{vis → fig}/model/_axes.py +1 -1
- scitex/{vis → fig}/model/_figure.py +1 -1
- scitex/{vis → fig}/model/_guides.py +1 -1
- scitex/{vis → fig}/model/_plot.py +1 -1
- scitex/{vis → fig}/model/_styles.py +1 -1
- scitex/{vis → fig}/utils/__init__.py +1 -1
- scitex/io/__init__.py +22 -26
- scitex/io/_bundle.py +493 -0
- scitex/io/_flush.py +5 -2
- scitex/io/_load.py +98 -0
- scitex/io/_load_modules/_H5Explorer.py +5 -2
- scitex/io/_load_modules/_canvas.py +2 -2
- scitex/io/_load_modules/_image.py +3 -4
- scitex/io/_load_modules/_txt.py +4 -2
- scitex/io/_metadata.py +34 -324
- scitex/io/_metadata_modules/__init__.py +46 -0
- scitex/io/_metadata_modules/_embed.py +70 -0
- scitex/io/_metadata_modules/_read.py +64 -0
- scitex/io/_metadata_modules/_utils.py +79 -0
- scitex/io/_metadata_modules/embed_metadata_jpeg.py +74 -0
- scitex/io/_metadata_modules/embed_metadata_pdf.py +53 -0
- scitex/io/_metadata_modules/embed_metadata_png.py +26 -0
- scitex/io/_metadata_modules/embed_metadata_svg.py +62 -0
- scitex/io/_metadata_modules/read_metadata_jpeg.py +57 -0
- scitex/io/_metadata_modules/read_metadata_pdf.py +51 -0
- scitex/io/_metadata_modules/read_metadata_png.py +39 -0
- scitex/io/_metadata_modules/read_metadata_svg.py +44 -0
- scitex/io/_qr_utils.py +5 -3
- scitex/io/_save.py +548 -30
- scitex/io/_save_modules/_canvas.py +3 -3
- scitex/io/_save_modules/_image.py +5 -9
- scitex/io/_save_modules/_tex.py +7 -4
- scitex/io/_zip_bundle.py +439 -0
- scitex/io/utils/h5_to_zarr.py +11 -9
- scitex/msword/__init__.py +255 -0
- scitex/msword/profiles.py +357 -0
- scitex/msword/reader.py +753 -0
- scitex/msword/utils.py +289 -0
- scitex/msword/writer.py +362 -0
- scitex/plt/__init__.py +5 -2
- scitex/plt/_subplots/_AxesWrapper.py +6 -6
- scitex/plt/_subplots/_AxisWrapper.py +15 -9
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +36 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +264 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +213 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +128 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +59 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +34 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +593 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +654 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +527 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +321 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +33 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +152 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +600 -0
- scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +79 -5
- scitex/plt/_subplots/_FigWrapper.py +6 -6
- scitex/plt/_subplots/_SubplotsWrapper.py +28 -18
- scitex/plt/_subplots/_export_as_csv.py +35 -5
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +8 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +10 -21
- scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +18 -7
- scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +28 -12
- scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +10 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +13 -1
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +12 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +10 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +10 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +18 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +44 -36
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +14 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +11 -5
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +84 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +85 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_conf_mat.py +14 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_contour.py +54 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +14 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +120 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +16 -6
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +29 -19
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_imshow.py +63 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +22 -5
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +10 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +51 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter_hist.py +18 -9
- scitex/plt/ax/_plot/_stx_ecdf.py +4 -2
- scitex/plt/gallery/_generate.py +421 -14
- scitex/plt/io/__init__.py +53 -0
- scitex/plt/io/_bundle.py +490 -0
- scitex/plt/io/_layered_bundle.py +1343 -0
- scitex/plt/styles/SCITEX_STYLE.yaml +26 -0
- scitex/plt/styles/__init__.py +14 -0
- scitex/plt/styles/presets.py +78 -0
- scitex/plt/utils/__init__.py +13 -1
- scitex/plt/utils/_collect_figure_metadata.py +10 -14
- scitex/plt/utils/_configure_mpl.py +6 -18
- scitex/plt/utils/_crop.py +32 -14
- scitex/plt/utils/_csv_column_naming.py +54 -0
- scitex/plt/utils/_figure_mm.py +116 -1
- scitex/plt/utils/_hitmap.py +1643 -0
- scitex/plt/utils/metadata/__init__.py +25 -0
- scitex/plt/utils/metadata/_core.py +9 -10
- scitex/plt/utils/metadata/_dimensions.py +6 -3
- scitex/plt/utils/metadata/_editable_export.py +405 -0
- scitex/plt/utils/metadata/_geometry_extraction.py +570 -0
- scitex/schema/__init__.py +109 -16
- scitex/schema/_canvas.py +1 -1
- scitex/schema/_plot.py +1015 -0
- scitex/schema/_stats.py +2 -2
- scitex/stats/__init__.py +117 -0
- scitex/stats/io/__init__.py +29 -0
- scitex/stats/io/_bundle.py +156 -0
- scitex/tex/__init__.py +4 -0
- scitex/tex/_export.py +890 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/METADATA +11 -1
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/RECORD +294 -170
- scitex/io/memo.md +0 -2827
- scitex/plt/REQUESTS.md +0 -191
- scitex/plt/_subplots/TODO.md +0 -53
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +0 -559
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +0 -1609
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +0 -447
- scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_between.json +0 -110
- scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_betweenx.json +0 -88
- scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fill_between.json +0 -103
- scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fillv.json +0 -106
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/bar.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/barh.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/boxplot.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_bar.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_barh.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_box.json +0 -83
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_boxplot.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violin.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violinplot.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/violinplot.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/contour.json +0 -97
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/contourf.json +0 -98
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/stx_contour.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist.json +0 -101
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist2d.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_ecdf.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_joyplot.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_kde.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/imshow.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/matshow.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_conf_mat.json +0 -83
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_heatmap.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_image.json +0 -121
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_imshow.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/line/plot.json +0 -110
- scitex/plt/templates/research-master/scitex/vis/gallery/line/step.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_line.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_shaded_line.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/hexbin.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/scatter.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stem.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stx_scatter.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/special/pie.json +0 -94
- scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_raster.json +0 -109
- scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_rectangle.json +0 -108
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/errorbar.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_errorbar.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_ci.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_std.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_median_iqr.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/vector/quiver.json +0 -99
- scitex/plt/templates/research-master/scitex/vis/gallery/vector/streamplot.json +0 -100
- scitex/vis/__init__.py +0 -177
- scitex/vis/editor/_edit.py +0 -390
- scitex/vis/editor/flask_editor/_bbox.py +0 -529
- scitex/vis/editor/flask_editor/_core.py +0 -168
- scitex/vis/editor/flask_editor/_renderer.py +0 -393
- scitex/vis/editor/flask_editor/templates/__init__.py +0 -33
- scitex/vis/editor/flask_editor/templates/_html.py +0 -513
- scitex/vis/editor/flask_editor/templates/_scripts.py +0 -1261
- scitex/vis/editor/flask_editor/templates/_styles.py +0 -739
- /scitex/{vis → fig}/README.md +0 -0
- /scitex/{vis → fig}/backend/__init__.py +0 -0
- /scitex/{vis → fig}/backend/_export.py +0 -0
- /scitex/{vis → fig}/backend/_render.py +0 -0
- /scitex/{vis → fig}/docs/CANVAS_ARCHITECTURE.md +0 -0
- /scitex/{vis → fig}/editor/_flask_editor.py +0 -0
- /scitex/{vis → fig}/editor/flask_editor/__init__.py +0 -0
- /scitex/{vis → fig}/editor/flask_editor/_utils.py +0 -0
- /scitex/{vis → fig}/io/_directory.py +0 -0
- /scitex/{vis → fig}/model/_plot_types.py +0 -0
- /scitex/{vis → fig}/utils/_defaults.py +0 -0
- /scitex/{vis → fig}/utils/_validate.py +0 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/WHEEL +0 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/entry_points.txt +0 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -56,6 +56,7 @@ class QtEditor:
|
|
|
56
56
|
- Real-time preview updates
|
|
57
57
|
- Save to .manual.json
|
|
58
58
|
- SciTeX style defaults pre-filled
|
|
59
|
+
- Hitmap-based element selection (when available)
|
|
59
60
|
"""
|
|
60
61
|
|
|
61
62
|
def __init__(
|
|
@@ -65,12 +66,32 @@ class QtEditor:
|
|
|
65
66
|
csv_data: Optional[Any] = None,
|
|
66
67
|
png_path: Optional[Path] = None,
|
|
67
68
|
manual_overrides: Optional[Dict[str, Any]] = None,
|
|
69
|
+
hitmap_path: Optional[Path] = None,
|
|
70
|
+
bundle_spec: Optional[Dict[str, Any]] = None,
|
|
68
71
|
):
|
|
69
72
|
self.json_path = Path(json_path)
|
|
70
73
|
self.metadata = metadata
|
|
71
74
|
self.csv_data = csv_data
|
|
72
75
|
self.png_path = Path(png_path) if png_path else None
|
|
73
76
|
self.manual_overrides = manual_overrides or {}
|
|
77
|
+
self.hitmap_path = Path(hitmap_path) if hitmap_path else None
|
|
78
|
+
self.bundle_spec = bundle_spec or {}
|
|
79
|
+
|
|
80
|
+
# Load hitmap image if available
|
|
81
|
+
self.hitmap_array = None
|
|
82
|
+
if self.hitmap_path and self.hitmap_path.exists():
|
|
83
|
+
try:
|
|
84
|
+
from PIL import Image
|
|
85
|
+
import numpy as np
|
|
86
|
+
hitmap_img = Image.open(self.hitmap_path).convert('RGB')
|
|
87
|
+
self.hitmap_array = np.array(hitmap_img)
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
# Extract hit_regions and selectable_regions from spec
|
|
92
|
+
self.hit_regions = self.bundle_spec.get('hit_regions', {})
|
|
93
|
+
self.selectable_regions = self.bundle_spec.get('selectable_regions', {})
|
|
94
|
+
self.color_map = self.hit_regions.get('color_map', {})
|
|
74
95
|
|
|
75
96
|
# Get SciTeX defaults and merge with metadata
|
|
76
97
|
from ._defaults import get_scitex_defaults, extract_defaults_from_metadata
|
|
@@ -164,7 +185,7 @@ class QtEditorWindow:
|
|
|
164
185
|
self.window.show()
|
|
165
186
|
|
|
166
187
|
def _create_canvas_panel(self, parent):
|
|
167
|
-
"""Create the matplotlib canvas panel."""
|
|
188
|
+
"""Create the matplotlib canvas panel with hitmap-based selection."""
|
|
168
189
|
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
|
|
169
190
|
from matplotlib.backends.backend_qtagg import NavigationToolbar2QT
|
|
170
191
|
from matplotlib.figure import Figure
|
|
@@ -184,8 +205,167 @@ class QtEditorWindow:
|
|
|
184
205
|
toolbar = NavigationToolbar2QT(self.canvas, canvas_widget)
|
|
185
206
|
canvas_layout.addWidget(toolbar)
|
|
186
207
|
|
|
208
|
+
# Connect click event for hitmap-based selection
|
|
209
|
+
self.canvas.mpl_connect('button_press_event', self._on_canvas_click)
|
|
210
|
+
|
|
211
|
+
# Selection info label
|
|
212
|
+
self.selection_label = self.QtWidgets.QLabel("Click on figure to select element")
|
|
213
|
+
self.selection_label.setStyleSheet("padding: 5px; background: #f0f0f0; border-radius: 3px;")
|
|
214
|
+
canvas_layout.addWidget(self.selection_label)
|
|
215
|
+
|
|
187
216
|
parent.addWidget(canvas_widget)
|
|
188
217
|
|
|
218
|
+
def _on_canvas_click(self, event):
|
|
219
|
+
"""Handle canvas click - use hitmap to identify clicked element."""
|
|
220
|
+
if event.inaxes is None:
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
# Get pixel coordinates
|
|
224
|
+
x_px = int(event.x)
|
|
225
|
+
y_px = int(self.canvas.get_width_height()[1] - event.y) # Flip Y
|
|
226
|
+
|
|
227
|
+
# First check selectable_regions (bounding boxes for axis elements)
|
|
228
|
+
selection_info = self._check_selectable_regions(x_px, y_px)
|
|
229
|
+
|
|
230
|
+
# If no selectable_region hit, try hitmap for data elements
|
|
231
|
+
if selection_info is None and self.editor.hitmap_array is not None:
|
|
232
|
+
selection_info = self._check_hitmap(x_px, y_px)
|
|
233
|
+
|
|
234
|
+
# Update selection display
|
|
235
|
+
if selection_info:
|
|
236
|
+
self._show_selection(selection_info)
|
|
237
|
+
else:
|
|
238
|
+
self.selection_label.setText(f"No element at ({x_px}, {y_px})")
|
|
239
|
+
self.status_bar.showMessage(f"Click at ({x_px}, {y_px}) - no element found")
|
|
240
|
+
|
|
241
|
+
def _check_selectable_regions(self, x_px: int, y_px: int) -> Optional[Dict[str, Any]]:
|
|
242
|
+
"""Check if click is within any selectable_region bounding box."""
|
|
243
|
+
regions = self.editor.selectable_regions
|
|
244
|
+
if not regions or 'axes' not in regions:
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
for ax_info in regions['axes']:
|
|
248
|
+
ax_idx = ax_info.get('index', 0)
|
|
249
|
+
|
|
250
|
+
# Check title
|
|
251
|
+
if 'title' in ax_info:
|
|
252
|
+
bbox = ax_info['title'].get('bbox_px', [])
|
|
253
|
+
if len(bbox) == 4 and self._point_in_bbox(x_px, y_px, bbox):
|
|
254
|
+
return {
|
|
255
|
+
'type': 'title',
|
|
256
|
+
'axes_index': ax_idx,
|
|
257
|
+
'text': ax_info['title'].get('text', ''),
|
|
258
|
+
'element': 'title',
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# Check xlabel
|
|
262
|
+
if 'xlabel' in ax_info:
|
|
263
|
+
bbox = ax_info['xlabel'].get('bbox_px', [])
|
|
264
|
+
if len(bbox) == 4 and self._point_in_bbox(x_px, y_px, bbox):
|
|
265
|
+
return {
|
|
266
|
+
'type': 'xlabel',
|
|
267
|
+
'axes_index': ax_idx,
|
|
268
|
+
'text': ax_info['xlabel'].get('text', ''),
|
|
269
|
+
'element': 'xlabel',
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# Check ylabel
|
|
273
|
+
if 'ylabel' in ax_info:
|
|
274
|
+
bbox = ax_info['ylabel'].get('bbox_px', [])
|
|
275
|
+
if len(bbox) == 4 and self._point_in_bbox(x_px, y_px, bbox):
|
|
276
|
+
return {
|
|
277
|
+
'type': 'ylabel',
|
|
278
|
+
'axes_index': ax_idx,
|
|
279
|
+
'text': ax_info['ylabel'].get('text', ''),
|
|
280
|
+
'element': 'ylabel',
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
# Check legend
|
|
284
|
+
if 'legend' in ax_info:
|
|
285
|
+
bbox = ax_info['legend'].get('bbox_px', [])
|
|
286
|
+
if len(bbox) == 4 and self._point_in_bbox(x_px, y_px, bbox):
|
|
287
|
+
return {
|
|
288
|
+
'type': 'legend',
|
|
289
|
+
'axes_index': ax_idx,
|
|
290
|
+
'element': 'legend',
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
# Check xaxis elements
|
|
294
|
+
if 'xaxis' in ax_info:
|
|
295
|
+
xaxis = ax_info['xaxis']
|
|
296
|
+
if 'spine' in xaxis and xaxis['spine']:
|
|
297
|
+
bbox = xaxis['spine'].get('bbox_px', [])
|
|
298
|
+
if len(bbox) == 4 and self._point_in_bbox(x_px, y_px, bbox):
|
|
299
|
+
return {'type': 'xaxis_spine', 'axes_index': ax_idx, 'element': 'xaxis'}
|
|
300
|
+
|
|
301
|
+
# Check yaxis elements
|
|
302
|
+
if 'yaxis' in ax_info:
|
|
303
|
+
yaxis = ax_info['yaxis']
|
|
304
|
+
if 'spine' in yaxis and yaxis['spine']:
|
|
305
|
+
bbox = yaxis['spine'].get('bbox_px', [])
|
|
306
|
+
if len(bbox) == 4 and self._point_in_bbox(x_px, y_px, bbox):
|
|
307
|
+
return {'type': 'yaxis_spine', 'axes_index': ax_idx, 'element': 'yaxis'}
|
|
308
|
+
|
|
309
|
+
return None
|
|
310
|
+
|
|
311
|
+
def _check_hitmap(self, x_px: int, y_px: int) -> Optional[Dict[str, Any]]:
|
|
312
|
+
"""Check hitmap for data element at pixel position."""
|
|
313
|
+
hitmap = self.editor.hitmap_array
|
|
314
|
+
color_map = self.editor.color_map
|
|
315
|
+
|
|
316
|
+
if hitmap is None or not color_map:
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
# Bounds check
|
|
320
|
+
h, w = hitmap.shape[:2]
|
|
321
|
+
if x_px < 0 or x_px >= w or y_px < 0 or y_px >= h:
|
|
322
|
+
return None
|
|
323
|
+
|
|
324
|
+
# Get RGB at position
|
|
325
|
+
r, g, b = hitmap[y_px, x_px]
|
|
326
|
+
|
|
327
|
+
# Skip background (black) and axes elements (dark gray)
|
|
328
|
+
if (r, g, b) == (0, 0, 0): # Background
|
|
329
|
+
return None
|
|
330
|
+
if (r, g, b) == (64, 64, 64): # Axes color (#404040)
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
# Convert RGB to ID
|
|
334
|
+
element_id = r * 65536 + g * 256 + b
|
|
335
|
+
|
|
336
|
+
# Look up in color_map
|
|
337
|
+
if str(element_id) in color_map:
|
|
338
|
+
info = color_map[str(element_id)]
|
|
339
|
+
return {
|
|
340
|
+
'type': 'data_element',
|
|
341
|
+
'element_id': element_id,
|
|
342
|
+
'element_type': info.get('type', 'unknown'),
|
|
343
|
+
'label': info.get('label', ''),
|
|
344
|
+
'axes_index': info.get('axes_index', 0),
|
|
345
|
+
'rgb': [r, g, b],
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return None
|
|
349
|
+
|
|
350
|
+
def _point_in_bbox(self, x: int, y: int, bbox: list) -> bool:
|
|
351
|
+
"""Check if point (x, y) is within bounding box [x0, y0, x1, y1]."""
|
|
352
|
+
x0, y0, x1, y1 = bbox
|
|
353
|
+
return x0 <= x <= x1 and y0 <= y <= y1
|
|
354
|
+
|
|
355
|
+
def _show_selection(self, info: Dict[str, Any]):
|
|
356
|
+
"""Display selection info in UI."""
|
|
357
|
+
if info['type'] == 'data_element':
|
|
358
|
+
text = f"Selected: {info['element_type']} - {info.get('label', 'unnamed')}"
|
|
359
|
+
detail = f"ID: {info['element_id']}, Axes: {info['axes_index']}"
|
|
360
|
+
else:
|
|
361
|
+
text = f"Selected: {info['type']}"
|
|
362
|
+
if 'text' in info:
|
|
363
|
+
text += f" - \"{info['text']}\""
|
|
364
|
+
detail = f"Axes: {info.get('axes_index', 0)}"
|
|
365
|
+
|
|
366
|
+
self.selection_label.setText(f"{text}\n{detail}")
|
|
367
|
+
self.status_bar.showMessage(text)
|
|
368
|
+
|
|
189
369
|
def _create_control_panel(self, parent):
|
|
190
370
|
"""Create the control panel with property editors."""
|
|
191
371
|
scroll_area = self.QtWidgets.QScrollArea()
|
|
@@ -837,7 +1017,7 @@ class QtEditorWindow:
|
|
|
837
1017
|
|
|
838
1018
|
def _save_manual(self):
|
|
839
1019
|
"""Save to .manual.json."""
|
|
840
|
-
from .
|
|
1020
|
+
from .edit import save_manual_overrides
|
|
841
1021
|
|
|
842
1022
|
try:
|
|
843
1023
|
self._collect_overrides()
|
|
@@ -490,7 +490,7 @@ class TkinterEditor:
|
|
|
490
490
|
|
|
491
491
|
def _save_manual(self):
|
|
492
492
|
"""Save current overrides to .manual.json."""
|
|
493
|
-
from .
|
|
493
|
+
from .edit import save_manual_overrides
|
|
494
494
|
|
|
495
495
|
try:
|
|
496
496
|
manual_path = save_manual_overrides(self.json_path, self.current_overrides)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-12-14 (ywatanabe)"
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fig/editor/edit/__init__.py
|
|
5
|
+
|
|
6
|
+
"""Edit module for launching visual figure editors.
|
|
7
|
+
|
|
8
|
+
This module provides the main edit() function and supporting utilities for:
|
|
9
|
+
- Backend detection and selection (flask, dearpygui, qt, tkinter, mpl)
|
|
10
|
+
- Path resolution for figure files (.json, .csv, .png)
|
|
11
|
+
- Bundle resolution for .pltz and .figz formats
|
|
12
|
+
- Panel data loading for multi-panel figures
|
|
13
|
+
- Manual override handling (.manual.json)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .editor_launcher import edit
|
|
17
|
+
from .backend_detector import (
|
|
18
|
+
detect_best_backend,
|
|
19
|
+
print_available_backends,
|
|
20
|
+
)
|
|
21
|
+
from .path_resolver import resolve_figure_paths
|
|
22
|
+
from .bundle_resolver import (
|
|
23
|
+
resolve_pltz_bundle,
|
|
24
|
+
resolve_figz_bundle,
|
|
25
|
+
resolve_layered_pltz_bundle,
|
|
26
|
+
)
|
|
27
|
+
from .panel_loader import load_panel_data
|
|
28
|
+
from .manual_handler import compute_file_hash, save_manual_overrides
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
# Main entry point
|
|
32
|
+
"edit",
|
|
33
|
+
# Backend detection
|
|
34
|
+
"detect_best_backend",
|
|
35
|
+
"print_available_backends",
|
|
36
|
+
# Path resolution
|
|
37
|
+
"resolve_figure_paths",
|
|
38
|
+
# Bundle resolution
|
|
39
|
+
"resolve_pltz_bundle",
|
|
40
|
+
"resolve_figz_bundle",
|
|
41
|
+
"resolve_layered_pltz_bundle",
|
|
42
|
+
# Panel loading
|
|
43
|
+
"load_panel_data",
|
|
44
|
+
# Manual overrides
|
|
45
|
+
"compute_file_hash",
|
|
46
|
+
"save_manual_overrides",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# EOF
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-12-14 (ywatanabe)"
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fig/editor/edit/backend_detector.py
|
|
5
|
+
|
|
6
|
+
"""Backend detection and selection for figure editor."""
|
|
7
|
+
|
|
8
|
+
import warnings
|
|
9
|
+
|
|
10
|
+
__all__ = ["print_available_backends", "detect_best_backend"]
|
|
11
|
+
|
|
12
|
+
# Backend packages mapping
|
|
13
|
+
BACKENDS = {
|
|
14
|
+
"flask": ["flask"],
|
|
15
|
+
"dearpygui": ["dearpygui"],
|
|
16
|
+
"qt": ["PyQt6", "PyQt5", "PySide6", "PySide2"],
|
|
17
|
+
"tkinter": ["tkinter"],
|
|
18
|
+
"mpl": ["matplotlib"],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def print_available_backends() -> None:
|
|
23
|
+
"""Print available backends status."""
|
|
24
|
+
print("\n" + "=" * 50)
|
|
25
|
+
print("SciTeX Visual Editor - Available Backends")
|
|
26
|
+
print("=" * 50)
|
|
27
|
+
|
|
28
|
+
for backend, packages in BACKENDS.items():
|
|
29
|
+
available = False
|
|
30
|
+
available_pkg = None
|
|
31
|
+
for pkg in packages:
|
|
32
|
+
try:
|
|
33
|
+
__import__(pkg)
|
|
34
|
+
available = True
|
|
35
|
+
available_pkg = pkg
|
|
36
|
+
break
|
|
37
|
+
except ImportError:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
status = f"[OK] {available_pkg}" if available else "[NOT INSTALLED]"
|
|
41
|
+
print(f" {backend:12s}: {status}")
|
|
42
|
+
|
|
43
|
+
print("=" * 50)
|
|
44
|
+
print("Install: pip install scitex[gui]")
|
|
45
|
+
print("=" * 50 + "\n")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def detect_best_backend() -> str:
|
|
49
|
+
"""
|
|
50
|
+
Detect the best available GUI backend with graceful degradation.
|
|
51
|
+
|
|
52
|
+
Order: flask > dearpygui > qt > tkinter > mpl
|
|
53
|
+
Shows warnings when falling back to less capable backends.
|
|
54
|
+
"""
|
|
55
|
+
# Try Flask - best for modern UI
|
|
56
|
+
try:
|
|
57
|
+
import flask
|
|
58
|
+
return "flask"
|
|
59
|
+
except ImportError:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
# Try DearPyGui - GPU-accelerated, modern
|
|
63
|
+
try:
|
|
64
|
+
import dearpygui
|
|
65
|
+
return "dearpygui"
|
|
66
|
+
except ImportError:
|
|
67
|
+
warnings.warn(
|
|
68
|
+
"Flask not available. Consider: pip install flask\nTrying DearPyGui..."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Try DearPyGui again
|
|
72
|
+
try:
|
|
73
|
+
import dearpygui
|
|
74
|
+
return "dearpygui"
|
|
75
|
+
except ImportError:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
# Try Qt (richest desktop features)
|
|
79
|
+
for qt_pkg in ["PyQt6", "PyQt5", "PySide6", "PySide2"]:
|
|
80
|
+
try:
|
|
81
|
+
__import__(qt_pkg)
|
|
82
|
+
warnings.warn(
|
|
83
|
+
"DearPyGui not available. Consider: pip install dearpygui\n"
|
|
84
|
+
"Using Qt backend instead."
|
|
85
|
+
)
|
|
86
|
+
return "qt"
|
|
87
|
+
except ImportError:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
# Try Tkinter (built-in, good features)
|
|
91
|
+
try:
|
|
92
|
+
import tkinter
|
|
93
|
+
warnings.warn(
|
|
94
|
+
"Qt not available. Consider: pip install PyQt6\n"
|
|
95
|
+
"Using Tkinter backend (basic features)."
|
|
96
|
+
)
|
|
97
|
+
return "tkinter"
|
|
98
|
+
except ImportError:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
# Fall back to matplotlib interactive (always works)
|
|
102
|
+
warnings.warn(
|
|
103
|
+
"No GUI toolkit found. Using minimal matplotlib editor.\n"
|
|
104
|
+
"For better experience, install: pip install flask (web) or pip install PyQt6 (desktop)"
|
|
105
|
+
)
|
|
106
|
+
return "mpl"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# EOF
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-12-14 (ywatanabe)"
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fig/editor/edit/bundle_resolver.py
|
|
5
|
+
|
|
6
|
+
"""Bundle path resolution for .pltz and .figz formats."""
|
|
7
|
+
|
|
8
|
+
import json as json_module
|
|
9
|
+
import tempfile
|
|
10
|
+
import zipfile
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional, Tuple, Dict, Any
|
|
13
|
+
|
|
14
|
+
__all__ = ["resolve_pltz_bundle", "resolve_figz_bundle", "resolve_layered_pltz_bundle"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def resolve_figz_bundle(path: Path, panel_index: int = 0) -> Tuple:
|
|
18
|
+
"""
|
|
19
|
+
Resolve paths from a .figz bundle (multi-panel figure).
|
|
20
|
+
|
|
21
|
+
Uses in-memory zip reading for .pltz panels - no disk extraction.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
path : Path
|
|
26
|
+
Path to .figz bundle (.figz or .figz.d)
|
|
27
|
+
panel_index : int, optional
|
|
28
|
+
Index of panel to open (default: 0 for first panel)
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
tuple
|
|
33
|
+
(json_path, csv_path, png_path, hitmap_path, bundle_spec, panel_info)
|
|
34
|
+
"""
|
|
35
|
+
spath = str(path)
|
|
36
|
+
figz_is_zip = False
|
|
37
|
+
|
|
38
|
+
# Handle ZIP vs directory for figz
|
|
39
|
+
if spath.endswith('.figz') and not spath.endswith('.figz.d'):
|
|
40
|
+
figz_is_zip = True
|
|
41
|
+
if not path.exists():
|
|
42
|
+
raise FileNotFoundError(f"Figz bundle not found: {path}")
|
|
43
|
+
# For figz zip, extract to access nested pltz
|
|
44
|
+
temp_dir = tempfile.mkdtemp(prefix='scitex_edit_figz_')
|
|
45
|
+
with zipfile.ZipFile(path, 'r') as zf:
|
|
46
|
+
zf.extractall(temp_dir)
|
|
47
|
+
bundle_dir = Path(temp_dir)
|
|
48
|
+
for item in bundle_dir.iterdir():
|
|
49
|
+
if item.is_dir() and str(item).endswith('.figz.d'):
|
|
50
|
+
bundle_dir = item
|
|
51
|
+
break
|
|
52
|
+
else:
|
|
53
|
+
bundle_dir = Path(path)
|
|
54
|
+
if not bundle_dir.exists():
|
|
55
|
+
raise FileNotFoundError(f"Figz bundle directory not found: {bundle_dir}")
|
|
56
|
+
|
|
57
|
+
# Find nested pltz bundles
|
|
58
|
+
panel_paths = []
|
|
59
|
+
panel_is_zip = []
|
|
60
|
+
|
|
61
|
+
for item in sorted(bundle_dir.iterdir(), key=lambda x: x.name):
|
|
62
|
+
if item.is_dir() and str(item).endswith('.pltz.d'):
|
|
63
|
+
panel_paths.append(str(item))
|
|
64
|
+
panel_is_zip.append(False)
|
|
65
|
+
elif item.is_file() and str(item).endswith('.pltz'):
|
|
66
|
+
panel_paths.append(str(item))
|
|
67
|
+
panel_is_zip.append(True)
|
|
68
|
+
|
|
69
|
+
if not panel_paths:
|
|
70
|
+
raise FileNotFoundError(f"No .pltz panels found in figz bundle: {bundle_dir}")
|
|
71
|
+
|
|
72
|
+
# Validate panel index
|
|
73
|
+
if panel_index < 0 or panel_index >= len(panel_paths):
|
|
74
|
+
panel_index = 0
|
|
75
|
+
|
|
76
|
+
selected_panel_path = panel_paths[panel_index]
|
|
77
|
+
panel_name = Path(selected_panel_path).name
|
|
78
|
+
print(f"Opening panel: {panel_name}")
|
|
79
|
+
if len(panel_paths) > 1:
|
|
80
|
+
print(f" (Figz contains {len(panel_paths)} panels)")
|
|
81
|
+
|
|
82
|
+
# Build panel info
|
|
83
|
+
panel_names = [Path(p).name for p in panel_paths]
|
|
84
|
+
panel_info = {
|
|
85
|
+
"panels": panel_names,
|
|
86
|
+
"panel_paths": panel_paths,
|
|
87
|
+
"panel_is_zip": panel_is_zip,
|
|
88
|
+
"current_index": panel_index,
|
|
89
|
+
"figz_dir": str(bundle_dir),
|
|
90
|
+
"figz_is_zip": figz_is_zip,
|
|
91
|
+
"bundle_path": str(path) if figz_is_zip else None, # Original figz zip path for export/download
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# Resolve the selected panel
|
|
95
|
+
result = resolve_pltz_bundle(Path(selected_panel_path))
|
|
96
|
+
return result + (panel_info,)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def resolve_pltz_bundle(path: Path) -> Tuple:
|
|
100
|
+
"""
|
|
101
|
+
Resolve paths from a .pltz bundle (directory or ZIP).
|
|
102
|
+
|
|
103
|
+
Supports both:
|
|
104
|
+
- Legacy format (single {basename}.json)
|
|
105
|
+
- Layered format v2.0 (spec.json + style.json + cache/)
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
path : Path
|
|
110
|
+
Path to .pltz bundle (.pltz or .pltz.d)
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
tuple
|
|
115
|
+
(json_path, csv_path, png_path, hitmap_path, bundle_spec)
|
|
116
|
+
"""
|
|
117
|
+
spath = str(path)
|
|
118
|
+
|
|
119
|
+
# Handle ZIP vs directory
|
|
120
|
+
if spath.endswith('.pltz') and not spath.endswith('.pltz.d'):
|
|
121
|
+
if not path.exists():
|
|
122
|
+
raise FileNotFoundError(f"Bundle not found: {path}")
|
|
123
|
+
temp_dir = tempfile.mkdtemp(prefix='scitex_edit_')
|
|
124
|
+
with zipfile.ZipFile(path, 'r') as zf:
|
|
125
|
+
zf.extractall(temp_dir)
|
|
126
|
+
bundle_dir = Path(temp_dir)
|
|
127
|
+
for item in bundle_dir.iterdir():
|
|
128
|
+
if item.is_dir() and str(item).endswith('.pltz.d'):
|
|
129
|
+
bundle_dir = item
|
|
130
|
+
break
|
|
131
|
+
else:
|
|
132
|
+
bundle_dir = Path(path)
|
|
133
|
+
if not bundle_dir.exists():
|
|
134
|
+
raise FileNotFoundError(f"Bundle directory not found: {bundle_dir}")
|
|
135
|
+
|
|
136
|
+
# Check if this is a layered bundle (v2.0)
|
|
137
|
+
spec_path = bundle_dir / "spec.json"
|
|
138
|
+
if spec_path.exists():
|
|
139
|
+
return resolve_layered_pltz_bundle(bundle_dir)
|
|
140
|
+
|
|
141
|
+
# === Legacy format ===
|
|
142
|
+
json_path = None
|
|
143
|
+
csv_path = None
|
|
144
|
+
png_path = None
|
|
145
|
+
svg_path = None
|
|
146
|
+
hitmap_path = None
|
|
147
|
+
bundle_spec = None
|
|
148
|
+
|
|
149
|
+
for f in bundle_dir.iterdir():
|
|
150
|
+
name = f.name
|
|
151
|
+
if name.endswith('.json') and not name.endswith('.manual.json'):
|
|
152
|
+
json_path = f
|
|
153
|
+
elif name.endswith('.csv'):
|
|
154
|
+
csv_path = f
|
|
155
|
+
elif name.endswith('_hitmap.png'):
|
|
156
|
+
hitmap_path = f
|
|
157
|
+
elif name.endswith('.svg') and '_hitmap' not in name:
|
|
158
|
+
svg_path = f
|
|
159
|
+
elif name.endswith('.png') and '_hitmap' not in name and '_overview' not in name:
|
|
160
|
+
png_path = f
|
|
161
|
+
|
|
162
|
+
# Prefer SVG for display
|
|
163
|
+
if svg_path:
|
|
164
|
+
png_path = svg_path
|
|
165
|
+
|
|
166
|
+
if json_path and json_path.exists():
|
|
167
|
+
with open(json_path, 'r') as f:
|
|
168
|
+
bundle_spec = json_module.load(f)
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
json_path,
|
|
172
|
+
csv_path if csv_path and csv_path.exists() else None,
|
|
173
|
+
png_path if png_path and png_path.exists() else None,
|
|
174
|
+
hitmap_path if hitmap_path and hitmap_path.exists() else None,
|
|
175
|
+
bundle_spec,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def resolve_layered_pltz_bundle(bundle_dir: Path) -> Tuple:
|
|
180
|
+
"""
|
|
181
|
+
Resolve paths from a layered .pltz bundle (v2.0 format).
|
|
182
|
+
|
|
183
|
+
Layered format structure:
|
|
184
|
+
plot.pltz.d/
|
|
185
|
+
spec.json # Semantic
|
|
186
|
+
style.json # Appearance
|
|
187
|
+
{basename}.csv # Data
|
|
188
|
+
exports/ # PNG, SVG, hitmap
|
|
189
|
+
cache/ # geometry_px.json
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
bundle_dir : Path
|
|
194
|
+
Path to .pltz.d bundle directory.
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
tuple
|
|
199
|
+
(json_path, csv_path, png_path, hitmap_path, bundle_spec)
|
|
200
|
+
"""
|
|
201
|
+
from scitex.plt.io import load_layered_pltz_bundle
|
|
202
|
+
|
|
203
|
+
bundle_data = load_layered_pltz_bundle(bundle_dir)
|
|
204
|
+
spec_path = bundle_dir / "spec.json"
|
|
205
|
+
csv_path = None
|
|
206
|
+
png_path = None
|
|
207
|
+
hitmap_path = None
|
|
208
|
+
|
|
209
|
+
# Find CSV
|
|
210
|
+
for f in bundle_dir.glob("*.csv"):
|
|
211
|
+
csv_path = f
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
# Find exports
|
|
215
|
+
exports_dir = bundle_dir / "exports"
|
|
216
|
+
if exports_dir.exists():
|
|
217
|
+
for f in exports_dir.iterdir():
|
|
218
|
+
name = f.name
|
|
219
|
+
if name.endswith('_hitmap.png'):
|
|
220
|
+
hitmap_path = f
|
|
221
|
+
elif name.endswith('.svg') and '_hitmap' not in name:
|
|
222
|
+
png_path = f
|
|
223
|
+
elif name.endswith('.png') and '_hitmap' not in name and png_path is None:
|
|
224
|
+
png_path = f
|
|
225
|
+
|
|
226
|
+
bundle_spec = bundle_data.get("merged", {})
|
|
227
|
+
|
|
228
|
+
if hitmap_path and "hit_regions" in bundle_spec:
|
|
229
|
+
bundle_spec["hit_regions"]["hit_map"] = str(hitmap_path.name)
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
spec_path,
|
|
233
|
+
csv_path if csv_path and csv_path.exists() else None,
|
|
234
|
+
png_path if png_path and png_path.exists() else None,
|
|
235
|
+
hitmap_path if hitmap_path and hitmap_path.exists() else None,
|
|
236
|
+
bundle_spec,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# EOF
|