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,261 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-12-21"
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/logging/_warnings.py
|
|
5
|
+
|
|
6
|
+
"""Warning system for SciTeX, mimicking Python's warnings module.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
import scitex.logging as logging
|
|
10
|
+
from scitex.logging import UnitWarning
|
|
11
|
+
|
|
12
|
+
# Emit a warning
|
|
13
|
+
logging.warn("Missing units on axis label", UnitWarning)
|
|
14
|
+
|
|
15
|
+
# Filter warnings (like warnings.filterwarnings)
|
|
16
|
+
logging.filterwarnings("ignore", category=UnitWarning)
|
|
17
|
+
logging.filterwarnings("error", category=UnitWarning) # Raise as exception
|
|
18
|
+
logging.filterwarnings("always", category=UnitWarning) # Always show
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import logging as _logging
|
|
22
|
+
from typing import Dict, Optional, Type, Set
|
|
23
|
+
|
|
24
|
+
# =============================================================================
|
|
25
|
+
# Warning Categories (similar to Python's warning classes)
|
|
26
|
+
# =============================================================================
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SciTeXWarning(UserWarning):
|
|
30
|
+
"""Base warning class for all SciTeX warnings."""
|
|
31
|
+
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class UnitWarning(SciTeXWarning):
|
|
36
|
+
"""Warning for axis label unit issues (educational for SI conventions).
|
|
37
|
+
|
|
38
|
+
Raised when:
|
|
39
|
+
- Axis labels are missing units
|
|
40
|
+
- Units use parentheses instead of brackets (SI prefers [])
|
|
41
|
+
- Units use division instead of negative exponents (m/s vs m·s⁻¹)
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class StyleWarning(SciTeXWarning):
|
|
48
|
+
"""Warning for style/formatting issues."""
|
|
49
|
+
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SciTeXDeprecationWarning(SciTeXWarning):
|
|
54
|
+
"""Warning for deprecated SciTeX features."""
|
|
55
|
+
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class PerformanceWarning(SciTeXWarning):
|
|
60
|
+
"""Warning for performance issues."""
|
|
61
|
+
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class DataLossWarning(SciTeXWarning):
|
|
66
|
+
"""Warning for potential data loss."""
|
|
67
|
+
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# =============================================================================
|
|
72
|
+
# Warning Filter Registry
|
|
73
|
+
# =============================================================================
|
|
74
|
+
|
|
75
|
+
# Actions: "ignore", "error", "always", "default", "once", "module"
|
|
76
|
+
_filters: Dict[Type[SciTeXWarning], str] = {}
|
|
77
|
+
_seen_warnings: Set[str] = set() # For "once" action
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def filterwarnings(
|
|
81
|
+
action: str,
|
|
82
|
+
category: Type[SciTeXWarning] = SciTeXWarning,
|
|
83
|
+
message: Optional[str] = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Control warning behavior (like warnings.filterwarnings).
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
action : str
|
|
90
|
+
One of:
|
|
91
|
+
- "ignore": Never show this warning
|
|
92
|
+
- "error": Raise as exception
|
|
93
|
+
- "always": Always show
|
|
94
|
+
- "default": Show first occurrence per location
|
|
95
|
+
- "once": Show only once total
|
|
96
|
+
- "module": Show once per module
|
|
97
|
+
category : type
|
|
98
|
+
Warning category (default: SciTeXWarning = all)
|
|
99
|
+
message : str, optional
|
|
100
|
+
Regex pattern to match warning message (not implemented yet)
|
|
101
|
+
|
|
102
|
+
Examples
|
|
103
|
+
--------
|
|
104
|
+
>>> import scitex.logging as logging
|
|
105
|
+
>>> from scitex.logging import UnitWarning
|
|
106
|
+
>>> logging.filterwarnings("ignore", category=UnitWarning)
|
|
107
|
+
"""
|
|
108
|
+
valid_actions = {"ignore", "error", "always", "default", "once", "module"}
|
|
109
|
+
if action not in valid_actions:
|
|
110
|
+
raise ValueError(f"Invalid action '{action}'. Must be one of: {valid_actions}")
|
|
111
|
+
|
|
112
|
+
_filters[category] = action
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def resetwarnings() -> None:
|
|
116
|
+
"""Reset all warning filters to default behavior."""
|
|
117
|
+
global _filters, _seen_warnings
|
|
118
|
+
_filters = {}
|
|
119
|
+
_seen_warnings = set()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _get_action(category: Type[SciTeXWarning]) -> str:
|
|
123
|
+
"""Get the action for a warning category, checking inheritance."""
|
|
124
|
+
# Check exact match first
|
|
125
|
+
if category in _filters:
|
|
126
|
+
return _filters[category]
|
|
127
|
+
|
|
128
|
+
# Check parent classes
|
|
129
|
+
for filter_cat, action in _filters.items():
|
|
130
|
+
if issubclass(category, filter_cat):
|
|
131
|
+
return action
|
|
132
|
+
|
|
133
|
+
# Default action
|
|
134
|
+
return "default"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# =============================================================================
|
|
138
|
+
# Warning Emission
|
|
139
|
+
# =============================================================================
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def warn(
|
|
143
|
+
message: str,
|
|
144
|
+
category: Type[SciTeXWarning] = SciTeXWarning,
|
|
145
|
+
stacklevel: int = 2,
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Emit a warning (like warnings.warn but integrated with scitex.logging).
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
message : str
|
|
152
|
+
Warning message
|
|
153
|
+
category : type
|
|
154
|
+
Warning category (default: SciTeXWarning)
|
|
155
|
+
stacklevel : int
|
|
156
|
+
Stack level for source location (default: 2 = caller)
|
|
157
|
+
|
|
158
|
+
Examples
|
|
159
|
+
--------
|
|
160
|
+
>>> import scitex.logging as logging
|
|
161
|
+
>>> from scitex.logging import UnitWarning
|
|
162
|
+
>>> logging.warn("X axis has no units", UnitWarning)
|
|
163
|
+
"""
|
|
164
|
+
import inspect
|
|
165
|
+
|
|
166
|
+
action = _get_action(category)
|
|
167
|
+
|
|
168
|
+
# Handle action
|
|
169
|
+
if action == "ignore":
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
if action == "error":
|
|
173
|
+
raise category(message)
|
|
174
|
+
|
|
175
|
+
# Get source location for "once", "module", "default" actions
|
|
176
|
+
frame = inspect.currentframe()
|
|
177
|
+
for _ in range(stacklevel):
|
|
178
|
+
if frame is not None:
|
|
179
|
+
frame = frame.f_back
|
|
180
|
+
|
|
181
|
+
location = ""
|
|
182
|
+
if frame is not None:
|
|
183
|
+
filename = frame.f_code.co_filename
|
|
184
|
+
lineno = frame.f_lineno
|
|
185
|
+
location = f"{filename}:{lineno}"
|
|
186
|
+
|
|
187
|
+
# Check if already seen
|
|
188
|
+
warn_key = f"{category.__name__}:{message}:{location}"
|
|
189
|
+
|
|
190
|
+
if action == "once":
|
|
191
|
+
if warn_key in _seen_warnings:
|
|
192
|
+
return
|
|
193
|
+
_seen_warnings.add(warn_key)
|
|
194
|
+
elif action == "default":
|
|
195
|
+
# Show first per location
|
|
196
|
+
loc_key = f"{category.__name__}:{location}"
|
|
197
|
+
if loc_key in _seen_warnings:
|
|
198
|
+
return
|
|
199
|
+
_seen_warnings.add(loc_key)
|
|
200
|
+
elif action == "module":
|
|
201
|
+
# Show once per module
|
|
202
|
+
if frame is not None:
|
|
203
|
+
module_key = f"{category.__name__}:{frame.f_code.co_filename}"
|
|
204
|
+
if module_key in _seen_warnings:
|
|
205
|
+
return
|
|
206
|
+
_seen_warnings.add(module_key)
|
|
207
|
+
|
|
208
|
+
# Emit via scitex.logging
|
|
209
|
+
logger = _logging.getLogger("scitex.warnings")
|
|
210
|
+
category_name = category.__name__
|
|
211
|
+
|
|
212
|
+
# Format: "UnitWarning: message"
|
|
213
|
+
logger.warning(f"{category_name}: {message}")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# =============================================================================
|
|
217
|
+
# Convenience Warning Functions
|
|
218
|
+
# =============================================================================
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def warn_deprecated(
|
|
222
|
+
old_name: str, new_name: str, version: Optional[str] = None
|
|
223
|
+
) -> None:
|
|
224
|
+
"""Issue a deprecation warning."""
|
|
225
|
+
message = f"{old_name} is deprecated. Use {new_name} instead."
|
|
226
|
+
if version:
|
|
227
|
+
message += f" Will be removed in version {version}."
|
|
228
|
+
warn(message, SciTeXDeprecationWarning, stacklevel=3)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def warn_performance(operation: str, suggestion: str) -> None:
|
|
232
|
+
"""Issue a performance warning."""
|
|
233
|
+
message = f"Performance warning in {operation}: {suggestion}"
|
|
234
|
+
warn(message, PerformanceWarning, stacklevel=3)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def warn_data_loss(operation: str, detail: str) -> None:
|
|
238
|
+
"""Issue a data loss warning."""
|
|
239
|
+
message = f"Potential data loss in {operation}: {detail}"
|
|
240
|
+
warn(message, DataLossWarning, stacklevel=3)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
__all__ = [
|
|
244
|
+
# Warning categories
|
|
245
|
+
"SciTeXWarning",
|
|
246
|
+
"UnitWarning",
|
|
247
|
+
"StyleWarning",
|
|
248
|
+
"SciTeXDeprecationWarning",
|
|
249
|
+
"PerformanceWarning",
|
|
250
|
+
"DataLossWarning",
|
|
251
|
+
# Functions
|
|
252
|
+
"warn",
|
|
253
|
+
"filterwarnings",
|
|
254
|
+
"resetwarnings",
|
|
255
|
+
# Convenience functions
|
|
256
|
+
"warn_deprecated",
|
|
257
|
+
"warn_performance",
|
|
258
|
+
"warn_data_loss",
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
# EOF
|
scitex/plt/__init__.py
CHANGED
|
@@ -179,7 +179,10 @@ try:
|
|
|
179
179
|
except Exception:
|
|
180
180
|
pass # Use matplotlib default colors if color module fails
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
try:
|
|
183
|
+
from ._tpl import termplot
|
|
184
|
+
except ImportError:
|
|
185
|
+
termplot = None
|
|
183
186
|
from . import color
|
|
184
187
|
from . import utils
|
|
185
188
|
from . import ax
|
scitex/plt/_figrecipe.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2026-01-01 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/plt/_figrecipe.py
|
|
4
|
+
# ----------------------------------------
|
|
5
|
+
"""
|
|
6
|
+
Figrecipe integration for scitex.
|
|
7
|
+
|
|
8
|
+
This module provides integration with figrecipe for reproducible matplotlib figures.
|
|
9
|
+
Uses csv_format="single" by default for backward compatibility with scitex's
|
|
10
|
+
SigmaPlot-compatible CSV format.
|
|
11
|
+
|
|
12
|
+
Usage
|
|
13
|
+
-----
|
|
14
|
+
>>> import scitex.plt as splt
|
|
15
|
+
>>> fig, ax = splt.subplots()
|
|
16
|
+
>>> ax.plot([1, 2, 3], [4, 5, 6], id='data')
|
|
17
|
+
>>> splt.save_recipe(fig, 'figure.yaml') # Saves recipe with single CSV
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Dict, Literal, Optional, Tuple, Union
|
|
22
|
+
|
|
23
|
+
from scitex import logging
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
# Check if figrecipe is available
|
|
28
|
+
try:
|
|
29
|
+
import figrecipe as fr
|
|
30
|
+
|
|
31
|
+
FIGRECIPE_AVAILABLE = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
FIGRECIPE_AVAILABLE = False
|
|
34
|
+
fr = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def check_figrecipe_available() -> bool:
|
|
38
|
+
"""Check if figrecipe is installed."""
|
|
39
|
+
return FIGRECIPE_AVAILABLE
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def subplots(
|
|
43
|
+
nrows: int = 1,
|
|
44
|
+
ncols: int = 1,
|
|
45
|
+
**kwargs,
|
|
46
|
+
) -> Tuple[Any, Any]:
|
|
47
|
+
"""Create recording-enabled subplots using figrecipe.
|
|
48
|
+
|
|
49
|
+
This is a wrapper around figrecipe.subplots() that creates
|
|
50
|
+
figures with recording capabilities for reproducibility.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
nrows, ncols : int
|
|
55
|
+
Number of rows and columns.
|
|
56
|
+
**kwargs
|
|
57
|
+
Additional arguments passed to figrecipe.subplots().
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
fig : RecordingFigure
|
|
62
|
+
Figrecipe's wrapped figure.
|
|
63
|
+
axes : RecordingAxes or ndarray
|
|
64
|
+
Wrapped axes.
|
|
65
|
+
|
|
66
|
+
Raises
|
|
67
|
+
------
|
|
68
|
+
ImportError
|
|
69
|
+
If figrecipe is not installed.
|
|
70
|
+
"""
|
|
71
|
+
if not FIGRECIPE_AVAILABLE:
|
|
72
|
+
raise ImportError(
|
|
73
|
+
"figrecipe is not installed. Install with: pip install figrecipe"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return fr.subplots(nrows, ncols, **kwargs)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def save_recipe(
|
|
80
|
+
fig,
|
|
81
|
+
path: Union[str, Path],
|
|
82
|
+
csv_format: Literal["single", "separate"] = "single",
|
|
83
|
+
data_format: Literal["csv", "npz", "inline"] = "csv",
|
|
84
|
+
validate: bool = True,
|
|
85
|
+
verbose: bool = True,
|
|
86
|
+
**kwargs,
|
|
87
|
+
) -> Optional[Tuple[Path, Path]]:
|
|
88
|
+
"""Save figure recipe using figrecipe with scitex-compatible CSV format.
|
|
89
|
+
|
|
90
|
+
This function saves a matplotlib figure as a reproducible recipe using
|
|
91
|
+
figrecipe. By default, uses csv_format="single" for backward compatibility
|
|
92
|
+
with scitex's SigmaPlot-compatible CSV format.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
fig : matplotlib.figure.Figure or RecordingFigure
|
|
97
|
+
The figure to save.
|
|
98
|
+
path : str or Path
|
|
99
|
+
Output path (.yaml for recipe, .png/.pdf for image+recipe).
|
|
100
|
+
csv_format : str
|
|
101
|
+
CSV format: 'single' (scitex-compatible, default) or 'separate'.
|
|
102
|
+
- 'single': All columns in one CSV with scitex naming convention
|
|
103
|
+
- 'separate': One CSV per variable (figrecipe default)
|
|
104
|
+
data_format : str
|
|
105
|
+
Data format: 'csv' (default), 'npz', or 'inline'.
|
|
106
|
+
validate : bool
|
|
107
|
+
If True (default), validate reproducibility after saving.
|
|
108
|
+
verbose : bool
|
|
109
|
+
If True (default), print save status.
|
|
110
|
+
**kwargs
|
|
111
|
+
Additional arguments passed to figrecipe.save().
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
tuple or None
|
|
116
|
+
(image_path, yaml_path) if successful, None if figrecipe unavailable.
|
|
117
|
+
|
|
118
|
+
Examples
|
|
119
|
+
--------
|
|
120
|
+
>>> import scitex.plt as splt
|
|
121
|
+
>>> fig, ax = splt.subplots()
|
|
122
|
+
>>> ax.plot([1, 2, 3], [4, 5, 6])
|
|
123
|
+
>>> splt.save_recipe(fig, 'output.yaml') # Single CSV format (scitex-compatible)
|
|
124
|
+
>>> splt.save_recipe(fig, 'output.yaml', csv_format='separate') # Separate CSVs
|
|
125
|
+
"""
|
|
126
|
+
if not FIGRECIPE_AVAILABLE:
|
|
127
|
+
logger.warning(
|
|
128
|
+
"figrecipe is not installed. Recipe not saved. "
|
|
129
|
+
"Install with: pip install figrecipe"
|
|
130
|
+
)
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
path = Path(path)
|
|
134
|
+
|
|
135
|
+
# Handle different figure types
|
|
136
|
+
# If it's a scitex FigWrapper, extract the matplotlib figure
|
|
137
|
+
if hasattr(fig, "_fig_mpl"):
|
|
138
|
+
mpl_fig = fig._fig_mpl
|
|
139
|
+
elif hasattr(fig, "figure"):
|
|
140
|
+
mpl_fig = fig.figure
|
|
141
|
+
else:
|
|
142
|
+
mpl_fig = fig
|
|
143
|
+
|
|
144
|
+
# Check if fig is already a figrecipe RecordingFigure
|
|
145
|
+
if hasattr(fig, "_recorder"):
|
|
146
|
+
# Already a RecordingFigure, use figrecipe's save directly
|
|
147
|
+
return fr.save(
|
|
148
|
+
fig,
|
|
149
|
+
path,
|
|
150
|
+
data_format=data_format,
|
|
151
|
+
csv_format=csv_format,
|
|
152
|
+
validate=validate,
|
|
153
|
+
verbose=verbose,
|
|
154
|
+
**kwargs,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# For regular matplotlib figures, we need to wrap them first
|
|
158
|
+
# This requires re-creating the figure with figrecipe
|
|
159
|
+
logger.warning(
|
|
160
|
+
"Figure is not a RecordingFigure. For full recipe support, "
|
|
161
|
+
"create figures with fr.subplots() or splt.subplots_recipe()."
|
|
162
|
+
)
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def load_recipe(path: Union[str, Path]) -> Tuple[Any, Any]:
|
|
167
|
+
"""Load and reproduce a figure from a recipe file.
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
path : str or Path
|
|
172
|
+
Path to .yaml recipe file.
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
fig : matplotlib.figure.Figure
|
|
177
|
+
Reproduced figure.
|
|
178
|
+
axes : Axes or list of Axes
|
|
179
|
+
Reproduced axes.
|
|
180
|
+
|
|
181
|
+
Raises
|
|
182
|
+
------
|
|
183
|
+
ImportError
|
|
184
|
+
If figrecipe is not installed.
|
|
185
|
+
"""
|
|
186
|
+
if not FIGRECIPE_AVAILABLE:
|
|
187
|
+
raise ImportError(
|
|
188
|
+
"figrecipe is not installed. Install with: pip install figrecipe"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return fr.reproduce(path)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def recipe_info(path: Union[str, Path]) -> Dict[str, Any]:
|
|
195
|
+
"""Get information about a recipe without reproducing.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
path : str or Path
|
|
200
|
+
Path to .yaml recipe file.
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
dict
|
|
205
|
+
Recipe information including figure settings, calls, etc.
|
|
206
|
+
|
|
207
|
+
Raises
|
|
208
|
+
------
|
|
209
|
+
ImportError
|
|
210
|
+
If figrecipe is not installed.
|
|
211
|
+
"""
|
|
212
|
+
if not FIGRECIPE_AVAILABLE:
|
|
213
|
+
raise ImportError(
|
|
214
|
+
"figrecipe is not installed. Install with: pip install figrecipe"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
return fr.info(path)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# Convenience aliases
|
|
221
|
+
reproduce = load_recipe
|
|
222
|
+
info = recipe_info
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
__all__ = [
|
|
226
|
+
"FIGRECIPE_AVAILABLE",
|
|
227
|
+
"check_figrecipe_available",
|
|
228
|
+
"subplots",
|
|
229
|
+
"save_recipe",
|
|
230
|
+
"load_recipe",
|
|
231
|
+
"reproduce",
|
|
232
|
+
"recipe_info",
|
|
233
|
+
"info",
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
# EOF
|
|
@@ -189,10 +189,14 @@ class AxisWrapper(
|
|
|
189
189
|
"errorbar",
|
|
190
190
|
"step",
|
|
191
191
|
"stem",
|
|
192
|
+
# Fill and area plots
|
|
193
|
+
"fill",
|
|
194
|
+
"stackplot",
|
|
192
195
|
# Statistical plots
|
|
193
196
|
"hist2d",
|
|
194
197
|
"hexbin",
|
|
195
198
|
"pie",
|
|
199
|
+
"eventplot",
|
|
196
200
|
# Contour plots
|
|
197
201
|
"contour",
|
|
198
202
|
"contourf",
|
|
@@ -202,6 +206,8 @@ class AxisWrapper(
|
|
|
202
206
|
"imshow",
|
|
203
207
|
"matshow",
|
|
204
208
|
"spy",
|
|
209
|
+
"pcolormesh",
|
|
210
|
+
"pcolor",
|
|
205
211
|
# Quiver plots
|
|
206
212
|
"quiver",
|
|
207
213
|
"streamplot",
|
|
@@ -19,9 +19,111 @@ Features:
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
from typing import Optional, Dict, Tuple, Union, Any
|
|
22
|
+
import re
|
|
22
23
|
import numpy as np
|
|
23
24
|
from scitex.units import Unit, Q, Units
|
|
24
|
-
from scitex.
|
|
25
|
+
from scitex.logging import SciTeXError
|
|
26
|
+
import scitex.logging as logging
|
|
27
|
+
from scitex.logging import UnitWarning, warn as _warn
|
|
28
|
+
|
|
29
|
+
# Valid dimensionless/special unit markers
|
|
30
|
+
_VALID_DIMENSIONLESS = {"[-]", "[a.u.]", "[arb. units]", "[dimensionless]", "[1]", "[A.U.]"}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _convert_to_negative_exponent(unit: str) -> str:
|
|
34
|
+
"""Convert unit with / to negative exponent format.
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
m/s -> m·s⁻¹
|
|
38
|
+
kg/m^2 -> kg·m⁻²
|
|
39
|
+
W/m^2/K -> W·m⁻²·K⁻¹
|
|
40
|
+
"""
|
|
41
|
+
superscript = str.maketrans("0123456789-", "⁰¹²³⁴⁵⁶⁷⁸⁹⁻")
|
|
42
|
+
|
|
43
|
+
parts = unit.split("/")
|
|
44
|
+
if len(parts) < 2:
|
|
45
|
+
return unit
|
|
46
|
+
|
|
47
|
+
result = parts[0]
|
|
48
|
+
for part in parts[1:]:
|
|
49
|
+
exp_match = re.match(r"([a-zA-Z]+)\^?(\d+)?", part)
|
|
50
|
+
if exp_match:
|
|
51
|
+
base = exp_match.group(1)
|
|
52
|
+
exp = exp_match.group(2) or "1"
|
|
53
|
+
neg_exp = f"-{exp}".translate(superscript)
|
|
54
|
+
result += f"·{base}{neg_exp}"
|
|
55
|
+
else:
|
|
56
|
+
result += f"·{part}⁻¹"
|
|
57
|
+
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def validate_axis_label(label: str, axis_name: str = "axis") -> str:
|
|
62
|
+
"""Validate and warn about axis label units (educational for scientific standards).
|
|
63
|
+
|
|
64
|
+
Checks for:
|
|
65
|
+
- Missing units
|
|
66
|
+
- Non-standard format (prefer [] over ())
|
|
67
|
+
- Suggests ^-1 format over /
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
label : str
|
|
72
|
+
Axis label to validate
|
|
73
|
+
axis_name : str
|
|
74
|
+
Name for warning messages (e.g., "X axis", "Y axis")
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
str
|
|
79
|
+
Original label (warnings are educational, not auto-correcting)
|
|
80
|
+
"""
|
|
81
|
+
if not label:
|
|
82
|
+
return label
|
|
83
|
+
|
|
84
|
+
# Check for units in brackets [] or parentheses ()
|
|
85
|
+
has_square_brackets = bool(re.search(r"\[.*?\]", label))
|
|
86
|
+
has_parentheses = bool(re.search(r"\(.*?\)", label))
|
|
87
|
+
|
|
88
|
+
unit_match_square = re.search(r"\[(.*?)\]", label)
|
|
89
|
+
unit_match_paren = re.search(r"\((.*?)\)", label)
|
|
90
|
+
|
|
91
|
+
if not has_square_brackets and not has_parentheses:
|
|
92
|
+
_warn(
|
|
93
|
+
f"{axis_name} label '{label}' has no units. "
|
|
94
|
+
f"Consider: '{label} [unit]' or '{label} [-]' for dimensionless",
|
|
95
|
+
UnitWarning,
|
|
96
|
+
stacklevel=3,
|
|
97
|
+
)
|
|
98
|
+
return label
|
|
99
|
+
|
|
100
|
+
if has_parentheses and not has_square_brackets:
|
|
101
|
+
unit = unit_match_paren.group(1) if unit_match_paren else ""
|
|
102
|
+
suggested = re.sub(r"\((.*?)\)", f"[{unit}]", label)
|
|
103
|
+
_warn(
|
|
104
|
+
f"{axis_name} label '{label}' uses parentheses. "
|
|
105
|
+
f"SI convention prefers: '{suggested}'",
|
|
106
|
+
UnitWarning,
|
|
107
|
+
stacklevel=3,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
unit_content = None
|
|
111
|
+
if unit_match_square:
|
|
112
|
+
unit_content = unit_match_square.group(1)
|
|
113
|
+
elif unit_match_paren:
|
|
114
|
+
unit_content = unit_match_paren.group(1)
|
|
115
|
+
|
|
116
|
+
if unit_content and "/" in unit_content:
|
|
117
|
+
suggested_unit = _convert_to_negative_exponent(unit_content)
|
|
118
|
+
if suggested_unit != unit_content:
|
|
119
|
+
suggested_label = label.replace(f"[{unit_content}]", f"[{suggested_unit}]")
|
|
120
|
+
_warn(
|
|
121
|
+
f"{axis_name} uses '/' in units. Consider: '{suggested_label}'",
|
|
122
|
+
UnitWarning,
|
|
123
|
+
stacklevel=3,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return label
|
|
25
127
|
|
|
26
128
|
|
|
27
129
|
class UnitMismatchError(SciTeXError):
|
|
@@ -287,6 +389,9 @@ class UnitAwareMixin:
|
|
|
287
389
|
if self._x_unit:
|
|
288
390
|
label = f"{label} [{self._x_unit.symbol}]"
|
|
289
391
|
|
|
392
|
+
# Validate units (educational warnings for scientific standards)
|
|
393
|
+
validate_axis_label(label, "X axis")
|
|
394
|
+
|
|
290
395
|
self._axes_mpl.set_xlabel(label)
|
|
291
396
|
|
|
292
397
|
def set_ylabel(self, label: str, unit: Optional[Union[str, Unit]] = None) -> None:
|
|
@@ -305,6 +410,9 @@ class UnitAwareMixin:
|
|
|
305
410
|
if self._y_unit:
|
|
306
411
|
label = f"{label} [{self._y_unit.symbol}]"
|
|
307
412
|
|
|
413
|
+
# Validate units (educational warnings for scientific standards)
|
|
414
|
+
validate_axis_label(label, "Y axis")
|
|
415
|
+
|
|
308
416
|
self._axes_mpl.set_ylabel(label)
|
|
309
417
|
|
|
310
418
|
def set_zlabel(self, label: str, unit: Optional[Union[str, Unit]] = None) -> None:
|
|
@@ -326,4 +434,7 @@ class UnitAwareMixin:
|
|
|
326
434
|
if self._z_unit:
|
|
327
435
|
label = f"{label} [{self._z_unit.symbol}]"
|
|
328
436
|
|
|
437
|
+
# Validate units (educational warnings for scientific standards)
|
|
438
|
+
validate_axis_label(label, "Z axis")
|
|
439
|
+
|
|
329
440
|
self._axes_mpl.set_zlabel(label)
|
|
@@ -304,6 +304,21 @@ class FigWrapper:
|
|
|
304
304
|
for ax in self.axes:
|
|
305
305
|
yield ax
|
|
306
306
|
|
|
307
|
+
@property
|
|
308
|
+
def history(self):
|
|
309
|
+
"""Aggregate tracking history from all axes in the figure.
|
|
310
|
+
|
|
311
|
+
Returns a combined OrderedDict of all tracking records from all axes,
|
|
312
|
+
enabling FTS bundle creation to build encoding from plot operations.
|
|
313
|
+
"""
|
|
314
|
+
from collections import OrderedDict
|
|
315
|
+
|
|
316
|
+
combined = OrderedDict()
|
|
317
|
+
for ax in self._traverse_axes():
|
|
318
|
+
if hasattr(ax, "history") and ax.history:
|
|
319
|
+
combined.update(ax.history)
|
|
320
|
+
return combined
|
|
321
|
+
|
|
307
322
|
def legend(self, *args, loc="best", **kwargs):
|
|
308
323
|
"""Legend with 'best' automatic placement by default for all axes."""
|
|
309
324
|
for ax in self._traverse_axes():
|