scitex 2.8.1__py3-none-any.whl → 2.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scitex/__init__.py +15 -7
- scitex/__version__.py +1 -2
- scitex/_install_guide.py +250 -0
- scitex/_optional_deps.py +206 -39
- scitex/ai/_gen_ai/_Groq.py +2 -4
- scitex/ai/_gen_ai/_OpenAI.py +5 -2
- scitex/ai/_gen_ai/_Perplexity.py +20 -6
- scitex/audio/__init__.py +24 -15
- scitex/audio/_cross_process_lock.py +139 -0
- scitex/audio/_mcp_handlers.py +256 -0
- scitex/audio/_mcp_tool_schemas.py +203 -0
- scitex/audio/engines/elevenlabs_engine.py +5 -2
- scitex/audio/mcp_server.py +98 -457
- scitex/bridge/__init__.py +30 -19
- scitex/bridge/_figrecipe.py +245 -0
- scitex/bridge/_helpers.py +2 -1
- scitex/bridge/_plt_vis.py +23 -10
- scitex/bridge/_stats_plt.py +18 -5
- scitex/bridge/_stats_vis.py +16 -2
- scitex/browser/__init__.py +84 -44
- scitex/browser/automation/__init__.py +5 -1
- scitex/browser/core/BrowserMixin.py +17 -4
- scitex/browser/core/__init__.py +11 -2
- scitex/browser/remote/CaptchaHandler.py +1 -1
- scitex/browser/remote/ZenRowsAPIClient.py +1 -1
- scitex/capture/grid.py +487 -0
- scitex/capture/mcp_handlers.py +401 -0
- scitex/capture/mcp_tool_defs.py +192 -0
- scitex/capture/mcp_tools.py +241 -0
- scitex/capture/mcp_utils.py +30 -0
- scitex/cli/convert.py +421 -0
- scitex/cli/main.py +6 -4
- scitex/datetime/__init__.py +46 -0
- scitex/datetime/_linspace.py +100 -0
- scitex/datetime/_normalize_timestamp.py +306 -0
- scitex/db/_delete_duplicates.py +4 -4
- scitex/db/_sqlite3/_delete_duplicates.py +11 -2
- scitex/dev/plt/__init__.py +61 -62
- scitex/dev/plt/demo_plotters/__init__.py +0 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
- scitex/dev/plt/mpl/get_dir_ax.py +46 -0
- scitex/dev/plt/mpl/get_signatures.py +176 -0
- scitex/dev/plt/mpl/get_signatures_details.py +522 -0
- scitex/dict/_pop_keys.py +1 -7
- scitex/dsp/__init__.py +15 -10
- scitex/dsp/add_noise.py +5 -2
- scitex/dsp/example.py +35 -22
- scitex/dsp/filt.py +8 -3
- scitex/dsp/reference.py +3 -2
- scitex/dsp/utils/__init__.py +2 -1
- scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
- scitex/dt/__init__.py +39 -2
- scitex/errors.py +82 -521
- scitex/fig/__init__.py +4 -4
- scitex/fig/editor/edit/panel_loader.py +1 -1
- scitex/fig/io/_bundle.py +7 -7
- scitex/fts/README.md +262 -0
- scitex/fts/TODO.md +66 -0
- scitex/fts/__init__.py +90 -0
- scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
- scitex/fts/_bundle/_FTS.py +657 -0
- scitex/fts/_bundle/__init__.py +38 -0
- scitex/fts/_bundle/_children.py +216 -0
- scitex/fts/_bundle/_conversion/__init__.py +15 -0
- scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
- scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
- scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
- scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
- scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
- scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
- scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
- scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
- scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
- scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
- scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
- scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
- scitex/fts/_bundle/_extractors/__init__.py +32 -0
- scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
- scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
- scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
- scitex/fts/_bundle/_loader.py +134 -0
- scitex/fts/_bundle/_mpl_helpers.py +389 -0
- scitex/fts/_bundle/_saver.py +269 -0
- scitex/fts/_bundle/_storage.py +200 -0
- scitex/fts/_bundle/_utils/__init__.py +55 -0
- scitex/fts/_bundle/_utils/_const.py +26 -0
- scitex/fts/_bundle/_utils/_errors.py +73 -0
- scitex/fts/_bundle/_utils/_generate.py +21 -0
- scitex/fts/_bundle/_utils/_types.py +76 -0
- scitex/fts/_bundle/_validation.py +434 -0
- scitex/fts/_bundle/_zipbundle.py +165 -0
- scitex/fts/_fig/__init__.py +22 -0
- scitex/fts/_fig/_backend/__init__.py +53 -0
- scitex/fts/_fig/_backend/_export.py +165 -0
- scitex/fts/_fig/_backend/_parser.py +188 -0
- scitex/fts/_fig/_backend/_render.py +538 -0
- scitex/fts/_fig/_composite.py +345 -0
- scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
- scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_fig/_dataclasses/__init__.py +47 -0
- scitex/fts/_fig/_editor/__init__.py +14 -0
- scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
- scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
- scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
- scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
- scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
- scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
- scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
- scitex/fts/_fig/_editor/_defaults.py +300 -0
- scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
- scitex/fts/_fig/_models/_Annotations.py +115 -0
- scitex/fts/_fig/_models/_Axes.py +152 -0
- scitex/fts/_fig/_models/_Figure.py +138 -0
- scitex/fts/_fig/_models/_Guides.py +104 -0
- scitex/fts/_fig/_models/_Plot.py +123 -0
- scitex/fts/_fig/_models/_Styles.py +245 -0
- scitex/fts/_fig/_models/__init__.py +80 -0
- scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
- scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
- scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
- scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_fig/_utils/__init__.py +129 -0
- scitex/fts/_fig/_utils/_auto_layout.py +127 -0
- scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
- scitex/fts/_fig/_utils/_const_sizes.py +48 -0
- scitex/fts/_fig/_utils/_convert_coords.py +77 -0
- scitex/fts/_fig/_utils/_get_template.py +178 -0
- scitex/fts/_fig/_utils/_normalize.py +73 -0
- scitex/fts/_fig/_utils/_plot_layout.py +397 -0
- scitex/fts/_fig/_utils/_validate.py +197 -0
- scitex/fts/_kinds/__init__.py +45 -0
- scitex/fts/_kinds/_figure/__init__.py +19 -0
- scitex/fts/_kinds/_figure/_composite.py +345 -0
- scitex/fts/_kinds/_plot/__init__.py +25 -0
- scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
- scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
- scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
- scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
- scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
- scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
- scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
- scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
- scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
- scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
- scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
- scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
- scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
- scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
- scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
- scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
- scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
- scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
- scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
- scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
- scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
- scitex/fts/_kinds/_shape/__init__.py +141 -0
- scitex/fts/_kinds/_stats/__init__.py +56 -0
- scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_kinds/_table/__init__.py +72 -0
- scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
- scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
- scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
- scitex/fts/_kinds/_table/_latex/_export.py +279 -0
- scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
- scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
- scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
- scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
- scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
- scitex/fts/_kinds/_text/__init__.py +77 -0
- scitex/fts/_schemas/data_info.schema.json +75 -0
- scitex/fts/_schemas/encoding.schema.json +90 -0
- scitex/fts/_schemas/node.schema.json +145 -0
- scitex/fts/_schemas/render_manifest.schema.json +62 -0
- scitex/fts/_schemas/stats.schema.json +132 -0
- scitex/fts/_schemas/theme.schema.json +141 -0
- scitex/fts/_stats/__init__.py +48 -0
- scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_tables/__init__.py +65 -0
- scitex/fts/_tables/_latex/__init__.py +93 -0
- scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
- scitex/fts/_tables/_latex/_editor/_app.py +725 -0
- scitex/fts/_tables/_latex/_export.py +279 -0
- scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
- scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
- scitex/fts/_tables/_latex/_table_exporter.py +362 -0
- scitex/fts/_tables/_latex/_utils.py +369 -0
- scitex/fts/_tables/_latex/_validator.py +445 -0
- scitex/gen/__init__.py +66 -25
- scitex/gen/misc.py +28 -0
- scitex/io/__init__.py +47 -32
- scitex/io/_load.py +87 -36
- scitex/io/_load_modules/__init__.py +10 -7
- scitex/io/_load_modules/_pandas.py +6 -1
- scitex/io/_save.py +299 -1556
- scitex/io/_save_modules/__init__.py +76 -19
- scitex/io/_save_modules/_figure_utils.py +90 -0
- scitex/io/_save_modules/_image_csv.py +497 -0
- scitex/io/_save_modules/_legends.py +91 -0
- scitex/io/_save_modules/_pltz_bundle.py +356 -0
- scitex/io/_save_modules/_pltz_stx.py +536 -0
- scitex/io/_save_modules/_stx_bundle.py +104 -0
- scitex/io/_save_modules/_symlink.py +96 -0
- scitex/io/_save_modules/_yaml.py +1 -1
- scitex/io/_save_modules/_zarr.py +64 -18
- scitex/io/bundle/README.md +212 -0
- scitex/io/bundle/__init__.py +110 -0
- scitex/io/{_bundle.py → bundle/_core.py} +168 -97
- scitex/io/bundle/_nested.py +713 -0
- scitex/io/bundle/_types.py +74 -0
- scitex/io/{_zip_bundle.py → bundle/_zip.py} +93 -45
- scitex/io/utils/h5_to_zarr.py +1 -1
- scitex/logging/__init__.py +108 -13
- scitex/logging/_errors.py +508 -0
- scitex/logging/_formatters.py +30 -6
- scitex/logging/_warnings.py +261 -0
- scitex/plt/__init__.py +4 -1
- scitex/plt/_figrecipe.py +236 -0
- scitex/plt/_subplots/_AxisWrapper.py +6 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
- scitex/plt/_subplots/_FigWrapper.py +15 -0
- scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
- scitex/plt/_subplots/_export_as_csv.py +11 -0
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
- scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
- scitex/plt/_subplots/_fonts.py +71 -0
- scitex/plt/_subplots/_mm_layout.py +282 -0
- scitex/plt/gallery/__init__.py +99 -2
- scitex/plt/styles/_plot_postprocess.py +3 -1
- scitex/plt/utils/_configure_mpl.py +16 -19
- scitex/repro/_RandomStateManager.py +13 -8
- scitex/resource/__init__.py +19 -1
- scitex/resource/_utils/_get_env_info.py +13 -25
- scitex/schema/__init__.py +149 -160
- scitex/schema/_encoding.py +273 -0
- scitex/schema/_figure_elements.py +406 -0
- scitex/schema/_theme.py +360 -0
- scitex/schema/_validation.py +0 -98
- scitex/scholar/__init__.py +56 -14
- scitex/scholar/auth/ScholarAuthManager.py +1 -1
- scitex/scholar/auth/__init__.py +11 -2
- scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
- scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
- scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
- scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
- scitex/scholar/config/ScholarConfig.py +1 -1
- scitex/scholar/core/Scholar.py +1 -1
- scitex/session/_decorator.py +18 -16
- scitex/session/_lifecycle.py +9 -11
- scitex/session/template.py +9 -8
- scitex/sh/test_sh.py +72 -0
- scitex/sh/test_sh_simple.py +61 -0
- scitex/stats/__init__.py +221 -97
- scitex/stats/_schema.py +21 -22
- scitex/stats/descriptive/_circular.py +212 -351
- scitex/stats/descriptive/_describe.py +81 -132
- scitex/stats/descriptive/_nan.py +205 -433
- scitex/stats/descriptive/_real.py +127 -141
- scitex/str/_format_plot_text.py +5 -5
- scitex/str/_latex.py +26 -84
- scitex/str/_latex_fallback.py +53 -47
- scitex/web/_search_pubmed.py +5 -4
- scitex/writer/tests/test_diff_between.py +451 -0
- scitex/writer/tests/test_document_section.py +311 -0
- scitex/writer/tests/test_document_workflow.py +393 -0
- scitex/writer/tests/test_writer.py +361 -0
- scitex/writer/tests/test_writer_integration.py +303 -0
- {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/METADATA +364 -181
- {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/RECORD +412 -97
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
- {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/WHEEL +0 -0
- {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/entry_points.txt +0 -0
- {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_extractors/_extract_line.py
|
|
4
|
+
|
|
5
|
+
"""Line plot data and encoding extraction."""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Dict, List, Tuple
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from matplotlib.axes import Axes
|
|
13
|
+
|
|
14
|
+
from ..._fig._dataclasses import TraceEncoding
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def extract_line_data(ax: "Axes", ax_idx: int) -> Dict[str, np.ndarray]:
|
|
18
|
+
"""Extract line plot data from axes.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
ax: Matplotlib axes
|
|
22
|
+
ax_idx: Axes index for column naming
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Dict mapping column names to data arrays
|
|
26
|
+
"""
|
|
27
|
+
data = {}
|
|
28
|
+
for line_idx, line in enumerate(ax.get_lines()):
|
|
29
|
+
label = line.get_label()
|
|
30
|
+
if label is None or label.startswith("_"):
|
|
31
|
+
label = f"series_{line_idx}"
|
|
32
|
+
|
|
33
|
+
xdata, ydata = line.get_data()
|
|
34
|
+
if len(xdata) > 0:
|
|
35
|
+
x_col = f"ax{ax_idx}_line{line_idx}_x"
|
|
36
|
+
y_col = f"ax{ax_idx}_line{line_idx}_y"
|
|
37
|
+
data[x_col] = np.array(xdata, dtype=float)
|
|
38
|
+
data[y_col] = np.array(ydata, dtype=float)
|
|
39
|
+
|
|
40
|
+
return data
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def build_line_traces(ax: "Axes", ax_idx: int) -> List["TraceEncoding"]:
|
|
44
|
+
"""Build encoding traces for line plots.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
ax: Matplotlib axes
|
|
48
|
+
ax_idx: Axes index for trace ID
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
List of TraceEncoding objects
|
|
52
|
+
"""
|
|
53
|
+
from ..._fig._dataclasses import ChannelEncoding, TraceEncoding
|
|
54
|
+
|
|
55
|
+
traces = []
|
|
56
|
+
for line_idx, line in enumerate(ax.get_lines()):
|
|
57
|
+
label = line.get_label()
|
|
58
|
+
if label and not label.startswith("_"):
|
|
59
|
+
trace = TraceEncoding(
|
|
60
|
+
trace_id=f"line_{ax_idx}_{line_idx}",
|
|
61
|
+
x=ChannelEncoding(column=f"ax{ax_idx}_line{line_idx}_x"),
|
|
62
|
+
y=ChannelEncoding(column=f"ax{ax_idx}_line{line_idx}_y"),
|
|
63
|
+
)
|
|
64
|
+
traces.append(trace)
|
|
65
|
+
|
|
66
|
+
return traces
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__all__ = ["extract_line_data", "build_line_traces"]
|
|
70
|
+
|
|
71
|
+
# EOF
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_extractors/_extract_scatter.py
|
|
4
|
+
|
|
5
|
+
"""Scatter plot data and encoding extraction."""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Dict, List, Tuple
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from matplotlib.axes import Axes
|
|
13
|
+
|
|
14
|
+
from ..._fig._dataclasses import TraceEncoding
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def extract_scatter_data(ax: "Axes", ax_idx: int) -> Dict[str, np.ndarray]:
|
|
18
|
+
"""Extract scatter plot data from axes (PathCollection).
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
ax: Matplotlib axes
|
|
22
|
+
ax_idx: Axes index for column naming
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Dict mapping column names to data arrays
|
|
26
|
+
"""
|
|
27
|
+
from matplotlib.collections import PathCollection
|
|
28
|
+
|
|
29
|
+
data = {}
|
|
30
|
+
scatter_idx = 0
|
|
31
|
+
|
|
32
|
+
for child in ax.get_children():
|
|
33
|
+
if isinstance(child, PathCollection):
|
|
34
|
+
offsets = child.get_offsets()
|
|
35
|
+
if len(offsets) > 0:
|
|
36
|
+
x_col = f"ax{ax_idx}_scatter{scatter_idx}_x"
|
|
37
|
+
y_col = f"ax{ax_idx}_scatter{scatter_idx}_y"
|
|
38
|
+
data[x_col] = np.array(offsets[:, 0], dtype=float)
|
|
39
|
+
data[y_col] = np.array(offsets[:, 1], dtype=float)
|
|
40
|
+
scatter_idx += 1
|
|
41
|
+
|
|
42
|
+
return data
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def build_scatter_traces(ax: "Axes", ax_idx: int) -> List["TraceEncoding"]:
|
|
46
|
+
"""Build encoding traces for scatter plots.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
ax: Matplotlib axes
|
|
50
|
+
ax_idx: Axes index for trace ID
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
List of TraceEncoding objects
|
|
54
|
+
"""
|
|
55
|
+
from matplotlib.collections import PathCollection
|
|
56
|
+
|
|
57
|
+
from ..._fig._dataclasses import ChannelEncoding, TraceEncoding
|
|
58
|
+
|
|
59
|
+
traces = []
|
|
60
|
+
scatter_idx = 0
|
|
61
|
+
|
|
62
|
+
for child in ax.get_children():
|
|
63
|
+
if isinstance(child, PathCollection):
|
|
64
|
+
offsets = child.get_offsets()
|
|
65
|
+
if len(offsets) > 0:
|
|
66
|
+
trace = TraceEncoding(
|
|
67
|
+
trace_id=f"scatter_{ax_idx}_{scatter_idx}",
|
|
68
|
+
x=ChannelEncoding(column=f"ax{ax_idx}_scatter{scatter_idx}_x"),
|
|
69
|
+
y=ChannelEncoding(column=f"ax{ax_idx}_scatter{scatter_idx}_y"),
|
|
70
|
+
)
|
|
71
|
+
traces.append(trace)
|
|
72
|
+
scatter_idx += 1
|
|
73
|
+
|
|
74
|
+
return traces
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
__all__ = ["extract_scatter_data", "build_scatter_traces"]
|
|
78
|
+
|
|
79
|
+
# EOF
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_loader.py
|
|
4
|
+
|
|
5
|
+
"""FTS Bundle loading utilities.
|
|
6
|
+
|
|
7
|
+
Loads bundles using the new canonical/artifacts/payload/children structure.
|
|
8
|
+
Supports backwards compatibility with old flat structure (node.json at root).
|
|
9
|
+
|
|
10
|
+
New structure:
|
|
11
|
+
canonical/spec.json (was node.json)
|
|
12
|
+
canonical/encoding.json (was encoding.json)
|
|
13
|
+
canonical/theme.json (was theme.json)
|
|
14
|
+
canonical/data_info.json (was data/data_info.json)
|
|
15
|
+
payload/stats.json (was stats/stats.json)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import TYPE_CHECKING, Optional, Tuple
|
|
20
|
+
|
|
21
|
+
from ._storage import get_storage
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from ._dataclasses import DataInfo, Node
|
|
25
|
+
from .._fig import Encoding, Theme
|
|
26
|
+
from .._stats import Stats
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def load_bundle_components(
|
|
30
|
+
path: Path,
|
|
31
|
+
) -> Tuple[
|
|
32
|
+
Optional["Node"],
|
|
33
|
+
Optional["Encoding"],
|
|
34
|
+
Optional["Theme"],
|
|
35
|
+
Optional["Stats"],
|
|
36
|
+
Optional["DataInfo"],
|
|
37
|
+
]:
|
|
38
|
+
"""Load all bundle components from storage.
|
|
39
|
+
|
|
40
|
+
Supports both new canonical/ structure and legacy flat structure.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
path: Bundle path (directory or ZIP)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Tuple of (node, encoding, theme, stats, data_info)
|
|
47
|
+
"""
|
|
48
|
+
from ._dataclasses import DataInfo, Node
|
|
49
|
+
from .._fig import Encoding, Theme
|
|
50
|
+
from .._stats import Stats
|
|
51
|
+
|
|
52
|
+
storage = get_storage(path)
|
|
53
|
+
|
|
54
|
+
node = None
|
|
55
|
+
encoding = None
|
|
56
|
+
theme = None
|
|
57
|
+
stats = None
|
|
58
|
+
data_info = None
|
|
59
|
+
|
|
60
|
+
# Detect structure: new (canonical/) or legacy (flat)
|
|
61
|
+
# - New: canonical/spec.json
|
|
62
|
+
# - Legacy FTS: node.json at root
|
|
63
|
+
# - Legacy sio.save(): spec.json at root
|
|
64
|
+
if storage.exists("canonical/spec.json"):
|
|
65
|
+
structure = "v2" # New canonical/ structure
|
|
66
|
+
elif storage.exists("spec.json"):
|
|
67
|
+
structure = "sio" # sio.save() structure
|
|
68
|
+
else:
|
|
69
|
+
structure = "v1" # Legacy node.json structure
|
|
70
|
+
is_new_structure = structure == "v2"
|
|
71
|
+
|
|
72
|
+
# Node / spec.json
|
|
73
|
+
if structure == "v2":
|
|
74
|
+
node_data = storage.read_json("canonical/spec.json")
|
|
75
|
+
elif structure == "sio":
|
|
76
|
+
node_data = storage.read_json("spec.json")
|
|
77
|
+
else:
|
|
78
|
+
node_data = storage.read_json("node.json")
|
|
79
|
+
if node_data:
|
|
80
|
+
node = Node.from_dict(node_data)
|
|
81
|
+
|
|
82
|
+
# Encoding
|
|
83
|
+
if is_new_structure:
|
|
84
|
+
encoding_data = storage.read_json("canonical/encoding.json")
|
|
85
|
+
else:
|
|
86
|
+
encoding_data = storage.read_json("encoding.json")
|
|
87
|
+
if encoding_data:
|
|
88
|
+
encoding = Encoding.from_dict(encoding_data)
|
|
89
|
+
|
|
90
|
+
# Theme
|
|
91
|
+
if is_new_structure:
|
|
92
|
+
theme_data = storage.read_json("canonical/theme.json")
|
|
93
|
+
else:
|
|
94
|
+
theme_data = storage.read_json("theme.json")
|
|
95
|
+
if theme_data:
|
|
96
|
+
theme = Theme.from_dict(theme_data)
|
|
97
|
+
|
|
98
|
+
# Stats (payload for kind=stats, or legacy stats/)
|
|
99
|
+
if is_new_structure:
|
|
100
|
+
stats_data = storage.read_json("payload/stats.json")
|
|
101
|
+
else:
|
|
102
|
+
stats_data = storage.read_json("stats/stats.json")
|
|
103
|
+
if stats_data:
|
|
104
|
+
stats = Stats.from_dict(stats_data)
|
|
105
|
+
|
|
106
|
+
# Data info
|
|
107
|
+
if is_new_structure:
|
|
108
|
+
data_info_data = storage.read_json("canonical/data_info.json")
|
|
109
|
+
else:
|
|
110
|
+
data_info_data = storage.read_json("data/data_info.json")
|
|
111
|
+
if data_info_data:
|
|
112
|
+
data_info = DataInfo.from_dict(data_info_data)
|
|
113
|
+
|
|
114
|
+
return node, encoding, theme, stats, data_info
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def get_bundle_structure_version(path: Path) -> str:
|
|
118
|
+
"""Detect bundle structure version.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
path: Bundle path
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
"v2" for new canonical/ structure, "v1" for legacy flat structure
|
|
125
|
+
"""
|
|
126
|
+
storage = get_storage(path)
|
|
127
|
+
if storage.exists("canonical/spec.json"):
|
|
128
|
+
return "v2"
|
|
129
|
+
return "v1"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
__all__ = ["load_bundle_components", "get_bundle_structure_version"]
|
|
133
|
+
|
|
134
|
+
# EOF
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_mpl_helpers.py
|
|
4
|
+
|
|
5
|
+
"""Matplotlib helper functions for FTS bundle creation."""
|
|
6
|
+
|
|
7
|
+
import warnings
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from matplotlib.figure import Figure as MplFigure
|
|
12
|
+
|
|
13
|
+
from .._fig import Encoding, Theme
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_scitex_axes(fig: "MplFigure") -> Optional[Any]:
|
|
17
|
+
"""Find scitex.plt wrapped axes with tracking data.
|
|
18
|
+
|
|
19
|
+
Uses the same helper as sio.save to find objects with export_as_csv.
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
from scitex.io._save_modules._figure_utils import get_figure_with_data
|
|
23
|
+
|
|
24
|
+
return get_figure_with_data(fig)
|
|
25
|
+
except ImportError:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
# Fallback: check figure axes directly
|
|
29
|
+
axes_list = list(fig.axes) if hasattr(fig.axes, "__iter__") else [fig.axes]
|
|
30
|
+
for ax in axes_list:
|
|
31
|
+
if hasattr(ax, "export_as_csv") and hasattr(ax, "history"):
|
|
32
|
+
return ax
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _build_encoding_from_csv_columns(csv_df: "Any") -> "Encoding":
|
|
37
|
+
"""Build encoding from actual CSV column names.
|
|
38
|
+
|
|
39
|
+
Handles two formats:
|
|
40
|
+
1. Verbose: ax-row-{row}-col-{col}_trace-id-{id}_variable-{var}
|
|
41
|
+
2. Simple: column names like 'x', 'y' (user-provided DataFrames)
|
|
42
|
+
|
|
43
|
+
This ensures encoding references match actual data columns.
|
|
44
|
+
"""
|
|
45
|
+
from .._fig._dataclasses import ChannelEncoding, Encoding, TraceEncoding
|
|
46
|
+
|
|
47
|
+
if csv_df is None or csv_df.empty:
|
|
48
|
+
return Encoding(traces=[])
|
|
49
|
+
|
|
50
|
+
from scitex.plt.utils._csv_column_naming import parse_csv_column_name
|
|
51
|
+
|
|
52
|
+
# Group columns by trace (for verbose format)
|
|
53
|
+
trace_columns = {} # {(ax_row, ax_col, trace_id): {variable: column_name}}
|
|
54
|
+
|
|
55
|
+
for col in csv_df.columns:
|
|
56
|
+
parsed = parse_csv_column_name(col)
|
|
57
|
+
if parsed["valid"]:
|
|
58
|
+
key = (parsed["ax_row"], parsed["ax_col"], parsed["trace_id"])
|
|
59
|
+
if key not in trace_columns:
|
|
60
|
+
trace_columns[key] = {}
|
|
61
|
+
trace_columns[key][parsed["variable"]] = col
|
|
62
|
+
|
|
63
|
+
# Build traces from verbose column names
|
|
64
|
+
traces = []
|
|
65
|
+
for (ax_row, ax_col, trace_id), variables in trace_columns.items():
|
|
66
|
+
trace = TraceEncoding(
|
|
67
|
+
trace_id=f"ax-row-{ax_row}-col-{ax_col}_trace-id-{trace_id}",
|
|
68
|
+
x=ChannelEncoding(column=variables.get("x")) if "x" in variables else None,
|
|
69
|
+
y=ChannelEncoding(column=variables.get("y")) if "y" in variables else None,
|
|
70
|
+
)
|
|
71
|
+
traces.append(trace)
|
|
72
|
+
|
|
73
|
+
# If no verbose columns found, try simple column names
|
|
74
|
+
if not traces:
|
|
75
|
+
columns = list(csv_df.columns)
|
|
76
|
+
# Check for common x/y patterns
|
|
77
|
+
x_col = None
|
|
78
|
+
y_col = None
|
|
79
|
+
for col in columns:
|
|
80
|
+
col_lower = col.lower()
|
|
81
|
+
if col_lower in ("x", "time", "index"):
|
|
82
|
+
x_col = col
|
|
83
|
+
elif col_lower in ("y", "value", "values"):
|
|
84
|
+
y_col = col
|
|
85
|
+
# If no pattern match, use first two numeric columns
|
|
86
|
+
if x_col is None or y_col is None:
|
|
87
|
+
numeric_cols = csv_df.select_dtypes(include=["number"]).columns.tolist()
|
|
88
|
+
if len(numeric_cols) >= 2:
|
|
89
|
+
if x_col is None:
|
|
90
|
+
x_col = numeric_cols[0]
|
|
91
|
+
if y_col is None:
|
|
92
|
+
y_col = numeric_cols[1] if numeric_cols[1] != x_col else numeric_cols[0]
|
|
93
|
+
elif len(numeric_cols) == 1:
|
|
94
|
+
if x_col is None:
|
|
95
|
+
x_col = numeric_cols[0]
|
|
96
|
+
if y_col is None:
|
|
97
|
+
y_col = numeric_cols[0]
|
|
98
|
+
|
|
99
|
+
if x_col and y_col:
|
|
100
|
+
trace = TraceEncoding(
|
|
101
|
+
trace_id="main",
|
|
102
|
+
x=ChannelEncoding(column=x_col),
|
|
103
|
+
y=ChannelEncoding(column=y_col),
|
|
104
|
+
)
|
|
105
|
+
traces.append(trace)
|
|
106
|
+
|
|
107
|
+
return Encoding(traces=traces)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def validate_encoding_csv_link(encoding: "Encoding", csv_df: "Any") -> list:
|
|
111
|
+
"""Validate that encoding column references exist in CSV data.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
encoding: Encoding object with trace definitions
|
|
115
|
+
csv_df: DataFrame with CSV data
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
List of validation errors (empty if valid)
|
|
119
|
+
"""
|
|
120
|
+
errors = []
|
|
121
|
+
|
|
122
|
+
if csv_df is None or csv_df.empty:
|
|
123
|
+
return errors
|
|
124
|
+
|
|
125
|
+
csv_columns = set(csv_df.columns)
|
|
126
|
+
|
|
127
|
+
for trace in encoding.traces:
|
|
128
|
+
if trace.x and trace.x.column:
|
|
129
|
+
if trace.x.column not in csv_columns:
|
|
130
|
+
errors.append(
|
|
131
|
+
f"Encoding references missing column: {trace.x.column}"
|
|
132
|
+
)
|
|
133
|
+
if trace.y and trace.y.column:
|
|
134
|
+
if trace.y.column not in csv_columns:
|
|
135
|
+
errors.append(
|
|
136
|
+
f"Encoding references missing column: {trace.y.column}"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return errors
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def extract_data_from_mpl_figure(fig: "MplFigure") -> Optional[Any]:
|
|
143
|
+
"""Extract plotted data from matplotlib figure.
|
|
144
|
+
|
|
145
|
+
Uses scitex.plt tracking if available (supports all 60+ plot types),
|
|
146
|
+
otherwise falls back to extracting from rendered figure.
|
|
147
|
+
"""
|
|
148
|
+
import numpy as np
|
|
149
|
+
import pandas as pd
|
|
150
|
+
|
|
151
|
+
# Try scitex.plt tracking first (supports all plot types)
|
|
152
|
+
scitex_ax = _get_scitex_axes(fig)
|
|
153
|
+
if scitex_ax is not None:
|
|
154
|
+
try:
|
|
155
|
+
csv_df = scitex_ax.export_as_csv()
|
|
156
|
+
if csv_df is not None and not csv_df.empty:
|
|
157
|
+
return csv_df
|
|
158
|
+
except Exception:
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
# Fallback: extract from rendered figure (limited plot types)
|
|
162
|
+
from ._extractors import extract_bar_data, extract_line_data, extract_scatter_data
|
|
163
|
+
|
|
164
|
+
extracted_data = {}
|
|
165
|
+
axes_list = list(fig.axes) if hasattr(fig.axes, "__iter__") else [fig.axes]
|
|
166
|
+
|
|
167
|
+
for ax_idx, ax in enumerate(axes_list):
|
|
168
|
+
extracted_data.update(extract_line_data(ax, ax_idx))
|
|
169
|
+
extracted_data.update(extract_scatter_data(ax, ax_idx))
|
|
170
|
+
extracted_data.update(extract_bar_data(ax, ax_idx))
|
|
171
|
+
|
|
172
|
+
if not extracted_data:
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
max_len = max(len(v) for v in extracted_data.values())
|
|
176
|
+
padded = {}
|
|
177
|
+
for k, v in extracted_data.items():
|
|
178
|
+
if len(v) < max_len:
|
|
179
|
+
padded[k] = np.pad(v, (0, max_len - len(v)), constant_values=np.nan)
|
|
180
|
+
else:
|
|
181
|
+
padded[k] = v
|
|
182
|
+
|
|
183
|
+
return pd.DataFrame(padded)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def build_encoding_from_mpl_figure(fig: "MplFigure") -> "Encoding":
|
|
187
|
+
"""Build encoding specification from matplotlib figure.
|
|
188
|
+
|
|
189
|
+
Uses scitex.plt tracking if available (captures actual plot method),
|
|
190
|
+
otherwise falls back to detecting from rendered figure.
|
|
191
|
+
"""
|
|
192
|
+
from .._fig._dataclasses import Encoding
|
|
193
|
+
|
|
194
|
+
# Try scitex.plt tracking first (knows exact plot method)
|
|
195
|
+
scitex_ax = _get_scitex_axes(fig)
|
|
196
|
+
if scitex_ax is not None and hasattr(scitex_ax, "history") and scitex_ax.history:
|
|
197
|
+
return _build_encoding_from_history(scitex_ax.history)
|
|
198
|
+
|
|
199
|
+
# Fallback: detect from rendered figure
|
|
200
|
+
from ._extractors import build_bar_traces, build_line_traces, build_scatter_traces
|
|
201
|
+
|
|
202
|
+
traces = []
|
|
203
|
+
axes_list = list(fig.axes) if hasattr(fig.axes, "__iter__") else [fig.axes]
|
|
204
|
+
|
|
205
|
+
for ax_idx, ax in enumerate(axes_list):
|
|
206
|
+
traces.extend(build_line_traces(ax, ax_idx))
|
|
207
|
+
traces.extend(build_scatter_traces(ax, ax_idx))
|
|
208
|
+
traces.extend(build_bar_traces(ax, ax_idx))
|
|
209
|
+
|
|
210
|
+
return Encoding(traces=traces)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def build_theme_from_mpl_figure(fig: "MplFigure") -> "Theme":
|
|
214
|
+
"""Build theme specification from matplotlib figure."""
|
|
215
|
+
from .._fig import Theme
|
|
216
|
+
|
|
217
|
+
return Theme()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def extract_geometry_from_mpl_figure(fig: "MplFigure") -> dict:
|
|
221
|
+
"""Extract geometry data from matplotlib figure for hit testing."""
|
|
222
|
+
try:
|
|
223
|
+
from scitex.plt.utils._hitmap import extract_path_data, extract_selectable_regions
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
"path_data": extract_path_data(fig),
|
|
227
|
+
"selectable_regions": extract_selectable_regions(fig),
|
|
228
|
+
}
|
|
229
|
+
except Exception:
|
|
230
|
+
return {"elements": []}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def generate_hitmap_from_mpl_figure(fig: "MplFigure", dpi: int = 300) -> tuple:
|
|
234
|
+
"""Generate hitmap images from matplotlib figure."""
|
|
235
|
+
try:
|
|
236
|
+
import io
|
|
237
|
+
|
|
238
|
+
from scitex.plt.utils._hitmap import (
|
|
239
|
+
HITMAP_AXES_COLOR,
|
|
240
|
+
HITMAP_BACKGROUND_COLOR,
|
|
241
|
+
apply_hitmap_colors,
|
|
242
|
+
restore_original_colors,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
axes_list = list(fig.axes) if hasattr(fig.axes, "__iter__") else [fig.axes]
|
|
246
|
+
original_props, color_map, groups = apply_hitmap_colors(fig)
|
|
247
|
+
|
|
248
|
+
saved_fig_facecolor = fig.patch.get_facecolor()
|
|
249
|
+
saved_ax_facecolors = []
|
|
250
|
+
for ax in axes_list:
|
|
251
|
+
saved_ax_facecolors.append(ax.get_facecolor())
|
|
252
|
+
ax.set_facecolor(HITMAP_BACKGROUND_COLOR)
|
|
253
|
+
for spine in ax.spines.values():
|
|
254
|
+
spine.set_color(HITMAP_AXES_COLOR)
|
|
255
|
+
fig.patch.set_facecolor(HITMAP_BACKGROUND_COLOR)
|
|
256
|
+
|
|
257
|
+
png_buf = io.BytesIO()
|
|
258
|
+
with warnings.catch_warnings():
|
|
259
|
+
warnings.filterwarnings("ignore", message=".*tight_layout.*")
|
|
260
|
+
fig.savefig(png_buf, format="png", dpi=dpi, facecolor=HITMAP_BACKGROUND_COLOR)
|
|
261
|
+
png_bytes = png_buf.getvalue()
|
|
262
|
+
|
|
263
|
+
svg_buf = io.BytesIO()
|
|
264
|
+
with warnings.catch_warnings():
|
|
265
|
+
warnings.filterwarnings("ignore", message=".*tight_layout.*")
|
|
266
|
+
fig.savefig(svg_buf, format="svg", facecolor=HITMAP_BACKGROUND_COLOR)
|
|
267
|
+
svg_bytes = svg_buf.getvalue()
|
|
268
|
+
|
|
269
|
+
restore_original_colors(original_props)
|
|
270
|
+
fig.patch.set_facecolor(saved_fig_facecolor)
|
|
271
|
+
for i, ax in enumerate(axes_list):
|
|
272
|
+
ax.set_facecolor(saved_ax_facecolors[i])
|
|
273
|
+
|
|
274
|
+
return png_bytes, svg_bytes
|
|
275
|
+
except Exception:
|
|
276
|
+
return None, None
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def from_matplotlib(
|
|
280
|
+
fig: "MplFigure",
|
|
281
|
+
path,
|
|
282
|
+
name: Optional[str] = None,
|
|
283
|
+
csv_df: Optional[Any] = None,
|
|
284
|
+
dpi: int = 300,
|
|
285
|
+
):
|
|
286
|
+
"""Create FTS bundle from matplotlib figure.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
fig: Matplotlib figure object
|
|
290
|
+
path: Output path for bundle (.zip or directory)
|
|
291
|
+
name: Bundle name (defaults to path stem)
|
|
292
|
+
csv_df: Pre-extracted CSV data (uses scitex.plt tracking if None)
|
|
293
|
+
dpi: Resolution for raster exports
|
|
294
|
+
|
|
295
|
+
Note:
|
|
296
|
+
Encoding is built from CSV column names (single source of truth).
|
|
297
|
+
This ensures encoding references always match actual data columns.
|
|
298
|
+
"""
|
|
299
|
+
import io
|
|
300
|
+
import json
|
|
301
|
+
from pathlib import Path
|
|
302
|
+
|
|
303
|
+
from ._FTS import FTS
|
|
304
|
+
from ._saver import save_bundle_components
|
|
305
|
+
|
|
306
|
+
path = Path(path)
|
|
307
|
+
|
|
308
|
+
fig_width_inch, fig_height_inch = fig.get_size_inches()
|
|
309
|
+
size_mm = {
|
|
310
|
+
"width": round(fig_width_inch * 25.4, 2),
|
|
311
|
+
"height": round(fig_height_inch * 25.4, 2),
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
bundle = FTS(path, create=True, kind="plot", name=name, size_mm=size_mm)
|
|
315
|
+
|
|
316
|
+
if csv_df is None:
|
|
317
|
+
csv_df = extract_data_from_mpl_figure(fig)
|
|
318
|
+
|
|
319
|
+
if csv_df is not None and not csv_df.empty:
|
|
320
|
+
bundle._node.payload_schema = "scitex.fts.payload.plot@1"
|
|
321
|
+
|
|
322
|
+
# Build encoding from actual CSV columns (single source of truth)
|
|
323
|
+
# This ensures encoding references match real data columns
|
|
324
|
+
if csv_df is not None and not csv_df.empty:
|
|
325
|
+
bundle._encoding = _build_encoding_from_csv_columns(csv_df)
|
|
326
|
+
# Validate encoding-CSV link
|
|
327
|
+
errors = validate_encoding_csv_link(bundle._encoding, csv_df)
|
|
328
|
+
if errors:
|
|
329
|
+
warnings.warn(f"Encoding validation errors: {errors}")
|
|
330
|
+
else:
|
|
331
|
+
bundle._encoding = build_encoding_from_mpl_figure(fig)
|
|
332
|
+
bundle._theme = build_theme_from_mpl_figure(fig)
|
|
333
|
+
|
|
334
|
+
storage = bundle.storage
|
|
335
|
+
|
|
336
|
+
if csv_df is not None and not csv_df.empty:
|
|
337
|
+
csv_bytes = csv_df.to_csv(index=False).encode("utf-8")
|
|
338
|
+
storage.write("payload/data.csv", csv_bytes)
|
|
339
|
+
|
|
340
|
+
data_info = {
|
|
341
|
+
"columns": list(csv_df.columns),
|
|
342
|
+
"dtypes": {col: str(dtype) for col, dtype in csv_df.dtypes.items()},
|
|
343
|
+
"shape": list(csv_df.shape),
|
|
344
|
+
}
|
|
345
|
+
storage.write("canonical/data_info.json", json.dumps(data_info, indent=2).encode())
|
|
346
|
+
|
|
347
|
+
for fmt in ["png", "svg", "pdf"]:
|
|
348
|
+
buf = io.BytesIO()
|
|
349
|
+
with warnings.catch_warnings():
|
|
350
|
+
warnings.filterwarnings("ignore", message=".*tight_layout.*")
|
|
351
|
+
fig.savefig(buf, format=fmt, dpi=dpi, bbox_inches="tight")
|
|
352
|
+
storage.write(f"artifacts/exports/figure.{fmt}", buf.getvalue())
|
|
353
|
+
|
|
354
|
+
geometry = extract_geometry_from_mpl_figure(fig)
|
|
355
|
+
geometry["space"] = "figure_px"
|
|
356
|
+
storage.write("artifacts/cache/geometry_px.json", json.dumps(geometry, indent=2).encode())
|
|
357
|
+
|
|
358
|
+
hitmap_png, hitmap_svg = generate_hitmap_from_mpl_figure(fig, dpi)
|
|
359
|
+
if hitmap_png:
|
|
360
|
+
storage.write("artifacts/cache/hitmap.png", hitmap_png)
|
|
361
|
+
if hitmap_svg:
|
|
362
|
+
storage.write("artifacts/cache/hitmap.svg", hitmap_svg)
|
|
363
|
+
|
|
364
|
+
manifest = {"dpi": dpi, "formats": ["png", "svg", "pdf"], "size_mm": size_mm}
|
|
365
|
+
storage.write("artifacts/cache/render_manifest.json", json.dumps(manifest, indent=2).encode())
|
|
366
|
+
|
|
367
|
+
save_bundle_components(
|
|
368
|
+
path,
|
|
369
|
+
node=bundle._node,
|
|
370
|
+
encoding=bundle._encoding,
|
|
371
|
+
theme=bundle._theme,
|
|
372
|
+
render=False,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
bundle._dirty = False
|
|
376
|
+
return bundle
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
__all__ = [
|
|
380
|
+
"extract_data_from_mpl_figure",
|
|
381
|
+
"build_encoding_from_mpl_figure",
|
|
382
|
+
"build_theme_from_mpl_figure",
|
|
383
|
+
"extract_geometry_from_mpl_figure",
|
|
384
|
+
"generate_hitmap_from_mpl_figure",
|
|
385
|
+
"validate_encoding_csv_link",
|
|
386
|
+
"from_matplotlib",
|
|
387
|
+
]
|
|
388
|
+
|
|
389
|
+
# EOF
|