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
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-12-10 (ywatanabe)"
|
|
4
|
+
# File: scitex/plt/utils/metadata/_geometry_extraction.py
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Geometry extraction utilities for Schema v0.3.
|
|
8
|
+
|
|
9
|
+
This module provides functions to extract shape geometry in axes-local pixel
|
|
10
|
+
coordinates from matplotlib artists for interactive hit-testing in /vis/.
|
|
11
|
+
|
|
12
|
+
All coordinates are in AXES-LOCAL pixels:
|
|
13
|
+
- Origin at axes bounding box top-left
|
|
14
|
+
- X increases right, Y increases down (screen coordinates)
|
|
15
|
+
|
|
16
|
+
Geometry types per artist:
|
|
17
|
+
- line: path_simplified (polyline)
|
|
18
|
+
- scatter: points with hit_radius_px
|
|
19
|
+
- fill_between/polygon: polygon (closed shape)
|
|
20
|
+
- bar/rectangle: rectangles array
|
|
21
|
+
- image/contour: bbox only
|
|
22
|
+
- text: bbox + anchor
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from typing import Dict, List, Optional, Tuple, Any
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def extract_axes_bbox_px(ax, fig) -> Dict[str, int]:
|
|
30
|
+
"""
|
|
31
|
+
Extract axes bounding box in figure pixel coordinates.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
ax : matplotlib.axes.Axes
|
|
36
|
+
The axes object
|
|
37
|
+
fig : matplotlib.figure.Figure
|
|
38
|
+
The figure object
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
dict
|
|
43
|
+
Bounding box with keys: x0, y0, x1, y1 (figure pixels)
|
|
44
|
+
"""
|
|
45
|
+
# Get axes position in figure coordinates (0-1)
|
|
46
|
+
bbox = ax.get_position()
|
|
47
|
+
|
|
48
|
+
# Get figure size in pixels
|
|
49
|
+
fig_width_px = fig.get_figwidth() * fig.dpi
|
|
50
|
+
fig_height_px = fig.get_figheight() * fig.dpi
|
|
51
|
+
|
|
52
|
+
# Convert to figure pixels
|
|
53
|
+
x0 = int(bbox.x0 * fig_width_px)
|
|
54
|
+
y0 = int((1 - bbox.y1) * fig_height_px) # Flip Y for screen coords
|
|
55
|
+
x1 = int(bbox.x1 * fig_width_px)
|
|
56
|
+
y1 = int((1 - bbox.y0) * fig_height_px) # Flip Y for screen coords
|
|
57
|
+
|
|
58
|
+
return {"x0": x0, "y0": y0, "x1": x1, "y1": y1}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def data_to_axes_px(ax, fig, x_data, y_data) -> Tuple[np.ndarray, np.ndarray]:
|
|
62
|
+
"""
|
|
63
|
+
Convert data coordinates to axes-local pixel coordinates.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
ax : matplotlib.axes.Axes
|
|
68
|
+
The axes object
|
|
69
|
+
fig : matplotlib.figure.Figure
|
|
70
|
+
The figure object
|
|
71
|
+
x_data : array-like
|
|
72
|
+
X coordinates in data units
|
|
73
|
+
y_data : array-like
|
|
74
|
+
Y coordinates in data units
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
tuple
|
|
79
|
+
(x_px, y_px) in axes-local pixels
|
|
80
|
+
"""
|
|
81
|
+
x_data = np.asarray(x_data)
|
|
82
|
+
y_data = np.asarray(y_data)
|
|
83
|
+
|
|
84
|
+
# Get axes bbox in figure pixels
|
|
85
|
+
axes_bbox = extract_axes_bbox_px(ax, fig)
|
|
86
|
+
ax_width = axes_bbox["x1"] - axes_bbox["x0"]
|
|
87
|
+
ax_height = axes_bbox["y1"] - axes_bbox["y0"]
|
|
88
|
+
|
|
89
|
+
# Get data limits
|
|
90
|
+
xlim = ax.get_xlim()
|
|
91
|
+
ylim = ax.get_ylim()
|
|
92
|
+
|
|
93
|
+
# Convert data to normalized axes coordinates (0-1)
|
|
94
|
+
x_norm = (x_data - xlim[0]) / (xlim[1] - xlim[0])
|
|
95
|
+
y_norm = (y_data - ylim[0]) / (ylim[1] - ylim[0])
|
|
96
|
+
|
|
97
|
+
# Convert to axes-local pixels (Y flipped for screen coords)
|
|
98
|
+
x_px = x_norm * ax_width
|
|
99
|
+
y_px = (1 - y_norm) * ax_height # Flip Y
|
|
100
|
+
|
|
101
|
+
return x_px.astype(float), y_px.astype(float)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def extract_line_geometry(line, ax, fig, simplify_threshold: float = 0.5) -> Dict[str, Any]:
|
|
105
|
+
"""
|
|
106
|
+
Extract line geometry in axes-local pixels.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
line : matplotlib.lines.Line2D
|
|
111
|
+
The line object
|
|
112
|
+
ax : matplotlib.axes.Axes
|
|
113
|
+
The axes object
|
|
114
|
+
fig : matplotlib.figure.Figure
|
|
115
|
+
The figure object
|
|
116
|
+
simplify_threshold : float
|
|
117
|
+
Maximum error in pixels for path simplification (Douglas-Peucker)
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
dict
|
|
122
|
+
Geometry with: coord_space, bbox, path_simplified, (optional) path
|
|
123
|
+
"""
|
|
124
|
+
x_data = line.get_xdata()
|
|
125
|
+
y_data = line.get_ydata()
|
|
126
|
+
|
|
127
|
+
if len(x_data) == 0:
|
|
128
|
+
return {"coord_space": "axes", "bbox": None, "path_simplified": []}
|
|
129
|
+
|
|
130
|
+
x_px, y_px = data_to_axes_px(ax, fig, x_data, y_data)
|
|
131
|
+
|
|
132
|
+
# Build path as list of [x, y] points
|
|
133
|
+
path = [[round(x, 1), round(y, 1)] for x, y in zip(x_px, y_px)]
|
|
134
|
+
|
|
135
|
+
# Simplify path using Douglas-Peucker
|
|
136
|
+
path_simplified = _simplify_path(path, simplify_threshold)
|
|
137
|
+
|
|
138
|
+
# Compute bounding box
|
|
139
|
+
bbox = _compute_bbox(x_px, y_px)
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
"coord_space": "axes",
|
|
143
|
+
"bbox": bbox,
|
|
144
|
+
"path_simplified": path_simplified,
|
|
145
|
+
"path": None # Full path omitted for performance
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def extract_scatter_geometry(collection, ax, fig) -> Dict[str, Any]:
|
|
150
|
+
"""
|
|
151
|
+
Extract scatter point geometry in axes-local pixels.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
collection : matplotlib.collections.PathCollection
|
|
156
|
+
The scatter collection
|
|
157
|
+
ax : matplotlib.axes.Axes
|
|
158
|
+
The axes object
|
|
159
|
+
fig : matplotlib.figure.Figure
|
|
160
|
+
The figure object
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
dict
|
|
165
|
+
Geometry with: coord_space, bbox, hit_radius_px, points
|
|
166
|
+
"""
|
|
167
|
+
offsets = collection.get_offsets()
|
|
168
|
+
|
|
169
|
+
if len(offsets) == 0:
|
|
170
|
+
return {"coord_space": "axes", "bbox": None, "hit_radius_px": 6.0, "points": []}
|
|
171
|
+
|
|
172
|
+
x_data = offsets[:, 0]
|
|
173
|
+
y_data = offsets[:, 1]
|
|
174
|
+
|
|
175
|
+
x_px, y_px = data_to_axes_px(ax, fig, x_data, y_data)
|
|
176
|
+
|
|
177
|
+
# Get marker sizes (s is area in points^2)
|
|
178
|
+
sizes = collection.get_sizes()
|
|
179
|
+
if len(sizes) == 0:
|
|
180
|
+
sizes = [36] # Default matplotlib marker size
|
|
181
|
+
|
|
182
|
+
# Compute hit radius from average marker size
|
|
183
|
+
# s = area in points^2, radius = sqrt(s/pi) in points
|
|
184
|
+
# Convert points to pixels (roughly 1:1 at 72 dpi, scale with actual dpi)
|
|
185
|
+
avg_size = np.mean(sizes)
|
|
186
|
+
radius_pt = np.sqrt(avg_size / np.pi)
|
|
187
|
+
hit_radius_px = max(radius_pt * fig.dpi / 72, 5.0) # Min 5px for usability
|
|
188
|
+
|
|
189
|
+
# Build points list
|
|
190
|
+
points = [{"x": round(x, 1), "y": round(y, 1)} for x, y in zip(x_px, y_px)]
|
|
191
|
+
|
|
192
|
+
# Compute bounding box
|
|
193
|
+
bbox = _compute_bbox(x_px, y_px)
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
"coord_space": "axes",
|
|
197
|
+
"bbox": bbox,
|
|
198
|
+
"hit_radius_px": round(hit_radius_px, 1),
|
|
199
|
+
"points": points
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def extract_polygon_geometry(collection, ax, fig) -> Dict[str, Any]:
|
|
204
|
+
"""
|
|
205
|
+
Extract polygon geometry (fill_between, violin, etc.) in axes-local pixels.
|
|
206
|
+
|
|
207
|
+
Parameters
|
|
208
|
+
----------
|
|
209
|
+
collection : matplotlib.collections.PolyCollection
|
|
210
|
+
The polygon collection
|
|
211
|
+
ax : matplotlib.axes.Axes
|
|
212
|
+
The axes object
|
|
213
|
+
fig : matplotlib.figure.Figure
|
|
214
|
+
The figure object
|
|
215
|
+
|
|
216
|
+
Returns
|
|
217
|
+
-------
|
|
218
|
+
dict
|
|
219
|
+
Geometry with: coord_space, bbox, polygon
|
|
220
|
+
"""
|
|
221
|
+
paths = collection.get_paths()
|
|
222
|
+
|
|
223
|
+
if len(paths) == 0:
|
|
224
|
+
return {"coord_space": "axes", "bbox": None, "polygon": []}
|
|
225
|
+
|
|
226
|
+
# Get vertices from first path
|
|
227
|
+
vertices = paths[0].vertices
|
|
228
|
+
|
|
229
|
+
if len(vertices) == 0:
|
|
230
|
+
return {"coord_space": "axes", "bbox": None, "polygon": []}
|
|
231
|
+
|
|
232
|
+
x_data = vertices[:, 0]
|
|
233
|
+
y_data = vertices[:, 1]
|
|
234
|
+
|
|
235
|
+
x_px, y_px = data_to_axes_px(ax, fig, x_data, y_data)
|
|
236
|
+
|
|
237
|
+
# Build polygon as list of [x, y] points
|
|
238
|
+
polygon = [[round(x, 1), round(y, 1)] for x, y in zip(x_px, y_px)]
|
|
239
|
+
|
|
240
|
+
# Simplify if too many points
|
|
241
|
+
if len(polygon) > 100:
|
|
242
|
+
polygon = _simplify_path(polygon, 1.0)
|
|
243
|
+
|
|
244
|
+
# Compute bounding box
|
|
245
|
+
bbox = _compute_bbox(x_px, y_px)
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
"coord_space": "axes",
|
|
249
|
+
"bbox": bbox,
|
|
250
|
+
"polygon": polygon
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def extract_rectangle_geometry(patch, ax, fig) -> Dict[str, Any]:
|
|
255
|
+
"""
|
|
256
|
+
Extract rectangle geometry (bar, etc.) in axes-local pixels.
|
|
257
|
+
|
|
258
|
+
Parameters
|
|
259
|
+
----------
|
|
260
|
+
patch : matplotlib.patches.Rectangle
|
|
261
|
+
The rectangle patch
|
|
262
|
+
ax : matplotlib.axes.Axes
|
|
263
|
+
The axes object
|
|
264
|
+
fig : matplotlib.figure.Figure
|
|
265
|
+
The figure object
|
|
266
|
+
|
|
267
|
+
Returns
|
|
268
|
+
-------
|
|
269
|
+
dict
|
|
270
|
+
Geometry with: coord_space, bbox (also serves as the rectangle)
|
|
271
|
+
"""
|
|
272
|
+
# Get rectangle corners in data coordinates
|
|
273
|
+
x0_data = patch.get_x()
|
|
274
|
+
y0_data = patch.get_y()
|
|
275
|
+
width_data = patch.get_width()
|
|
276
|
+
height_data = patch.get_height()
|
|
277
|
+
|
|
278
|
+
# Convert corners to axes-local pixels
|
|
279
|
+
x_corners = [x0_data, x0_data + width_data]
|
|
280
|
+
y_corners = [y0_data, y0_data + height_data]
|
|
281
|
+
|
|
282
|
+
x_px, _ = data_to_axes_px(ax, fig, x_corners, [y0_data, y0_data])
|
|
283
|
+
_, y_px = data_to_axes_px(ax, fig, [x0_data, x0_data], y_corners)
|
|
284
|
+
|
|
285
|
+
# Build rectangle (note: Y may be flipped)
|
|
286
|
+
x = round(min(x_px), 1)
|
|
287
|
+
y = round(min(y_px), 1)
|
|
288
|
+
width = round(abs(x_px[1] - x_px[0]), 1)
|
|
289
|
+
height = round(abs(y_px[1] - y_px[0]), 1)
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
"coord_space": "axes",
|
|
293
|
+
"bbox": {"x0": int(x), "y0": int(y), "x1": int(x + width), "y1": int(y + height)},
|
|
294
|
+
"rectangle": {"x": x, "y": y, "width": width, "height": height}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def extract_bar_group_geometry(patches, ax, fig) -> Dict[str, Any]:
|
|
299
|
+
"""
|
|
300
|
+
Extract geometry for a group of bar rectangles.
|
|
301
|
+
|
|
302
|
+
Parameters
|
|
303
|
+
----------
|
|
304
|
+
patches : list
|
|
305
|
+
List of Rectangle patches
|
|
306
|
+
ax : matplotlib.axes.Axes
|
|
307
|
+
The axes object
|
|
308
|
+
fig : matplotlib.figure.Figure
|
|
309
|
+
The figure object
|
|
310
|
+
|
|
311
|
+
Returns
|
|
312
|
+
-------
|
|
313
|
+
dict
|
|
314
|
+
Geometry with: coord_space, bbox, rectangles
|
|
315
|
+
"""
|
|
316
|
+
rectangles = []
|
|
317
|
+
all_x = []
|
|
318
|
+
all_y = []
|
|
319
|
+
|
|
320
|
+
for patch in patches:
|
|
321
|
+
geom = extract_rectangle_geometry(patch, ax, fig)
|
|
322
|
+
rect = geom.get("rectangle", {})
|
|
323
|
+
if rect:
|
|
324
|
+
rectangles.append(rect)
|
|
325
|
+
all_x.extend([rect["x"], rect["x"] + rect["width"]])
|
|
326
|
+
all_y.extend([rect["y"], rect["y"] + rect["height"]])
|
|
327
|
+
|
|
328
|
+
if not rectangles:
|
|
329
|
+
return {"coord_space": "axes", "bbox": None, "rectangles": []}
|
|
330
|
+
|
|
331
|
+
# Compute overall bounding box
|
|
332
|
+
bbox = {
|
|
333
|
+
"x0": int(min(all_x)),
|
|
334
|
+
"y0": int(min(all_y)),
|
|
335
|
+
"x1": int(max(all_x)),
|
|
336
|
+
"y1": int(max(all_y))
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
"coord_space": "axes",
|
|
341
|
+
"bbox": bbox,
|
|
342
|
+
"rectangles": rectangles
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def extract_text_geometry(text, ax, fig) -> Dict[str, Any]:
|
|
347
|
+
"""
|
|
348
|
+
Extract text geometry in axes-local pixels.
|
|
349
|
+
|
|
350
|
+
Parameters
|
|
351
|
+
----------
|
|
352
|
+
text : matplotlib.text.Text
|
|
353
|
+
The text object
|
|
354
|
+
ax : matplotlib.axes.Axes
|
|
355
|
+
The axes object
|
|
356
|
+
fig : matplotlib.figure.Figure
|
|
357
|
+
The figure object
|
|
358
|
+
|
|
359
|
+
Returns
|
|
360
|
+
-------
|
|
361
|
+
dict
|
|
362
|
+
Geometry with: coord_space, bbox, anchor
|
|
363
|
+
"""
|
|
364
|
+
# Get text position in data coordinates
|
|
365
|
+
x_data, y_data = text.get_position()
|
|
366
|
+
|
|
367
|
+
# Convert to axes-local pixels
|
|
368
|
+
x_px, y_px = data_to_axes_px(ax, fig, [x_data], [y_data])
|
|
369
|
+
|
|
370
|
+
anchor = {"x": float(round(x_px[0], 1)), "y": float(round(y_px[0], 1))}
|
|
371
|
+
|
|
372
|
+
# Try to get text bounding box
|
|
373
|
+
try:
|
|
374
|
+
renderer = fig.canvas.get_renderer()
|
|
375
|
+
bbox_display = text.get_window_extent(renderer=renderer)
|
|
376
|
+
|
|
377
|
+
# Get axes bbox for conversion
|
|
378
|
+
axes_bbox = extract_axes_bbox_px(ax, fig)
|
|
379
|
+
|
|
380
|
+
# Convert display coords to axes-local pixels
|
|
381
|
+
x0 = bbox_display.x0 - axes_bbox["x0"]
|
|
382
|
+
y0 = (fig.get_figheight() * fig.dpi - bbox_display.y1) - axes_bbox["y0"] # Flip Y
|
|
383
|
+
x1 = bbox_display.x1 - axes_bbox["x0"]
|
|
384
|
+
y1 = (fig.get_figheight() * fig.dpi - bbox_display.y0) - axes_bbox["y0"]
|
|
385
|
+
|
|
386
|
+
bbox = {
|
|
387
|
+
"x0": int(round(x0)),
|
|
388
|
+
"y0": int(round(y0)),
|
|
389
|
+
"x1": int(round(x1)),
|
|
390
|
+
"y1": int(round(y1))
|
|
391
|
+
}
|
|
392
|
+
except Exception:
|
|
393
|
+
# Fallback: estimate bbox from anchor
|
|
394
|
+
bbox = {
|
|
395
|
+
"x0": int(anchor["x"] - 20),
|
|
396
|
+
"y0": int(anchor["y"] - 10),
|
|
397
|
+
"x1": int(anchor["x"] + 80),
|
|
398
|
+
"y1": int(anchor["y"] + 10)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
"coord_space": "axes",
|
|
403
|
+
"bbox": bbox,
|
|
404
|
+
"anchor": anchor
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def extract_image_geometry(image, ax, fig) -> Dict[str, Any]:
|
|
409
|
+
"""
|
|
410
|
+
Extract image geometry (imshow, etc.) in axes-local pixels.
|
|
411
|
+
|
|
412
|
+
Parameters
|
|
413
|
+
----------
|
|
414
|
+
image : matplotlib.image.AxesImage
|
|
415
|
+
The image object
|
|
416
|
+
ax : matplotlib.axes.Axes
|
|
417
|
+
The axes object
|
|
418
|
+
fig : matplotlib.figure.Figure
|
|
419
|
+
The figure object
|
|
420
|
+
|
|
421
|
+
Returns
|
|
422
|
+
-------
|
|
423
|
+
dict
|
|
424
|
+
Geometry with: coord_space, bbox
|
|
425
|
+
"""
|
|
426
|
+
extent = image.get_extent()
|
|
427
|
+
|
|
428
|
+
if extent is None:
|
|
429
|
+
# Full axes
|
|
430
|
+
return {
|
|
431
|
+
"coord_space": "axes",
|
|
432
|
+
"bbox": {"x0": 0, "y0": 0, "x1": 100, "y1": 100} # Will be computed
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
x0_data, x1_data, y0_data, y1_data = extent
|
|
436
|
+
|
|
437
|
+
x_px, _ = data_to_axes_px(ax, fig, [x0_data, x1_data], [y0_data, y0_data])
|
|
438
|
+
_, y_px = data_to_axes_px(ax, fig, [x0_data, x0_data], [y0_data, y1_data])
|
|
439
|
+
|
|
440
|
+
bbox = {
|
|
441
|
+
"x0": int(round(min(x_px))),
|
|
442
|
+
"y0": int(round(min(y_px))),
|
|
443
|
+
"x1": int(round(max(x_px))),
|
|
444
|
+
"y1": int(round(max(y_px)))
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
"coord_space": "axes",
|
|
449
|
+
"bbox": bbox
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
# =============================================================================
|
|
454
|
+
# Helper functions
|
|
455
|
+
# =============================================================================
|
|
456
|
+
|
|
457
|
+
def _compute_bbox(x_px: np.ndarray, y_px: np.ndarray) -> Dict[str, int]:
|
|
458
|
+
"""Compute bounding box from pixel coordinates."""
|
|
459
|
+
return {
|
|
460
|
+
"x0": int(round(np.nanmin(x_px))),
|
|
461
|
+
"y0": int(round(np.nanmin(y_px))),
|
|
462
|
+
"x1": int(round(np.nanmax(x_px))),
|
|
463
|
+
"y1": int(round(np.nanmax(y_px)))
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _simplify_path(path: List[List[float]], tolerance: float) -> List[List[float]]:
|
|
468
|
+
"""
|
|
469
|
+
Simplify a path using Douglas-Peucker algorithm.
|
|
470
|
+
|
|
471
|
+
Parameters
|
|
472
|
+
----------
|
|
473
|
+
path : list
|
|
474
|
+
List of [x, y] points
|
|
475
|
+
tolerance : float
|
|
476
|
+
Maximum perpendicular distance in pixels
|
|
477
|
+
|
|
478
|
+
Returns
|
|
479
|
+
-------
|
|
480
|
+
list
|
|
481
|
+
Simplified path
|
|
482
|
+
"""
|
|
483
|
+
if len(path) <= 2:
|
|
484
|
+
return path
|
|
485
|
+
|
|
486
|
+
# Convert to numpy for easier computation
|
|
487
|
+
points = np.array(path)
|
|
488
|
+
|
|
489
|
+
# Find point with maximum distance from line between endpoints
|
|
490
|
+
start = points[0]
|
|
491
|
+
end = points[-1]
|
|
492
|
+
|
|
493
|
+
# Line vector
|
|
494
|
+
line_vec = end - start
|
|
495
|
+
line_len = np.linalg.norm(line_vec)
|
|
496
|
+
|
|
497
|
+
# Handle closed polygons (start ≈ end)
|
|
498
|
+
if line_len < 1e-10:
|
|
499
|
+
# Find point farthest from centroid to split the polygon
|
|
500
|
+
centroid = np.mean(points, axis=0)
|
|
501
|
+
distances_from_centroid = np.linalg.norm(points - centroid, axis=1)
|
|
502
|
+
split_idx = np.argmax(distances_from_centroid)
|
|
503
|
+
|
|
504
|
+
if split_idx == 0 or split_idx == len(points) - 1:
|
|
505
|
+
# Can't split well, return simplified version
|
|
506
|
+
return _simplify_open_path(path, tolerance)
|
|
507
|
+
|
|
508
|
+
# Split and simplify each half
|
|
509
|
+
first_half = [p.tolist() for p in points[:split_idx + 1]]
|
|
510
|
+
second_half = [p.tolist() for p in points[split_idx:]]
|
|
511
|
+
|
|
512
|
+
simplified_first = _simplify_open_path(first_half, tolerance)
|
|
513
|
+
simplified_second = _simplify_open_path(second_half, tolerance)
|
|
514
|
+
|
|
515
|
+
# Join halves (remove duplicate at split point)
|
|
516
|
+
return simplified_first[:-1] + simplified_second
|
|
517
|
+
|
|
518
|
+
return _simplify_open_path(path, tolerance)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def _simplify_open_path(path: List[List[float]], tolerance: float) -> List[List[float]]:
|
|
522
|
+
"""
|
|
523
|
+
Simplify an open path using Douglas-Peucker algorithm.
|
|
524
|
+
|
|
525
|
+
Parameters
|
|
526
|
+
----------
|
|
527
|
+
path : list
|
|
528
|
+
List of [x, y] points (open path, start != end)
|
|
529
|
+
tolerance : float
|
|
530
|
+
Maximum perpendicular distance in pixels
|
|
531
|
+
|
|
532
|
+
Returns
|
|
533
|
+
-------
|
|
534
|
+
list
|
|
535
|
+
Simplified path
|
|
536
|
+
"""
|
|
537
|
+
if len(path) <= 2:
|
|
538
|
+
return path
|
|
539
|
+
|
|
540
|
+
points = np.array(path)
|
|
541
|
+
start = points[0]
|
|
542
|
+
end = points[-1]
|
|
543
|
+
|
|
544
|
+
line_vec = end - start
|
|
545
|
+
line_len = np.linalg.norm(line_vec)
|
|
546
|
+
|
|
547
|
+
if line_len < 1e-10:
|
|
548
|
+
return [path[0], path[-1]]
|
|
549
|
+
|
|
550
|
+
line_unit = line_vec / line_len
|
|
551
|
+
|
|
552
|
+
# Perpendicular distances
|
|
553
|
+
point_vecs = points - start
|
|
554
|
+
projections = np.dot(point_vecs, line_unit)
|
|
555
|
+
closest_points = start + np.outer(projections, line_unit)
|
|
556
|
+
distances = np.linalg.norm(points - closest_points, axis=1)
|
|
557
|
+
|
|
558
|
+
max_idx = np.argmax(distances)
|
|
559
|
+
max_dist = distances[max_idx]
|
|
560
|
+
|
|
561
|
+
if max_dist > tolerance:
|
|
562
|
+
# Recurse
|
|
563
|
+
left = _simplify_open_path([p.tolist() for p in points[:max_idx + 1]], tolerance)
|
|
564
|
+
right = _simplify_open_path([p.tolist() for p in points[max_idx:]], tolerance)
|
|
565
|
+
return left[:-1] + right
|
|
566
|
+
else:
|
|
567
|
+
return [path[0], path[-1]]
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
# EOF
|