scitex 2.8.1__py3-none-any.whl → 2.10.2__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.2.dist-info}/METADATA +368 -183
- {scitex-2.8.1.dist-info → scitex-2.10.2.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.2.dist-info}/WHEEL +0 -0
- {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/entry_points.txt +0 -0
- {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-19
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/io/_save_modules/_pltz_stx.py
|
|
4
|
+
|
|
5
|
+
"""Save matplotlib figures as FTS bundles (ZIP or directory) with plot content type."""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import tempfile
|
|
9
|
+
import warnings
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from ._figure_utils import get_figure_with_data
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _create_stx_spec(bundle_type, title, size):
|
|
19
|
+
"""Create a spec dictionary for .stx bundle.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
bundle_type : str
|
|
24
|
+
Type of bundle ('plot', 'figure', 'stats')
|
|
25
|
+
title : str
|
|
26
|
+
Title/name of the bundle
|
|
27
|
+
size : dict
|
|
28
|
+
Size info with width_mm, height_mm, dpi
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
dict
|
|
33
|
+
Spec dictionary
|
|
34
|
+
"""
|
|
35
|
+
return {
|
|
36
|
+
"schema": {"name": f"scitex.{bundle_type}", "version": "1.0.0"},
|
|
37
|
+
"id": f"{bundle_type}_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}",
|
|
38
|
+
"type": bundle_type,
|
|
39
|
+
"title": title,
|
|
40
|
+
"size": size,
|
|
41
|
+
"created_at": datetime.utcnow().isoformat() + "Z",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _extract_data_from_figure(fig):
|
|
46
|
+
"""Extract plotted data from matplotlib figure lines.
|
|
47
|
+
|
|
48
|
+
Returns DataFrame with x/y columns for each trace, or None if no data.
|
|
49
|
+
"""
|
|
50
|
+
import pandas as pd
|
|
51
|
+
|
|
52
|
+
extracted_data = {}
|
|
53
|
+
axes_list = list(fig.axes) if hasattr(fig.axes, "__iter__") else [fig.axes]
|
|
54
|
+
|
|
55
|
+
for ax_idx, ax in enumerate(axes_list):
|
|
56
|
+
for line_idx, line in enumerate(ax.get_lines()):
|
|
57
|
+
label = line.get_label()
|
|
58
|
+
if label is None or label.startswith("_"):
|
|
59
|
+
label = f"series_{line_idx}"
|
|
60
|
+
|
|
61
|
+
xdata, ydata = line.get_data()
|
|
62
|
+
if len(xdata) > 0:
|
|
63
|
+
x_col = f"ax{ax_idx}_line{line_idx}_x"
|
|
64
|
+
y_col = f"ax{ax_idx}_line{line_idx}_y"
|
|
65
|
+
extracted_data[x_col] = np.array(xdata, dtype=float)
|
|
66
|
+
extracted_data[y_col] = np.array(ydata, dtype=float)
|
|
67
|
+
|
|
68
|
+
if not extracted_data:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
# Pad arrays to same length
|
|
72
|
+
max_len = max(len(v) for v in extracted_data.values())
|
|
73
|
+
padded = {}
|
|
74
|
+
for k, v in extracted_data.items():
|
|
75
|
+
if len(v) < max_len:
|
|
76
|
+
padded[k] = np.pad(v, (0, max_len - len(v)), constant_values=np.nan)
|
|
77
|
+
else:
|
|
78
|
+
padded[k] = v
|
|
79
|
+
|
|
80
|
+
return pd.DataFrame(padded)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _build_encoding_from_figure(fig, csv_df):
|
|
84
|
+
"""Build encoding.json data from matplotlib figure.
|
|
85
|
+
|
|
86
|
+
Encoding captures data→visual mappings for scientific reproducibility.
|
|
87
|
+
"""
|
|
88
|
+
from scitex.schema import ENCODING_VERSION
|
|
89
|
+
|
|
90
|
+
traces = []
|
|
91
|
+
axes_list = list(fig.axes) if hasattr(fig.axes, "__iter__") else [fig.axes]
|
|
92
|
+
|
|
93
|
+
for ax_idx, ax in enumerate(axes_list):
|
|
94
|
+
for line_idx, line in enumerate(ax.get_lines()):
|
|
95
|
+
label = line.get_label()
|
|
96
|
+
if label is None or label.startswith("_"):
|
|
97
|
+
label = f"line_{line_idx}"
|
|
98
|
+
|
|
99
|
+
trace = {
|
|
100
|
+
"trace_id": f"ax{ax_idx}_line{line_idx}",
|
|
101
|
+
"bindings": [],
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# X binding
|
|
105
|
+
if csv_df is not None:
|
|
106
|
+
x_col = f"ax{ax_idx}_line{line_idx}_x"
|
|
107
|
+
y_col = f"ax{ax_idx}_line{line_idx}_y"
|
|
108
|
+
if x_col in csv_df.columns:
|
|
109
|
+
trace["bindings"].append(
|
|
110
|
+
{
|
|
111
|
+
"channel": "x",
|
|
112
|
+
"column": x_col,
|
|
113
|
+
"scale": "linear",
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
if y_col in csv_df.columns:
|
|
117
|
+
trace["bindings"].append(
|
|
118
|
+
{
|
|
119
|
+
"channel": "y",
|
|
120
|
+
"column": y_col,
|
|
121
|
+
"scale": "linear",
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if trace["bindings"]:
|
|
126
|
+
traces.append(trace)
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
"schema": {"name": "scitex.plt.encoding", "version": ENCODING_VERSION},
|
|
130
|
+
"traces": traces,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _build_theme_from_figure(fig):
|
|
135
|
+
"""Build theme.json data from matplotlib figure.
|
|
136
|
+
|
|
137
|
+
Theme captures pure aesthetics without affecting scientific meaning.
|
|
138
|
+
"""
|
|
139
|
+
from scitex.schema import THEME_VERSION
|
|
140
|
+
|
|
141
|
+
# Extract colors from figure
|
|
142
|
+
fig_facecolor = fig.get_facecolor()
|
|
143
|
+
axes_list = list(fig.axes) if hasattr(fig.axes, "__iter__") else [fig.axes]
|
|
144
|
+
|
|
145
|
+
ax_facecolor = "white"
|
|
146
|
+
if axes_list:
|
|
147
|
+
try:
|
|
148
|
+
ax_facecolor = axes_list[0].get_facecolor()
|
|
149
|
+
if isinstance(ax_facecolor, (tuple, list)):
|
|
150
|
+
ax_facecolor = f"#{int(ax_facecolor[0] * 255):02x}{int(ax_facecolor[1] * 255):02x}{int(ax_facecolor[2] * 255):02x}"
|
|
151
|
+
except Exception:
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
# Extract trace styles
|
|
155
|
+
traces = []
|
|
156
|
+
for ax_idx, ax in enumerate(axes_list):
|
|
157
|
+
for line_idx, line in enumerate(ax.get_lines()):
|
|
158
|
+
trace_style = {
|
|
159
|
+
"trace_id": f"ax{ax_idx}_line{line_idx}",
|
|
160
|
+
}
|
|
161
|
+
try:
|
|
162
|
+
color = line.get_color()
|
|
163
|
+
if color:
|
|
164
|
+
trace_style["color"] = color
|
|
165
|
+
lw = line.get_linewidth()
|
|
166
|
+
if lw:
|
|
167
|
+
trace_style["linewidth"] = float(lw)
|
|
168
|
+
ls = line.get_linestyle()
|
|
169
|
+
if ls:
|
|
170
|
+
trace_style["linestyle"] = ls
|
|
171
|
+
marker = line.get_marker()
|
|
172
|
+
if marker and marker != "None":
|
|
173
|
+
trace_style["marker"] = marker
|
|
174
|
+
ms = line.get_markersize()
|
|
175
|
+
if ms:
|
|
176
|
+
trace_style["markersize"] = float(ms)
|
|
177
|
+
alpha = line.get_alpha()
|
|
178
|
+
if alpha is not None:
|
|
179
|
+
trace_style["alpha"] = float(alpha)
|
|
180
|
+
except Exception:
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
if len(trace_style) > 1: # More than just trace_id
|
|
184
|
+
traces.append(trace_style)
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
"schema": {"name": "scitex.plt.theme", "version": THEME_VERSION},
|
|
188
|
+
"colors": {
|
|
189
|
+
"mode": "light",
|
|
190
|
+
"background": "transparent",
|
|
191
|
+
"axes_bg": ax_facecolor if isinstance(ax_facecolor, str) else "white",
|
|
192
|
+
},
|
|
193
|
+
"typography": {
|
|
194
|
+
"family": "sans-serif",
|
|
195
|
+
"size_pt": 7.0,
|
|
196
|
+
},
|
|
197
|
+
"traces": traces,
|
|
198
|
+
"grid": False,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def save_pltz_as_stx(obj, spath, as_zip=True, basename=None, **kwargs):
|
|
203
|
+
"""Save a matplotlib figure as an FTS bundle (ZIP or directory).
|
|
204
|
+
|
|
205
|
+
Bundle structure:
|
|
206
|
+
plot_name/ # or plot_name.zip (with plot_name/ inside)
|
|
207
|
+
spec.json # Bundle specification (WHAT to plot)
|
|
208
|
+
encoding.json # Data→visual mapping (scientific rigor)
|
|
209
|
+
theme.json # Pure aesthetics (colors, fonts)
|
|
210
|
+
style.json # Backward compat (encoding + theme merged)
|
|
211
|
+
data/
|
|
212
|
+
data.csv # Plotted data (tidy format)
|
|
213
|
+
data_info.json # Column meanings, units, dtypes
|
|
214
|
+
stats/
|
|
215
|
+
stats.json # Statistical test results (if any)
|
|
216
|
+
stats.csv # Tabular statistics
|
|
217
|
+
cache/
|
|
218
|
+
geometry_px.json # Hit areas for GUI editing
|
|
219
|
+
render_manifest.json # Render metadata
|
|
220
|
+
hitmap.png # Hit testing image
|
|
221
|
+
hitmap.svg # Vector hit testing
|
|
222
|
+
exports/
|
|
223
|
+
plot.svg # Vector export
|
|
224
|
+
plot.png # Raster export
|
|
225
|
+
plot.pdf # Publication export
|
|
226
|
+
"""
|
|
227
|
+
import matplotlib.figure
|
|
228
|
+
|
|
229
|
+
p = Path(spath)
|
|
230
|
+
|
|
231
|
+
if basename is None:
|
|
232
|
+
basename = p.stem
|
|
233
|
+
|
|
234
|
+
# Extract figure
|
|
235
|
+
fig = obj
|
|
236
|
+
if hasattr(obj, "figure"):
|
|
237
|
+
fig = obj.figure
|
|
238
|
+
elif hasattr(obj, "fig"):
|
|
239
|
+
fig = obj.fig
|
|
240
|
+
|
|
241
|
+
if not isinstance(fig, matplotlib.figure.Figure):
|
|
242
|
+
raise TypeError(f"Expected matplotlib Figure, got {type(obj).__name__}")
|
|
243
|
+
|
|
244
|
+
dpi = kwargs.pop("dpi", 300)
|
|
245
|
+
data = kwargs.pop("data", None)
|
|
246
|
+
|
|
247
|
+
# Get CSV data from figure if not provided
|
|
248
|
+
csv_df = data
|
|
249
|
+
if csv_df is None:
|
|
250
|
+
# Try SciTeX wrapped objects first
|
|
251
|
+
csv_source = get_figure_with_data(obj)
|
|
252
|
+
if csv_source is not None and hasattr(csv_source, "export_as_csv"):
|
|
253
|
+
try:
|
|
254
|
+
csv_df = csv_source.export_as_csv()
|
|
255
|
+
except Exception:
|
|
256
|
+
pass
|
|
257
|
+
# Fall back to extracting from matplotlib lines
|
|
258
|
+
if csv_df is None:
|
|
259
|
+
csv_df = _extract_data_from_figure(fig)
|
|
260
|
+
|
|
261
|
+
# Create spec for .stx format
|
|
262
|
+
fig_width_inch, fig_height_inch = fig.get_size_inches()
|
|
263
|
+
|
|
264
|
+
spec = _create_stx_spec(
|
|
265
|
+
bundle_type="plot",
|
|
266
|
+
title=basename,
|
|
267
|
+
size={
|
|
268
|
+
"width_mm": round(fig_width_inch * 25.4, 2),
|
|
269
|
+
"height_mm": round(fig_height_inch * 25.4, 2),
|
|
270
|
+
"dpi": dpi,
|
|
271
|
+
},
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Determine paths
|
|
275
|
+
if as_zip:
|
|
276
|
+
zip_path = p
|
|
277
|
+
temp_dir = Path(tempfile.mkdtemp())
|
|
278
|
+
bundle_dir = temp_dir / basename
|
|
279
|
+
else:
|
|
280
|
+
bundle_dir = p
|
|
281
|
+
temp_dir = None
|
|
282
|
+
|
|
283
|
+
bundle_dir.mkdir(parents=True, exist_ok=True)
|
|
284
|
+
|
|
285
|
+
# Save spec.json
|
|
286
|
+
with open(bundle_dir / "spec.json", "w") as f:
|
|
287
|
+
json.dump(spec, f, indent=2, default=str)
|
|
288
|
+
|
|
289
|
+
# Save style.json (empty default for backward compatibility)
|
|
290
|
+
style_data = {}
|
|
291
|
+
with open(bundle_dir / "style.json", "w") as f:
|
|
292
|
+
json.dump(style_data, f, indent=2)
|
|
293
|
+
|
|
294
|
+
# Save encoding.json (data→visual mapping for scientific rigor)
|
|
295
|
+
encoding_data = _build_encoding_from_figure(fig, csv_df)
|
|
296
|
+
with open(bundle_dir / "encoding.json", "w") as f:
|
|
297
|
+
json.dump(encoding_data, f, indent=2)
|
|
298
|
+
|
|
299
|
+
# Save theme.json (pure aesthetics)
|
|
300
|
+
theme_data = _build_theme_from_figure(fig)
|
|
301
|
+
with open(bundle_dir / "theme.json", "w") as f:
|
|
302
|
+
json.dump(theme_data, f, indent=2)
|
|
303
|
+
|
|
304
|
+
# Create directory structure
|
|
305
|
+
data_dir = bundle_dir / "data"
|
|
306
|
+
stats_dir = bundle_dir / "stats"
|
|
307
|
+
cache_dir = bundle_dir / "cache"
|
|
308
|
+
exports_dir = bundle_dir / "exports"
|
|
309
|
+
|
|
310
|
+
data_dir.mkdir(exist_ok=True)
|
|
311
|
+
stats_dir.mkdir(exist_ok=True)
|
|
312
|
+
cache_dir.mkdir(exist_ok=True)
|
|
313
|
+
exports_dir.mkdir(exist_ok=True)
|
|
314
|
+
|
|
315
|
+
with warnings.catch_warnings():
|
|
316
|
+
warnings.filterwarnings("ignore", message=".*tight_layout.*")
|
|
317
|
+
|
|
318
|
+
# Save exports with simple names (bundle dir provides context)
|
|
319
|
+
fig.savefig(
|
|
320
|
+
exports_dir / "plot.png",
|
|
321
|
+
dpi=dpi,
|
|
322
|
+
bbox_inches="tight",
|
|
323
|
+
format="png",
|
|
324
|
+
transparent=True,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
fig.savefig(
|
|
328
|
+
exports_dir / "plot.svg",
|
|
329
|
+
bbox_inches="tight",
|
|
330
|
+
format="svg",
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
fig.savefig(
|
|
334
|
+
exports_dir / "plot.pdf",
|
|
335
|
+
bbox_inches="tight",
|
|
336
|
+
format="pdf",
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
# Save data/data.csv and data/meta.json if available
|
|
340
|
+
if csv_df is not None:
|
|
341
|
+
csv_df.to_csv(data_dir / "data.csv", index=False)
|
|
342
|
+
|
|
343
|
+
# Generate metadata for the data
|
|
344
|
+
meta = {
|
|
345
|
+
"columns": list(csv_df.columns),
|
|
346
|
+
"dtypes": {col: str(dtype) for col, dtype in csv_df.dtypes.items()},
|
|
347
|
+
"shape": list(csv_df.shape),
|
|
348
|
+
"description": "Plotted data (tidy format)",
|
|
349
|
+
}
|
|
350
|
+
with open(data_dir / "data_info.json", "w") as f:
|
|
351
|
+
json.dump(meta, f, indent=2)
|
|
352
|
+
|
|
353
|
+
# Save cache/geometry_px.json and hitmap images for GUI hit areas
|
|
354
|
+
try:
|
|
355
|
+
from scitex.plt.utils._hitmap import (
|
|
356
|
+
HITMAP_AXES_COLOR,
|
|
357
|
+
HITMAP_BACKGROUND_COLOR,
|
|
358
|
+
apply_hitmap_colors,
|
|
359
|
+
extract_path_data,
|
|
360
|
+
extract_selectable_regions,
|
|
361
|
+
restore_original_colors,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
geometry = {
|
|
365
|
+
"path_data": extract_path_data(fig),
|
|
366
|
+
"selectable_regions": extract_selectable_regions(fig),
|
|
367
|
+
}
|
|
368
|
+
with open(cache_dir / "geometry_px.json", "w") as f:
|
|
369
|
+
json.dump(geometry, f, indent=2)
|
|
370
|
+
|
|
371
|
+
# Generate hitmap images
|
|
372
|
+
axes_list = list(fig.axes) if hasattr(fig.axes, "__iter__") else [fig.axes]
|
|
373
|
+
original_props, color_map, groups = apply_hitmap_colors(fig)
|
|
374
|
+
|
|
375
|
+
# Store and set hitmap colors
|
|
376
|
+
saved_fig_facecolor = fig.patch.get_facecolor()
|
|
377
|
+
saved_ax_facecolors = []
|
|
378
|
+
for ax in axes_list:
|
|
379
|
+
saved_ax_facecolors.append(ax.get_facecolor())
|
|
380
|
+
ax.set_facecolor(HITMAP_BACKGROUND_COLOR)
|
|
381
|
+
for spine in ax.spines.values():
|
|
382
|
+
spine.set_color(HITMAP_AXES_COLOR)
|
|
383
|
+
fig.patch.set_facecolor(HITMAP_BACKGROUND_COLOR)
|
|
384
|
+
|
|
385
|
+
# Save hitmap PNG
|
|
386
|
+
fig.savefig(
|
|
387
|
+
cache_dir / "hitmap.png",
|
|
388
|
+
dpi=dpi,
|
|
389
|
+
format="png",
|
|
390
|
+
facecolor=HITMAP_BACKGROUND_COLOR,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# Save hitmap SVG
|
|
394
|
+
fig.savefig(
|
|
395
|
+
cache_dir / "hitmap.svg",
|
|
396
|
+
format="svg",
|
|
397
|
+
facecolor=HITMAP_BACKGROUND_COLOR,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# Restore colors
|
|
401
|
+
restore_original_colors(original_props)
|
|
402
|
+
fig.patch.set_facecolor(saved_fig_facecolor)
|
|
403
|
+
for i, ax in enumerate(axes_list):
|
|
404
|
+
ax.set_facecolor(saved_ax_facecolors[i])
|
|
405
|
+
|
|
406
|
+
except Exception:
|
|
407
|
+
pass # Skip if hitmap extraction fails
|
|
408
|
+
|
|
409
|
+
# Save cache/render_manifest.json
|
|
410
|
+
render_manifest = {
|
|
411
|
+
"dpi": dpi,
|
|
412
|
+
"format": ["png", "svg", "pdf"],
|
|
413
|
+
"bbox_inches": "tight",
|
|
414
|
+
"size_mm": {
|
|
415
|
+
"width": round(fig_width_inch * 25.4, 2),
|
|
416
|
+
"height": round(fig_height_inch * 25.4, 2),
|
|
417
|
+
},
|
|
418
|
+
"hitmap_png": "cache/hitmap.png",
|
|
419
|
+
"hitmap_svg": "cache/hitmap.svg",
|
|
420
|
+
}
|
|
421
|
+
with open(cache_dir / "render_manifest.json", "w") as f:
|
|
422
|
+
json.dump(render_manifest, f, indent=2)
|
|
423
|
+
|
|
424
|
+
# Save stats/stats.json and stats.csv placeholder (empty by default)
|
|
425
|
+
stats_data = {"comparisons": [], "tests": []}
|
|
426
|
+
with open(stats_dir / "stats.json", "w") as f:
|
|
427
|
+
json.dump(stats_data, f, indent=2)
|
|
428
|
+
|
|
429
|
+
# Create stats.csv with header for tabular export
|
|
430
|
+
import pandas as pd
|
|
431
|
+
|
|
432
|
+
stats_df = pd.DataFrame(
|
|
433
|
+
columns=[
|
|
434
|
+
"test_type",
|
|
435
|
+
"group1",
|
|
436
|
+
"group2",
|
|
437
|
+
"statistic",
|
|
438
|
+
"p_value",
|
|
439
|
+
"effect_size",
|
|
440
|
+
"significant",
|
|
441
|
+
]
|
|
442
|
+
)
|
|
443
|
+
stats_df.to_csv(stats_dir / "stats.csv", index=False)
|
|
444
|
+
|
|
445
|
+
# Generate README.md for self-documentation
|
|
446
|
+
_generate_readme(bundle_dir, basename, spec, csv_df)
|
|
447
|
+
|
|
448
|
+
# Pack to ZIP if requested
|
|
449
|
+
if as_zip:
|
|
450
|
+
import shutil
|
|
451
|
+
import zipfile
|
|
452
|
+
|
|
453
|
+
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
454
|
+
for file_path in bundle_dir.rglob("*"):
|
|
455
|
+
if file_path.is_file():
|
|
456
|
+
arcname = file_path.relative_to(bundle_dir.parent)
|
|
457
|
+
zf.write(file_path, arcname)
|
|
458
|
+
shutil.rmtree(temp_dir)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _generate_readme(bundle_dir, basename, spec, csv_df):
|
|
462
|
+
"""Generate README.md for self-documentation."""
|
|
463
|
+
from datetime import datetime
|
|
464
|
+
|
|
465
|
+
readme_lines = [
|
|
466
|
+
f"# {basename} FTS Bundle",
|
|
467
|
+
"",
|
|
468
|
+
"## Overview",
|
|
469
|
+
"",
|
|
470
|
+
f"- **Type**: {spec.get('type', 'plot')}",
|
|
471
|
+
f"- **Created**: {datetime.now().isoformat()}",
|
|
472
|
+
f"- **Bundle ID**: {spec.get('bundle_id', 'N/A')}",
|
|
473
|
+
"",
|
|
474
|
+
"## Structure",
|
|
475
|
+
"",
|
|
476
|
+
"```",
|
|
477
|
+
f"{basename}/",
|
|
478
|
+
"├── spec.json # What to plot (data mapping)",
|
|
479
|
+
"├── encoding.json # Data→visual mapping (scientific rigor)",
|
|
480
|
+
"├── theme.json # Pure aesthetics (colors, fonts)",
|
|
481
|
+
"├── style.json # Backward compat (= encoding + theme)",
|
|
482
|
+
"├── data/",
|
|
483
|
+
"│ ├── data.csv # Raw data",
|
|
484
|
+
"│ └── data_info.json # Column metadata",
|
|
485
|
+
"├── stats/",
|
|
486
|
+
"│ ├── stats.json # Statistical results",
|
|
487
|
+
"│ └── stats.csv # Tabular statistics",
|
|
488
|
+
"├── cache/",
|
|
489
|
+
"│ ├── geometry_px.json # Hit areas (regenerable)",
|
|
490
|
+
"│ ├── render_manifest.json",
|
|
491
|
+
"│ ├── hitmap.png # Hit testing image",
|
|
492
|
+
"│ └── hitmap.svg # Vector hit testing",
|
|
493
|
+
"└── exports/",
|
|
494
|
+
" ├── plot.png # Raster export",
|
|
495
|
+
" ├── plot.svg # Vector export",
|
|
496
|
+
" └── plot.pdf # Publication export",
|
|
497
|
+
"```",
|
|
498
|
+
"",
|
|
499
|
+
]
|
|
500
|
+
|
|
501
|
+
if csv_df is not None:
|
|
502
|
+
readme_lines.extend(
|
|
503
|
+
[
|
|
504
|
+
"## Data Columns",
|
|
505
|
+
"",
|
|
506
|
+
"| Column | Type | Description |",
|
|
507
|
+
"|--------|------|-------------|",
|
|
508
|
+
]
|
|
509
|
+
)
|
|
510
|
+
for col in csv_df.columns:
|
|
511
|
+
dtype = str(csv_df[col].dtype)
|
|
512
|
+
readme_lines.append(f"| {col} | {dtype} | - |")
|
|
513
|
+
readme_lines.append("")
|
|
514
|
+
|
|
515
|
+
readme_lines.extend(
|
|
516
|
+
[
|
|
517
|
+
"## Usage",
|
|
518
|
+
"",
|
|
519
|
+
"```python",
|
|
520
|
+
"from scitex.fts import FTS",
|
|
521
|
+
"",
|
|
522
|
+
f'bundle = FTS("{basename}.zip") # or "{basename}/" directory',
|
|
523
|
+
"bundle.show() # Display",
|
|
524
|
+
'bundle.export("output.png") # Export',
|
|
525
|
+
"```",
|
|
526
|
+
"",
|
|
527
|
+
"---",
|
|
528
|
+
"*Generated by SciTeX*",
|
|
529
|
+
]
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
with open(bundle_dir / "README.md", "w") as f:
|
|
533
|
+
f.write("\n".join(readme_lines))
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
# EOF
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-19
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/io/_save_modules/_stx_bundle.py
|
|
4
|
+
|
|
5
|
+
"""Save functions for FTS bundle format (.zip or directory)."""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def save_stx_bundle(obj, spath, as_zip=True, bundle_type=None, basename=None, **kwargs):
|
|
11
|
+
"""Save an object as an FTS bundle (.zip or directory).
|
|
12
|
+
|
|
13
|
+
FTS (Figure-Table-Statistics) is the unified bundle format that supports:
|
|
14
|
+
- figure: Publication figures with multiple panels
|
|
15
|
+
- plot: Single matplotlib plots
|
|
16
|
+
- stats: Statistical results
|
|
17
|
+
|
|
18
|
+
The content type is auto-detected from the object:
|
|
19
|
+
- FTS instance -> delegates to FTS.save()
|
|
20
|
+
- matplotlib.figure.Figure -> plot
|
|
21
|
+
- dict with 'panels' or 'elements' -> figure
|
|
22
|
+
- dict with 'comparisons' -> stats
|
|
23
|
+
|
|
24
|
+
Bundle structure:
|
|
25
|
+
output/ # or output.zip
|
|
26
|
+
node.json # Bundle metadata
|
|
27
|
+
encoding.json # Data-to-visual mappings
|
|
28
|
+
theme.json # Visual styling
|
|
29
|
+
data/ # Raw data files
|
|
30
|
+
exports/ # PNG, SVG, PDF exports
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
obj : Any
|
|
35
|
+
Object to save (FTS, Figure, dict, etc.)
|
|
36
|
+
spath : str or Path
|
|
37
|
+
Output path (e.g., "output.zip" or "output/")
|
|
38
|
+
as_zip : bool
|
|
39
|
+
If True (default), save as ZIP archive. Use False for directory.
|
|
40
|
+
bundle_type : str, optional
|
|
41
|
+
Force bundle type: 'figure', 'plot', or 'stats'. Auto-detected if None.
|
|
42
|
+
**kwargs
|
|
43
|
+
Additional arguments passed to format-specific savers.
|
|
44
|
+
"""
|
|
45
|
+
from scitex.fts import FTS
|
|
46
|
+
|
|
47
|
+
if isinstance(obj, FTS):
|
|
48
|
+
# Delegate to FTS.save()
|
|
49
|
+
obj.save()
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
p = Path(spath)
|
|
53
|
+
|
|
54
|
+
# Extract basename from path if not provided
|
|
55
|
+
if basename is None:
|
|
56
|
+
basename = p.stem
|
|
57
|
+
|
|
58
|
+
# Auto-detect content type from object
|
|
59
|
+
content_type = bundle_type
|
|
60
|
+
if content_type is None:
|
|
61
|
+
import matplotlib.figure
|
|
62
|
+
|
|
63
|
+
if isinstance(obj, matplotlib.figure.Figure):
|
|
64
|
+
content_type = "plot"
|
|
65
|
+
elif hasattr(obj, "figure"):
|
|
66
|
+
content_type = "plot"
|
|
67
|
+
obj = obj.figure
|
|
68
|
+
elif isinstance(obj, dict):
|
|
69
|
+
if "panels" in obj or "elements" in obj:
|
|
70
|
+
content_type = "figure"
|
|
71
|
+
elif "comparisons" in obj:
|
|
72
|
+
content_type = "stats"
|
|
73
|
+
else:
|
|
74
|
+
content_type = "figure" # Default for dicts
|
|
75
|
+
else:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"Cannot auto-detect bundle type for {type(obj).__name__}. "
|
|
78
|
+
"Please specify bundle_type='figure', 'plot', or 'stats'."
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Route to appropriate handler based on content type
|
|
82
|
+
if content_type == "plot":
|
|
83
|
+
from ._pltz_stx import save_pltz_as_stx
|
|
84
|
+
|
|
85
|
+
save_pltz_as_stx(obj, spath, as_zip=as_zip, basename=basename, **kwargs)
|
|
86
|
+
elif content_type == "figure":
|
|
87
|
+
from scitex.fts import FTS
|
|
88
|
+
|
|
89
|
+
bundle = FTS(spath, create=True, node_type="figure")
|
|
90
|
+
if isinstance(obj, dict):
|
|
91
|
+
if "title" in obj:
|
|
92
|
+
bundle.node.title = obj["title"]
|
|
93
|
+
if "description" in obj:
|
|
94
|
+
bundle.node.description = obj["description"]
|
|
95
|
+
bundle.save()
|
|
96
|
+
elif content_type == "stats":
|
|
97
|
+
import scitex.stats as sstats
|
|
98
|
+
|
|
99
|
+
sstats.save_statsz(obj, spath, as_zip=as_zip, **kwargs)
|
|
100
|
+
else:
|
|
101
|
+
raise ValueError(f"Unknown bundle type: {content_type}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# EOF
|