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
scitex/io/_save.py
CHANGED
|
@@ -161,7 +161,7 @@ def save(
|
|
|
161
161
|
auto_crop: bool = True,
|
|
162
162
|
crop_margin_mm: float = 1.0,
|
|
163
163
|
metadata_extra: dict = None,
|
|
164
|
-
json_schema: str = "
|
|
164
|
+
json_schema: str = "editable",
|
|
165
165
|
**kwargs,
|
|
166
166
|
) -> None:
|
|
167
167
|
"""
|
|
@@ -206,9 +206,10 @@ def save(
|
|
|
206
206
|
Default is None.
|
|
207
207
|
json_schema : str, optional
|
|
208
208
|
Schema type for JSON metadata output. Options:
|
|
209
|
-
- "
|
|
209
|
+
- "editable": Schema v0.3.0 with element geometry for interactive editing (default)
|
|
210
|
+
- "recipe": Minimal schema with method calls + data refs
|
|
210
211
|
- "verbose": Full schema with all artist details
|
|
211
|
-
Default is "
|
|
212
|
+
Default is "editable".
|
|
212
213
|
**kwargs
|
|
213
214
|
Additional keyword arguments to pass to the underlying save function of the specific format.
|
|
214
215
|
|
|
@@ -218,9 +219,16 @@ def save(
|
|
|
218
219
|
|
|
219
220
|
Notes
|
|
220
221
|
-----
|
|
221
|
-
Supported formats include CSV, NPY, PKL, JOBLIB, PNG, HTML, TIFF, MP4, YAML, JSON, HDF5, PTH, MAT,
|
|
222
|
+
Supported formats include CSV, NPY, PKL, JOBLIB, PNG, HTML, TIFF, MP4, YAML, JSON, HDF5, PTH, MAT, CBM,
|
|
223
|
+
and SciTeX bundles (.figz, .pltz, .statsz).
|
|
222
224
|
The function dynamically selects the appropriate saving mechanism based on the file extension.
|
|
223
225
|
|
|
226
|
+
Bundle Formats:
|
|
227
|
+
- .figz: Publication figure bundle (panels dict). Default: ZIP archive.
|
|
228
|
+
- .pltz: Plot bundle (matplotlib figure). Default: directory bundle.
|
|
229
|
+
- .statsz: Statistics bundle (comparisons list). Default: directory bundle.
|
|
230
|
+
- Use .d suffix (e.g., "Figure1.figz.d") to force directory format for .figz.
|
|
231
|
+
|
|
224
232
|
Examples
|
|
225
233
|
--------
|
|
226
234
|
>>> import scitex
|
|
@@ -495,6 +503,482 @@ def _symlink_to(spath_final, symlink_to, verbose):
|
|
|
495
503
|
logger.success(f"Symlinked: {spath_final} -> {symlink_to_full}")
|
|
496
504
|
|
|
497
505
|
|
|
506
|
+
def _save_pltz_bundle(obj, spath, as_zip=False, data=None, layered=True, **kwargs):
|
|
507
|
+
"""Save a matplotlib figure as a .pltz bundle.
|
|
508
|
+
|
|
509
|
+
Bundle structure v2.0 (layered - default):
|
|
510
|
+
plot.pltz.d/
|
|
511
|
+
spec.json # Semantic: WHAT to plot (canonical)
|
|
512
|
+
style.json # Appearance: HOW it looks (canonical)
|
|
513
|
+
data.csv # Raw data (immutable)
|
|
514
|
+
exports/ # PNG, SVG, hitmap
|
|
515
|
+
cache/ # geometry_px.json, render_manifest.json
|
|
516
|
+
|
|
517
|
+
Bundle structure v1.0 (legacy):
|
|
518
|
+
plot.json - specification (axes, styles, theme, etc.)
|
|
519
|
+
plot.csv - raw data (immutable)
|
|
520
|
+
plot.png - raster export (required)
|
|
521
|
+
plot.svg - vector export (optional)
|
|
522
|
+
plot.pdf - publication export (optional)
|
|
523
|
+
|
|
524
|
+
Parameters
|
|
525
|
+
----------
|
|
526
|
+
obj : matplotlib.figure.Figure
|
|
527
|
+
The figure to save.
|
|
528
|
+
spath : str or Path
|
|
529
|
+
Output path (e.g., "plot.pltz.d" or "plot.pltz").
|
|
530
|
+
as_zip : bool
|
|
531
|
+
If True, save as ZIP archive.
|
|
532
|
+
data : pandas.DataFrame, optional
|
|
533
|
+
Data to embed in the bundle as plot.csv.
|
|
534
|
+
layered : bool
|
|
535
|
+
If True (default), use new layered format (spec/style/geometry).
|
|
536
|
+
If False, use legacy single JSON format.
|
|
537
|
+
**kwargs
|
|
538
|
+
Additional arguments passed to savefig.
|
|
539
|
+
"""
|
|
540
|
+
from pathlib import Path
|
|
541
|
+
import tempfile
|
|
542
|
+
import json
|
|
543
|
+
import numpy as np
|
|
544
|
+
from ._bundle import save_bundle, BundleType
|
|
545
|
+
|
|
546
|
+
p = Path(spath)
|
|
547
|
+
|
|
548
|
+
# Extract basename from path (e.g., "myplot.pltz" -> "myplot", "myplot.pltz.d" -> "myplot")
|
|
549
|
+
basename = p.stem # e.g., "myplot.pltz" or "myplot"
|
|
550
|
+
if basename.endswith('.pltz'):
|
|
551
|
+
basename = basename[:-5] # Remove .pltz suffix
|
|
552
|
+
elif basename.endswith('.d'):
|
|
553
|
+
# Handle myplot.pltz.d -> myplot.pltz -> myplot
|
|
554
|
+
basename = Path(basename).stem
|
|
555
|
+
if basename.endswith('.pltz'):
|
|
556
|
+
basename = basename[:-5]
|
|
557
|
+
|
|
558
|
+
# Extract figure from various matplotlib object types
|
|
559
|
+
import matplotlib.figure
|
|
560
|
+
fig = obj
|
|
561
|
+
if hasattr(obj, 'figure'):
|
|
562
|
+
fig = obj.figure
|
|
563
|
+
elif hasattr(obj, 'fig'):
|
|
564
|
+
fig = obj.fig
|
|
565
|
+
|
|
566
|
+
if not isinstance(fig, matplotlib.figure.Figure):
|
|
567
|
+
raise TypeError(f"Expected matplotlib Figure, got {type(obj).__name__}")
|
|
568
|
+
|
|
569
|
+
dpi = kwargs.pop('dpi', 300)
|
|
570
|
+
|
|
571
|
+
# === Always use layered format ===
|
|
572
|
+
from scitex.plt.io import save_layered_pltz_bundle
|
|
573
|
+
import shutil
|
|
574
|
+
import tempfile
|
|
575
|
+
|
|
576
|
+
# Determine bundle directory path
|
|
577
|
+
if as_zip:
|
|
578
|
+
# For ZIP: save to temp dir, then compress
|
|
579
|
+
temp_dir = Path(tempfile.mkdtemp())
|
|
580
|
+
bundle_dir = temp_dir / f"{basename}.pltz.d"
|
|
581
|
+
zip_path = p if not str(p).endswith('.d') else Path(str(p)[:-2])
|
|
582
|
+
else:
|
|
583
|
+
# For directory: save directly
|
|
584
|
+
bundle_dir = p if str(p).endswith('.d') else Path(str(p) + '.d')
|
|
585
|
+
|
|
586
|
+
# Get CSV data from figure if not provided
|
|
587
|
+
csv_df = data
|
|
588
|
+
if csv_df is None:
|
|
589
|
+
csv_source = _get_figure_with_data(obj)
|
|
590
|
+
if csv_source is not None and hasattr(csv_source, 'export_as_csv'):
|
|
591
|
+
try:
|
|
592
|
+
csv_df = csv_source.export_as_csv()
|
|
593
|
+
except Exception:
|
|
594
|
+
pass
|
|
595
|
+
|
|
596
|
+
save_layered_pltz_bundle(
|
|
597
|
+
fig=fig,
|
|
598
|
+
bundle_dir=bundle_dir,
|
|
599
|
+
basename=basename,
|
|
600
|
+
dpi=dpi,
|
|
601
|
+
csv_df=csv_df,
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
# Compress to ZIP if requested
|
|
605
|
+
if as_zip:
|
|
606
|
+
from ._bundle import pack_bundle
|
|
607
|
+
pack_bundle(bundle_dir, zip_path)
|
|
608
|
+
shutil.rmtree(temp_dir) # Clean up temp directory
|
|
609
|
+
|
|
610
|
+
return # Done with layered format
|
|
611
|
+
|
|
612
|
+
# === Legacy format below (DEPRECATED - kept for reference) ===
|
|
613
|
+
|
|
614
|
+
# Calculate size info
|
|
615
|
+
fig_width_inch, fig_height_inch = fig.get_size_inches()
|
|
616
|
+
fig_dpi = fig.get_dpi()
|
|
617
|
+
|
|
618
|
+
# Build spec according to contract (using basename for file references)
|
|
619
|
+
spec = {
|
|
620
|
+
'schema': {'name': 'scitex.plt.plot', 'version': '1.0.0'},
|
|
621
|
+
'backend': 'mpl',
|
|
622
|
+
'data': {
|
|
623
|
+
'source': f'{basename}.csv',
|
|
624
|
+
'path': f'{basename}.csv',
|
|
625
|
+
'hash': None, # Will be computed after data extraction
|
|
626
|
+
'columns': [], # Will be populated after data extraction
|
|
627
|
+
},
|
|
628
|
+
'size': {
|
|
629
|
+
'width_inch': round(fig_width_inch, 2),
|
|
630
|
+
'height_inch': round(fig_height_inch, 2),
|
|
631
|
+
'width_mm': round(fig_width_inch * 25.4, 2),
|
|
632
|
+
'height_mm': round(fig_height_inch * 25.4, 2),
|
|
633
|
+
'width_px': int(fig_width_inch * dpi),
|
|
634
|
+
'height_px': int(fig_height_inch * dpi),
|
|
635
|
+
'dpi': dpi,
|
|
636
|
+
'crop_margin_mm': 1.0,
|
|
637
|
+
},
|
|
638
|
+
'axes': [],
|
|
639
|
+
'theme': {
|
|
640
|
+
'mode': 'light',
|
|
641
|
+
'colors': {
|
|
642
|
+
'background': 'transparent',
|
|
643
|
+
'axes_bg': 'white',
|
|
644
|
+
'text': 'black',
|
|
645
|
+
'spine': 'black',
|
|
646
|
+
'tick': 'black',
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
# Extract data from plot lines if no data provided
|
|
652
|
+
extracted_data = {}
|
|
653
|
+
|
|
654
|
+
# Extract axes metadata
|
|
655
|
+
for i, ax in enumerate(fig.axes):
|
|
656
|
+
# Get axes bounding box in figure coordinates (0-1)
|
|
657
|
+
bbox = ax.get_position()
|
|
658
|
+
|
|
659
|
+
ax_info = {
|
|
660
|
+
'xlabel': ax.get_xlabel() or None,
|
|
661
|
+
'ylabel': ax.get_ylabel() or None,
|
|
662
|
+
'title': ax.get_title() or None,
|
|
663
|
+
'xlim': [round(v, 2) for v in ax.get_xlim()],
|
|
664
|
+
'ylim': [round(v, 2) for v in ax.get_ylim()],
|
|
665
|
+
'plot_type': 'line', # Default, could be detected
|
|
666
|
+
# Bounding box in normalized figure coordinates (0-1)
|
|
667
|
+
'bbox': {
|
|
668
|
+
'x0': round(bbox.x0, 4),
|
|
669
|
+
'y0': round(bbox.y0, 4),
|
|
670
|
+
'x1': round(bbox.x1, 4),
|
|
671
|
+
'y1': round(bbox.y1, 4),
|
|
672
|
+
'width': round(bbox.width, 4),
|
|
673
|
+
'height': round(bbox.height, 4),
|
|
674
|
+
},
|
|
675
|
+
# Bounding box in mm
|
|
676
|
+
'bbox_mm': {
|
|
677
|
+
'x0': round(bbox.x0 * fig_width_inch * 25.4, 2),
|
|
678
|
+
'y0': round(bbox.y0 * fig_height_inch * 25.4, 2),
|
|
679
|
+
'x1': round(bbox.x1 * fig_width_inch * 25.4, 2),
|
|
680
|
+
'y1': round(bbox.y1 * fig_height_inch * 25.4, 2),
|
|
681
|
+
'width': round(bbox.width * fig_width_inch * 25.4, 2),
|
|
682
|
+
'height': round(bbox.height * fig_height_inch * 25.4, 2),
|
|
683
|
+
},
|
|
684
|
+
# Bounding box in pixels
|
|
685
|
+
'bbox_px': {
|
|
686
|
+
'x0': int(bbox.x0 * fig_width_inch * dpi),
|
|
687
|
+
'y0': int(bbox.y0 * fig_height_inch * dpi),
|
|
688
|
+
'x1': int(bbox.x1 * fig_width_inch * dpi),
|
|
689
|
+
'y1': int(bbox.y1 * fig_height_inch * dpi),
|
|
690
|
+
'width': int(bbox.width * fig_width_inch * dpi),
|
|
691
|
+
'height': int(bbox.height * fig_height_inch * dpi),
|
|
692
|
+
},
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
# SciTeX-specific axis dimensions
|
|
696
|
+
if hasattr(ax, '_scitex_axes_width_mm'):
|
|
697
|
+
ax_info['axes_width_mm'] = ax._scitex_axes_width_mm
|
|
698
|
+
else:
|
|
699
|
+
ax_info['axes_width_mm'] = round(bbox.width * fig_width_inch * 25.4, 1)
|
|
700
|
+
|
|
701
|
+
if hasattr(ax, '_scitex_axes_height_mm'):
|
|
702
|
+
ax_info['axes_height_mm'] = ax._scitex_axes_height_mm
|
|
703
|
+
else:
|
|
704
|
+
ax_info['axes_height_mm'] = round(bbox.height * fig_height_inch * 25.4, 1)
|
|
705
|
+
|
|
706
|
+
# Extract line data for CSV and build lines array
|
|
707
|
+
lines_info = []
|
|
708
|
+
for j, line in enumerate(ax.get_lines()):
|
|
709
|
+
label = line.get_label()
|
|
710
|
+
if label is None or label.startswith('_'):
|
|
711
|
+
label = f'series_{j}'
|
|
712
|
+
xdata, ydata = line.get_data()
|
|
713
|
+
if len(xdata) > 0:
|
|
714
|
+
col_x = f'{label}_x' if i == 0 else f'ax{i}_{label}_x'
|
|
715
|
+
col_y = f'{label}_y' if i == 0 else f'ax{i}_{label}_y'
|
|
716
|
+
extracted_data[col_x] = np.array(xdata)
|
|
717
|
+
extracted_data[col_y] = np.array(ydata)
|
|
718
|
+
|
|
719
|
+
# Get line color (convert RGBA to hex)
|
|
720
|
+
color = line.get_color()
|
|
721
|
+
if isinstance(color, (list, tuple)):
|
|
722
|
+
import matplotlib.colors as mcolors
|
|
723
|
+
color = mcolors.to_hex(color)
|
|
724
|
+
|
|
725
|
+
lines_info.append({
|
|
726
|
+
'label': label,
|
|
727
|
+
'x_col': col_x,
|
|
728
|
+
'y_col': col_y,
|
|
729
|
+
'color': color,
|
|
730
|
+
'linewidth': line.get_linewidth(),
|
|
731
|
+
})
|
|
732
|
+
|
|
733
|
+
if lines_info:
|
|
734
|
+
ax_info['lines'] = lines_info
|
|
735
|
+
|
|
736
|
+
spec['axes'].append(ax_info)
|
|
737
|
+
|
|
738
|
+
# Handle theme from figure
|
|
739
|
+
if hasattr(fig, '_scitex_theme'):
|
|
740
|
+
theme_mode = fig._scitex_theme
|
|
741
|
+
spec['theme']['mode'] = theme_mode
|
|
742
|
+
# Update colors based on theme mode
|
|
743
|
+
if theme_mode == 'dark':
|
|
744
|
+
spec['theme']['colors'] = {
|
|
745
|
+
'background': 'transparent',
|
|
746
|
+
'axes_bg': 'transparent',
|
|
747
|
+
'text': '#e8e8e8',
|
|
748
|
+
'spine': '#e8e8e8',
|
|
749
|
+
'tick': '#e8e8e8',
|
|
750
|
+
}
|
|
751
|
+
# Re-apply theme colors to ensure legends and other elements get the correct colors
|
|
752
|
+
from scitex.plt.utils._figure_mm import _apply_theme_colors
|
|
753
|
+
for ax in fig.axes:
|
|
754
|
+
_apply_theme_colors(ax, theme='dark')
|
|
755
|
+
|
|
756
|
+
# Build bundle data (include basename for file naming)
|
|
757
|
+
bundle_data = {'spec': spec, 'basename': basename}
|
|
758
|
+
|
|
759
|
+
# Use provided data or extracted data for CSV
|
|
760
|
+
# Priority: 1) explicit data param, 2) export_as_csv method, 3) line extraction fallback
|
|
761
|
+
csv_df = None
|
|
762
|
+
if data is not None:
|
|
763
|
+
csv_df = data
|
|
764
|
+
bundle_data['data'] = data
|
|
765
|
+
else:
|
|
766
|
+
# Try to use export_as_csv from SciTeX wrapped objects (handles all plot types)
|
|
767
|
+
csv_source = _get_figure_with_data(obj)
|
|
768
|
+
if csv_source is not None and hasattr(csv_source, 'export_as_csv'):
|
|
769
|
+
try:
|
|
770
|
+
csv_df = csv_source.export_as_csv()
|
|
771
|
+
if csv_df is not None and not csv_df.empty:
|
|
772
|
+
bundle_data['data'] = csv_df
|
|
773
|
+
logger.debug(f"CSV data extracted via export_as_csv: {len(csv_df)} rows, {len(csv_df.columns)} cols")
|
|
774
|
+
except Exception as e:
|
|
775
|
+
logger.debug(f"export_as_csv failed: {e}")
|
|
776
|
+
csv_df = None
|
|
777
|
+
|
|
778
|
+
# Fallback to line extraction if export_as_csv didn't work
|
|
779
|
+
if csv_df is None and extracted_data:
|
|
780
|
+
try:
|
|
781
|
+
import pandas as pd
|
|
782
|
+
# Pad arrays to same length
|
|
783
|
+
max_len = max(len(v) for v in extracted_data.values())
|
|
784
|
+
padded = {}
|
|
785
|
+
for k, v in extracted_data.items():
|
|
786
|
+
if len(v) < max_len:
|
|
787
|
+
padded[k] = np.pad(v, (0, max_len - len(v)), constant_values=np.nan)
|
|
788
|
+
else:
|
|
789
|
+
padded[k] = v
|
|
790
|
+
csv_df = pd.DataFrame(padded)
|
|
791
|
+
bundle_data['data'] = csv_df
|
|
792
|
+
logger.debug(f"CSV data extracted via line fallback: {len(csv_df)} rows")
|
|
793
|
+
except ImportError:
|
|
794
|
+
pass
|
|
795
|
+
|
|
796
|
+
# Compute hash and columns for data section
|
|
797
|
+
if csv_df is not None:
|
|
798
|
+
import hashlib
|
|
799
|
+
# Get CSV string for hash computation
|
|
800
|
+
csv_str = csv_df.to_csv(index=False)
|
|
801
|
+
csv_hash = hashlib.sha256(csv_str.encode()).hexdigest()
|
|
802
|
+
spec['data']['hash'] = f'sha256:{csv_hash[:16]}'
|
|
803
|
+
spec['data']['columns'] = list(csv_df.columns)
|
|
804
|
+
|
|
805
|
+
# Save figure to multiple formats
|
|
806
|
+
import warnings
|
|
807
|
+
from PIL import Image as PILImage
|
|
808
|
+
from scitex.plt.utils._hitmap import (
|
|
809
|
+
apply_hitmap_colors, restore_original_colors, extract_path_data,
|
|
810
|
+
extract_selectable_regions, HITMAP_BACKGROUND_COLOR, HITMAP_AXES_COLOR
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
crop_box = None
|
|
814
|
+
color_map = {}
|
|
815
|
+
|
|
816
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
817
|
+
tmp_path = Path(tmp_dir)
|
|
818
|
+
|
|
819
|
+
# Suppress tight_layout warnings for SciTeX figures with custom axes
|
|
820
|
+
with warnings.catch_warnings():
|
|
821
|
+
warnings.filterwarnings('ignore', message='.*tight_layout.*')
|
|
822
|
+
|
|
823
|
+
# Always use transparent background for SciTeX figures (both light and dark themes)
|
|
824
|
+
use_transparent = True
|
|
825
|
+
|
|
826
|
+
# Save PNG (raster) - required
|
|
827
|
+
png_path = tmp_path / "plot.png"
|
|
828
|
+
fig.savefig(png_path, dpi=dpi, bbox_inches='tight', format='png', transparent=use_transparent)
|
|
829
|
+
|
|
830
|
+
# Save SVG (vector) - optional
|
|
831
|
+
svg_path = tmp_path / "plot.svg"
|
|
832
|
+
fig.savefig(svg_path, bbox_inches='tight', format='svg')
|
|
833
|
+
|
|
834
|
+
# Save PDF (vector) - optional
|
|
835
|
+
pdf_path = tmp_path / "plot.pdf"
|
|
836
|
+
fig.savefig(pdf_path, bbox_inches='tight', format='pdf')
|
|
837
|
+
|
|
838
|
+
# Now generate hitmap by applying ID colors to data elements ONLY
|
|
839
|
+
# Keep axes/spines/labels with original colors to preserve bbox_inches='tight' bounds
|
|
840
|
+
# Also detects logical groups (histogram, bar_series, etc.)
|
|
841
|
+
original_props, color_map, groups = apply_hitmap_colors(fig)
|
|
842
|
+
|
|
843
|
+
# Store original background colors and set hitmap colors
|
|
844
|
+
original_fig_facecolor = fig.patch.get_facecolor()
|
|
845
|
+
original_ax_facecolors = []
|
|
846
|
+
original_ax_props = []
|
|
847
|
+
for ax in fig.axes:
|
|
848
|
+
original_ax_facecolors.append(ax.get_facecolor())
|
|
849
|
+
# Store axis element colors for restoration
|
|
850
|
+
ax_props = {
|
|
851
|
+
'ax': ax,
|
|
852
|
+
'spine_colors': {k: v.get_edgecolor() for k, v in ax.spines.items()},
|
|
853
|
+
'tick_colors': ax.tick_params, # Will restore later
|
|
854
|
+
'xlabel_color': ax.xaxis.label.get_color(),
|
|
855
|
+
'ylabel_color': ax.yaxis.label.get_color(),
|
|
856
|
+
'title_color': ax.title.get_color(),
|
|
857
|
+
}
|
|
858
|
+
original_ax_props.append(ax_props)
|
|
859
|
+
# Set hitmap colors for non-data elements
|
|
860
|
+
ax.set_facecolor(HITMAP_BACKGROUND_COLOR)
|
|
861
|
+
for spine in ax.spines.values():
|
|
862
|
+
spine.set_color(HITMAP_AXES_COLOR)
|
|
863
|
+
ax.tick_params(colors=HITMAP_AXES_COLOR, labelcolor=HITMAP_AXES_COLOR)
|
|
864
|
+
ax.xaxis.label.set_color(HITMAP_AXES_COLOR)
|
|
865
|
+
ax.yaxis.label.set_color(HITMAP_AXES_COLOR)
|
|
866
|
+
ax.title.set_color(HITMAP_AXES_COLOR)
|
|
867
|
+
|
|
868
|
+
fig.patch.set_facecolor(HITMAP_BACKGROUND_COLOR)
|
|
869
|
+
|
|
870
|
+
# Save hitmap PNG with same bbox_inches='tight'
|
|
871
|
+
hitmap_path = tmp_path / "plot_hitmap.png"
|
|
872
|
+
fig.savefig(hitmap_path, dpi=dpi, bbox_inches='tight', format='png', facecolor=HITMAP_BACKGROUND_COLOR)
|
|
873
|
+
|
|
874
|
+
# Optimize hitmap PNG size using zlib compression
|
|
875
|
+
try:
|
|
876
|
+
hitmap_img = PILImage.open(hitmap_path).convert('RGB')
|
|
877
|
+
hitmap_img.save(hitmap_path, format='PNG', optimize=True, compress_level=9)
|
|
878
|
+
except Exception:
|
|
879
|
+
pass # Keep original if optimization fails
|
|
880
|
+
|
|
881
|
+
# Save hitmap SVG with same bbox_inches='tight'
|
|
882
|
+
hitmap_svg_path = tmp_path / "plot_hitmap.svg"
|
|
883
|
+
fig.savefig(hitmap_svg_path, bbox_inches='tight', format='svg')
|
|
884
|
+
|
|
885
|
+
# Restore original colors (data elements)
|
|
886
|
+
restore_original_colors(original_props)
|
|
887
|
+
|
|
888
|
+
# Restore original figure and axes colors
|
|
889
|
+
fig.patch.set_facecolor(original_fig_facecolor)
|
|
890
|
+
for i, ax in enumerate(fig.axes):
|
|
891
|
+
ax.set_facecolor(original_ax_facecolors[i])
|
|
892
|
+
if i < len(original_ax_props):
|
|
893
|
+
props = original_ax_props[i]
|
|
894
|
+
for spine_name, color in props['spine_colors'].items():
|
|
895
|
+
ax.spines[spine_name].set_edgecolor(color)
|
|
896
|
+
ax.xaxis.label.set_color(props['xlabel_color'])
|
|
897
|
+
ax.yaxis.label.set_color(props['ylabel_color'])
|
|
898
|
+
ax.title.set_color(props['title_color'])
|
|
899
|
+
|
|
900
|
+
# Now apply auto-crop to BOTH PNG and hitmap with same parameters
|
|
901
|
+
try:
|
|
902
|
+
from scitex.plt.utils._crop import crop
|
|
903
|
+
|
|
904
|
+
# Crop PNG and get crop coordinates
|
|
905
|
+
_, crop_offset = crop(
|
|
906
|
+
str(png_path),
|
|
907
|
+
output_path=str(png_path),
|
|
908
|
+
overwrite=True,
|
|
909
|
+
margin=12, # ~1mm at 300 DPI
|
|
910
|
+
verbose=False,
|
|
911
|
+
return_offset=True,
|
|
912
|
+
)
|
|
913
|
+
crop_box = (crop_offset['left'], crop_offset['upper'],
|
|
914
|
+
crop_offset['right'], crop_offset['lower'])
|
|
915
|
+
|
|
916
|
+
# Apply SAME crop to hitmap PNG
|
|
917
|
+
crop(
|
|
918
|
+
str(hitmap_path),
|
|
919
|
+
output_path=str(hitmap_path),
|
|
920
|
+
overwrite=True,
|
|
921
|
+
crop_box=crop_box,
|
|
922
|
+
verbose=False,
|
|
923
|
+
)
|
|
924
|
+
except Exception as e:
|
|
925
|
+
crop_box = None
|
|
926
|
+
logger.debug(f"Crop failed: {e}")
|
|
927
|
+
|
|
928
|
+
# Validate sizes match
|
|
929
|
+
with PILImage.open(png_path) as png_img, PILImage.open(hitmap_path) as hm_img:
|
|
930
|
+
if png_img.size != hm_img.size:
|
|
931
|
+
logger.warning(f"Size mismatch: PNG={png_img.size}, Hitmap={hm_img.size}")
|
|
932
|
+
|
|
933
|
+
with open(png_path, 'rb') as f:
|
|
934
|
+
bundle_data['png'] = f.read()
|
|
935
|
+
|
|
936
|
+
with open(hitmap_path, 'rb') as f:
|
|
937
|
+
bundle_data['hitmap_png'] = f.read()
|
|
938
|
+
|
|
939
|
+
with open(svg_path, 'rb') as f:
|
|
940
|
+
bundle_data['svg'] = f.read()
|
|
941
|
+
|
|
942
|
+
with open(hitmap_svg_path, 'rb') as f:
|
|
943
|
+
bundle_data['hitmap_svg'] = f.read()
|
|
944
|
+
|
|
945
|
+
with open(pdf_path, 'rb') as f:
|
|
946
|
+
bundle_data['pdf'] = f.read()
|
|
947
|
+
|
|
948
|
+
# Add hit_regions to spec
|
|
949
|
+
try:
|
|
950
|
+
path_data = extract_path_data(fig)
|
|
951
|
+
|
|
952
|
+
spec['hit_regions'] = {
|
|
953
|
+
'strategy': 'hybrid',
|
|
954
|
+
'hit_map': f'{basename}_hitmap.png',
|
|
955
|
+
'hit_map_svg': f'{basename}_hitmap.svg',
|
|
956
|
+
'color_map': {str(k): v for k, v in color_map.items()},
|
|
957
|
+
'groups': groups, # Logical groups (histogram, bar_series, etc.)
|
|
958
|
+
'path_data': path_data,
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if crop_box is not None:
|
|
962
|
+
spec['hit_regions']['crop_box'] = {
|
|
963
|
+
'left': int(crop_box[0]),
|
|
964
|
+
'upper': int(crop_box[1]),
|
|
965
|
+
'right': int(crop_box[2]),
|
|
966
|
+
'lower': int(crop_box[3]),
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
# Extract selectable regions (bounding boxes for axis/annotation elements)
|
|
970
|
+
# This complements hitmap color-based selection with bbox-based selection
|
|
971
|
+
selectable_regions = extract_selectable_regions(fig)
|
|
972
|
+
if selectable_regions and selectable_regions.get('axes'):
|
|
973
|
+
spec['selectable_regions'] = selectable_regions
|
|
974
|
+
|
|
975
|
+
except Exception as e:
|
|
976
|
+
logger.debug(f"Hit regions spec failed: {e}")
|
|
977
|
+
|
|
978
|
+
# Save the bundle
|
|
979
|
+
save_bundle(bundle_data, p, bundle_type=BundleType.PLTZ, as_zip=as_zip)
|
|
980
|
+
|
|
981
|
+
|
|
498
982
|
def _save(
|
|
499
983
|
obj,
|
|
500
984
|
spath,
|
|
@@ -506,7 +990,7 @@ def _save(
|
|
|
506
990
|
auto_crop=False,
|
|
507
991
|
crop_margin_mm=1.0,
|
|
508
992
|
metadata_extra=None,
|
|
509
|
-
json_schema="
|
|
993
|
+
json_schema="editable",
|
|
510
994
|
**kwargs,
|
|
511
995
|
):
|
|
512
996
|
# Don't use object's own save method - use consistent handlers
|
|
@@ -521,6 +1005,50 @@ def _save(
|
|
|
521
1005
|
save_canvas(obj, spath, **kwargs)
|
|
522
1006
|
return
|
|
523
1007
|
|
|
1008
|
+
# Handle bundle formats (.figz, .pltz, .statsz and their .d variants)
|
|
1009
|
+
# These use special naming: file.figz (ZIP) or file.figz.d (directory)
|
|
1010
|
+
# Note: .figz defaults to ZIP (as_zip=True), .pltz/.statsz default to directory
|
|
1011
|
+
bundle_extensions = (".figz", ".pltz", ".statsz")
|
|
1012
|
+
for bext in bundle_extensions:
|
|
1013
|
+
if spath.endswith(bext) or spath.endswith(f"{bext}.d"):
|
|
1014
|
+
# Remove as_zip from kwargs if present to avoid duplicate
|
|
1015
|
+
bundle_kwargs = {k: v for k, v in kwargs.items() if k != 'as_zip'}
|
|
1016
|
+
as_zip = kwargs.get('as_zip', not spath.endswith(".d"))
|
|
1017
|
+
if bext == ".figz":
|
|
1018
|
+
import scitex.fig as sfig
|
|
1019
|
+
# figz defaults to ZIP, so always pass as_zip explicitly
|
|
1020
|
+
sfig.save_figz(obj, spath, as_zip=as_zip, **bundle_kwargs)
|
|
1021
|
+
elif bext == ".pltz":
|
|
1022
|
+
_save_pltz_bundle(obj, spath, as_zip=as_zip, **bundle_kwargs)
|
|
1023
|
+
elif bext == ".statsz":
|
|
1024
|
+
import scitex.stats as sstats
|
|
1025
|
+
sstats.save_statsz(obj, spath, as_zip=as_zip, **bundle_kwargs)
|
|
1026
|
+
|
|
1027
|
+
# Log "Saved to:" for bundle formats (consistent with other formats)
|
|
1028
|
+
# For bundles, determine the actual saved path (zip or directory)
|
|
1029
|
+
bundle_path = spath if as_zip else f"{spath}.d" if not spath.endswith(".d") else spath
|
|
1030
|
+
|
|
1031
|
+
if verbose and _os.path.exists(bundle_path):
|
|
1032
|
+
file_size = getsize(bundle_path)
|
|
1033
|
+
file_size = readable_bytes(file_size)
|
|
1034
|
+
try:
|
|
1035
|
+
rel_path = _os.path.relpath(bundle_path, _os.getcwd())
|
|
1036
|
+
except ValueError:
|
|
1037
|
+
rel_path = bundle_path
|
|
1038
|
+
logger.success(f"Saved to: ./{rel_path} ({file_size})")
|
|
1039
|
+
|
|
1040
|
+
# Handle symlinks for bundle formats (consistent with other formats)
|
|
1041
|
+
if symlink_from_cwd and _os.path.exists(bundle_path):
|
|
1042
|
+
# Create symlink from cwd to bundle path
|
|
1043
|
+
bundle_basename = _os.path.basename(bundle_path)
|
|
1044
|
+
bundle_cwd = _os.path.join(_os.getcwd(), bundle_basename)
|
|
1045
|
+
_symlink(bundle_path, bundle_cwd, symlink_from_cwd, verbose)
|
|
1046
|
+
|
|
1047
|
+
if symlink_to and _os.path.exists(bundle_path):
|
|
1048
|
+
_symlink_to(bundle_path, symlink_to, verbose)
|
|
1049
|
+
|
|
1050
|
+
return
|
|
1051
|
+
|
|
524
1052
|
# Try dispatch dictionary first for O(1) lookup
|
|
525
1053
|
if ext in _FILE_HANDLERS:
|
|
526
1054
|
# Check if handler needs special parameters
|
|
@@ -560,7 +1088,7 @@ def _save(
|
|
|
560
1088
|
elif spath.endswith(".pkl.gz"):
|
|
561
1089
|
save_pickle_compressed(obj, spath, **kwargs)
|
|
562
1090
|
else:
|
|
563
|
-
|
|
1091
|
+
logger.warning(f"Unsupported file format. {spath} was not saved.")
|
|
564
1092
|
|
|
565
1093
|
if verbose:
|
|
566
1094
|
if _os.path.exists(spath):
|
|
@@ -657,7 +1185,7 @@ def _handle_image_with_csv(
|
|
|
657
1185
|
auto_crop=True,
|
|
658
1186
|
crop_margin_mm=1.0,
|
|
659
1187
|
metadata_extra=None,
|
|
660
|
-
json_schema="
|
|
1188
|
+
json_schema="editable",
|
|
661
1189
|
**kwargs,
|
|
662
1190
|
):
|
|
663
1191
|
"""Handle image file saving with optional CSV export and auto-cropping."""
|
|
@@ -701,7 +1229,10 @@ def _handle_image_with_csv(
|
|
|
701
1229
|
|
|
702
1230
|
# Collect metadata using scitex's metadata collector
|
|
703
1231
|
try:
|
|
704
|
-
if json_schema == "
|
|
1232
|
+
if json_schema == "editable":
|
|
1233
|
+
from scitex.plt.utils.metadata import export_editable_figure
|
|
1234
|
+
auto_metadata = export_editable_figure(fig_mpl)
|
|
1235
|
+
elif json_schema == "recipe":
|
|
705
1236
|
from scitex.plt.utils import collect_recipe_metadata
|
|
706
1237
|
auto_metadata = collect_recipe_metadata(
|
|
707
1238
|
fig_mpl, ax,
|
|
@@ -716,15 +1247,14 @@ def _handle_image_with_csv(
|
|
|
716
1247
|
kwargs["metadata"] = auto_metadata
|
|
717
1248
|
collected_metadata = auto_metadata # Save for JSON export
|
|
718
1249
|
if verbose:
|
|
719
|
-
|
|
1250
|
+
schema_names = {"editable": "editable v0.3", "recipe": "recipe", "verbose": "verbose"}
|
|
1251
|
+
schema_name = schema_names.get(json_schema, json_schema)
|
|
720
1252
|
logger.info(f" • Auto-collected metadata ({schema_name} schema)")
|
|
721
1253
|
except ImportError:
|
|
722
1254
|
pass # collect_figure_metadata not available
|
|
723
1255
|
except Exception as e:
|
|
724
1256
|
if verbose:
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
warnings.warn(f"Could not auto-collect metadata: {e}")
|
|
1257
|
+
logger.warning(f"Could not auto-collect metadata: {e}")
|
|
728
1258
|
except Exception:
|
|
729
1259
|
pass # Silently continue if auto-collection fails
|
|
730
1260
|
else:
|
|
@@ -820,9 +1350,7 @@ def _handle_image_with_csv(
|
|
|
820
1350
|
)
|
|
821
1351
|
|
|
822
1352
|
except Exception as e:
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
warnings.warn(f"Auto-crop failed: {e}. Image saved without cropping.")
|
|
1353
|
+
logger.warning(f"Auto-crop failed: {e}. Image saved without cropping.")
|
|
826
1354
|
|
|
827
1355
|
# Handle separate legend saving
|
|
828
1356
|
_save_separate_legends(
|
|
@@ -1057,12 +1585,7 @@ def _handle_image_with_csv(
|
|
|
1057
1585
|
)
|
|
1058
1586
|
_symlink(csv_sigmaplot_path, csv_cwd, True, True)
|
|
1059
1587
|
except Exception as e:
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
warnings.warn(f"CSV export failed: {e}")
|
|
1063
|
-
import traceback
|
|
1064
|
-
|
|
1065
|
-
traceback.print_exc()
|
|
1588
|
+
logger.warning(f"CSV export failed: {e}")
|
|
1066
1589
|
|
|
1067
1590
|
# Save metadata as JSON if collected
|
|
1068
1591
|
if collected_metadata is not None and not dry_run:
|
|
@@ -1109,8 +1632,8 @@ def _handle_image_with_csv(
|
|
|
1109
1632
|
)
|
|
1110
1633
|
|
|
1111
1634
|
# Verify CSV/JSON consistency (data_ref must match columns_actual)
|
|
1112
|
-
# Only check for verbose schema - recipe
|
|
1113
|
-
if csv_path and not dry_run and json_schema
|
|
1635
|
+
# Only check for verbose schema - recipe/editable schemas use different data_ref structure
|
|
1636
|
+
if csv_path and not dry_run and json_schema == "verbose":
|
|
1114
1637
|
from scitex.plt.utils._collect_figure_metadata import (
|
|
1115
1638
|
assert_csv_json_consistency,
|
|
1116
1639
|
)
|
|
@@ -1181,17 +1704,12 @@ def _handle_image_with_csv(
|
|
|
1181
1704
|
# Re-raise assertion errors - these are validation failures that should stop execution
|
|
1182
1705
|
raise
|
|
1183
1706
|
except Exception as e:
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
warnings.warn(f"JSON metadata export failed: {e}")
|
|
1187
|
-
import traceback
|
|
1188
|
-
|
|
1189
|
-
traceback.print_exc()
|
|
1707
|
+
logger.warning(f"JSON metadata export failed: {e}")
|
|
1190
1708
|
|
|
1191
1709
|
|
|
1192
1710
|
# Dispatch dictionary for O(1) file format lookup
|
|
1193
1711
|
_FILE_HANDLERS = {
|
|
1194
|
-
# Canvas directory format (scitex.
|
|
1712
|
+
# Canvas directory format (scitex.fig)
|
|
1195
1713
|
".canvas": save_canvas,
|
|
1196
1714
|
# Excel formats
|
|
1197
1715
|
".xlsx": save_excel,
|