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
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
# Timestamp: "2025-05-29 03:46:53 (ywatanabe)"
|
|
4
|
-
# File: /ssh:ywatanabe@sp:/home/ywatanabe/proj/scitex_repo/src/scitex/plt/_subplots/_SubplotsWrapper.py
|
|
5
|
-
# ----------------------------------------
|
|
6
|
-
import os
|
|
7
|
-
|
|
8
|
-
__FILE__ = "./src/scitex/plt/_subplots/_SubplotsWrapper.py"
|
|
9
|
-
__DIR__ = os.path.dirname(__FILE__)
|
|
10
|
-
# ----------------------------------------
|
|
2
|
+
"""SubplotsWrapper: Monitor data plotted using matplotlib for CSV export."""
|
|
11
3
|
|
|
4
|
+
import os
|
|
12
5
|
from collections import OrderedDict
|
|
13
6
|
|
|
14
7
|
import matplotlib.pyplot as plt
|
|
15
|
-
import numpy as np
|
|
16
8
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
__FILE__ = "./src/scitex/plt/_subplots/_SubplotsWrapper.py"
|
|
10
|
+
__DIR__ = os.path.dirname(__FILE__)
|
|
11
|
+
|
|
12
|
+
# Configure fonts at import
|
|
13
|
+
from ._fonts import _arial_enabled # noqa: F401
|
|
14
|
+
from ._mm_layout import create_with_mm_control
|
|
20
15
|
|
|
21
16
|
# Register Arial fonts at module import
|
|
22
17
|
import matplotlib.font_manager as fm
|
|
@@ -75,12 +70,28 @@ class SubplotsWrapper:
|
|
|
75
70
|
"""
|
|
76
71
|
A wrapper class monitors data plotted using the ax methods from matplotlib.pyplot.
|
|
77
72
|
This data can be converted into a CSV file formatted for SigmaPlot compatibility.
|
|
73
|
+
|
|
74
|
+
Supports optional figrecipe integration for reproducible figures.
|
|
75
|
+
When figrecipe is available and `use_figrecipe=True`, figures are created
|
|
76
|
+
with recipe recording capability for later reproduction.
|
|
78
77
|
"""
|
|
79
78
|
|
|
80
79
|
def __init__(self):
|
|
81
80
|
self._subplots_wrapper_history = OrderedDict()
|
|
82
81
|
self._fig_scitex = None
|
|
83
82
|
self._counter_part = plt.subplots
|
|
83
|
+
self._figrecipe_available = None # Lazy check
|
|
84
|
+
|
|
85
|
+
def _check_figrecipe(self):
|
|
86
|
+
"""Check if figrecipe is available (lazy, cached)."""
|
|
87
|
+
if self._figrecipe_available is None:
|
|
88
|
+
try:
|
|
89
|
+
import figrecipe # noqa: F401
|
|
90
|
+
|
|
91
|
+
self._figrecipe_available = True
|
|
92
|
+
except ImportError:
|
|
93
|
+
self._figrecipe_available = False
|
|
94
|
+
return self._figrecipe_available
|
|
84
95
|
|
|
85
96
|
def __call__(
|
|
86
97
|
self,
|
|
@@ -89,6 +100,7 @@ class SubplotsWrapper:
|
|
|
89
100
|
sharex=False,
|
|
90
101
|
sharey=False,
|
|
91
102
|
constrained_layout=None,
|
|
103
|
+
use_figrecipe=None, # NEW: Enable figrecipe recording
|
|
92
104
|
# MM-control parameters (unified style system)
|
|
93
105
|
axes_width_mm=None,
|
|
94
106
|
axes_height_mm=None,
|
|
@@ -111,9 +123,9 @@ class SubplotsWrapper:
|
|
|
111
123
|
n_ticks=None,
|
|
112
124
|
mode=None,
|
|
113
125
|
dpi=None,
|
|
114
|
-
styles=None,
|
|
115
|
-
transparent=None,
|
|
116
|
-
theme=None,
|
|
126
|
+
styles=None,
|
|
127
|
+
transparent=None,
|
|
128
|
+
theme=None,
|
|
117
129
|
**kwargs,
|
|
118
130
|
):
|
|
119
131
|
"""
|
|
@@ -125,104 +137,33 @@ class SubplotsWrapper:
|
|
|
125
137
|
nrows, ncols passed to matplotlib.pyplot.subplots
|
|
126
138
|
track : bool, optional
|
|
127
139
|
Track plotting operations for CSV export (default: True)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
MM-Control Parameters
|
|
134
|
-
|
|
135
|
-
axes_width_mm : float or list
|
|
136
|
-
Axes
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
margin_bottom_mm : float, optional
|
|
144
|
-
Bottom margin in mm (default: 5.0)
|
|
145
|
-
margin_top_mm : float, optional
|
|
146
|
-
Top margin in mm (default: 2.0)
|
|
147
|
-
space_w_mm : float, optional
|
|
148
|
-
Horizontal spacing between axes in mm (default: 3.0)
|
|
149
|
-
space_h_mm : float, optional
|
|
150
|
-
Vertical spacing between axes in mm (default: 3.0)
|
|
151
|
-
axes_thickness_mm : float, optional
|
|
152
|
-
Axes spine thickness in mm
|
|
153
|
-
tick_length_mm : float, optional
|
|
154
|
-
Tick length in mm
|
|
155
|
-
tick_thickness_mm : float, optional
|
|
156
|
-
Tick thickness in mm
|
|
157
|
-
trace_thickness_mm : float, optional
|
|
158
|
-
Plot line thickness in mm
|
|
159
|
-
axis_font_size_pt : float, optional
|
|
160
|
-
Axis label font size in points
|
|
161
|
-
tick_font_size_pt : float, optional
|
|
162
|
-
Tick label font size in points
|
|
163
|
-
mode : str, optional
|
|
164
|
-
'publication' or 'display' (default: None, uses standard matplotlib)
|
|
165
|
-
dpi : int, optional
|
|
166
|
-
Resolution (default: 300 for publication, 100 for display)
|
|
167
|
-
styles : list of dict, optional
|
|
168
|
-
Individual style dicts for each axes
|
|
169
|
-
transparent : bool, optional
|
|
170
|
-
Create figure with transparent background (default: False)
|
|
171
|
-
Useful for publication-ready figures that will be cropped
|
|
172
|
-
theme : str, optional
|
|
173
|
-
Color theme: "light" or "dark" (default: "light")
|
|
174
|
-
Dark mode uses eye-friendly colors optimized for dark backgrounds
|
|
175
|
-
**kwargs
|
|
176
|
-
Additional arguments passed to matplotlib.pyplot.subplots
|
|
140
|
+
use_figrecipe : bool or None, optional
|
|
141
|
+
If True, use figrecipe for recipe recording.
|
|
142
|
+
If None (default), auto-detect figrecipe availability.
|
|
143
|
+
If False, disable figrecipe even if available.
|
|
144
|
+
|
|
145
|
+
MM-Control Parameters
|
|
146
|
+
---------------------
|
|
147
|
+
axes_width_mm, axes_height_mm : float or list
|
|
148
|
+
Axes dimensions in mm
|
|
149
|
+
margin_*_mm : float
|
|
150
|
+
Figure margins in mm
|
|
151
|
+
space_w_mm, space_h_mm : float
|
|
152
|
+
Spacing between axes in mm
|
|
153
|
+
mode : str
|
|
154
|
+
'publication' or 'display'
|
|
177
155
|
|
|
178
156
|
Returns
|
|
179
157
|
-------
|
|
180
158
|
fig : FigWrapper
|
|
181
|
-
Wrapped matplotlib Figure
|
|
159
|
+
Wrapped matplotlib Figure (with optional RecordingFigure)
|
|
182
160
|
ax or axes : AxisWrapper or AxesWrapper
|
|
183
161
|
Wrapped matplotlib Axes
|
|
184
|
-
|
|
185
|
-
Examples
|
|
186
|
-
--------
|
|
187
|
-
Single axes with style:
|
|
188
|
-
|
|
189
|
-
>>> fig, ax = stx.plt.subplots(
|
|
190
|
-
... axes_width_mm=30,
|
|
191
|
-
... axes_height_mm=21,
|
|
192
|
-
... axes_thickness_mm=0.2,
|
|
193
|
-
... tick_length_mm=0.8,
|
|
194
|
-
... mode='publication'
|
|
195
|
-
... )
|
|
196
|
-
|
|
197
|
-
Multiple axes with uniform style:
|
|
198
|
-
|
|
199
|
-
>>> fig, axes = stx.plt.subplots(
|
|
200
|
-
... nrows=2, ncols=3,
|
|
201
|
-
... axes_width_mm=30,
|
|
202
|
-
... axes_height_mm=21,
|
|
203
|
-
... space_w_mm=3,
|
|
204
|
-
... space_h_mm=3,
|
|
205
|
-
... mode='publication'
|
|
206
|
-
... )
|
|
207
|
-
|
|
208
|
-
Using style preset:
|
|
209
|
-
|
|
210
|
-
>>> NATURE_STYLE = {
|
|
211
|
-
... 'axes_width_mm': 30,
|
|
212
|
-
... 'axes_height_mm': 21,
|
|
213
|
-
... 'axes_thickness_mm': 0.2,
|
|
214
|
-
... 'tick_length_mm': 0.8,
|
|
215
|
-
... }
|
|
216
|
-
>>> fig, ax = stx.plt.subplots(**NATURE_STYLE)
|
|
217
162
|
"""
|
|
163
|
+
# Resolve style values
|
|
164
|
+
from scitex.plt.styles import SCITEX_STYLE as _S
|
|
165
|
+
from scitex.plt.styles import resolve_style_value as _resolve
|
|
218
166
|
|
|
219
|
-
# Use resolve_style_value for priority: direct → yaml → env → default
|
|
220
|
-
from scitex.plt.styles import (
|
|
221
|
-
resolve_style_value as _resolve,
|
|
222
|
-
SCITEX_STYLE as _S,
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
# Resolve all style values with proper priority chain
|
|
226
167
|
axes_width_mm = _resolve(
|
|
227
168
|
"axes.width_mm", axes_width_mm, _S.get("axes_width_mm")
|
|
228
169
|
)
|
|
@@ -275,419 +216,114 @@ class SubplotsWrapper:
|
|
|
275
216
|
)
|
|
276
217
|
n_ticks = _resolve("ticks.n_ticks", n_ticks, _S.get("n_ticks"), int)
|
|
277
218
|
dpi = _resolve("output.dpi", dpi, _S.get("dpi"), int)
|
|
278
|
-
|
|
219
|
+
|
|
279
220
|
if transparent is None:
|
|
280
221
|
transparent = _S.get("transparent", True)
|
|
281
222
|
if mode is None:
|
|
282
223
|
mode = _S.get("mode", "publication")
|
|
283
|
-
# Resolve theme from YAML (default: "light")
|
|
284
224
|
if theme is None:
|
|
285
225
|
theme = _resolve("theme.mode", None, "light", str)
|
|
286
226
|
|
|
287
|
-
#
|
|
288
|
-
if
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
def _create_with_mm_control(
|
|
323
|
-
self,
|
|
324
|
-
*args,
|
|
325
|
-
track=True,
|
|
326
|
-
sharex=False,
|
|
327
|
-
sharey=False,
|
|
328
|
-
axes_width_mm=None,
|
|
329
|
-
axes_height_mm=None,
|
|
330
|
-
margin_left_mm=None,
|
|
331
|
-
margin_right_mm=None,
|
|
332
|
-
margin_bottom_mm=None,
|
|
333
|
-
margin_top_mm=None,
|
|
334
|
-
space_w_mm=None,
|
|
335
|
-
space_h_mm=None,
|
|
336
|
-
axes_thickness_mm=None,
|
|
337
|
-
tick_length_mm=None,
|
|
338
|
-
tick_thickness_mm=None,
|
|
339
|
-
trace_thickness_mm=None,
|
|
340
|
-
marker_size_mm=None,
|
|
341
|
-
axis_font_size_pt=None,
|
|
342
|
-
tick_font_size_pt=None,
|
|
343
|
-
title_font_size_pt=None,
|
|
344
|
-
legend_font_size_pt=None,
|
|
345
|
-
suptitle_font_size_pt=None,
|
|
346
|
-
label_pad_pt=None,
|
|
347
|
-
tick_pad_pt=None,
|
|
348
|
-
title_pad_pt=None,
|
|
349
|
-
font_family=None,
|
|
350
|
-
n_ticks=None,
|
|
351
|
-
mode=None,
|
|
352
|
-
dpi=None,
|
|
353
|
-
styles=None,
|
|
354
|
-
transparent=None, # Resolved from caller
|
|
355
|
-
theme=None, # Color theme: "light" or "dark"
|
|
356
|
-
**kwargs,
|
|
357
|
-
):
|
|
358
|
-
"""Create figure with mm-based control over axes dimensions."""
|
|
359
|
-
from scitex.plt.utils import mm_to_inch, apply_style_mm
|
|
360
|
-
|
|
361
|
-
# Parse nrows, ncols from args or kwargs (like matplotlib.pyplot.subplots)
|
|
362
|
-
nrows, ncols = 1, 1
|
|
363
|
-
if len(args) >= 1:
|
|
364
|
-
nrows = args[0]
|
|
365
|
-
elif "nrows" in kwargs:
|
|
366
|
-
nrows = kwargs.pop("nrows")
|
|
367
|
-
if len(args) >= 2:
|
|
368
|
-
ncols = args[1]
|
|
369
|
-
elif "ncols" in kwargs:
|
|
370
|
-
ncols = kwargs.pop("ncols")
|
|
371
|
-
|
|
372
|
-
n_axes = nrows * ncols
|
|
373
|
-
|
|
374
|
-
# Apply mode-specific defaults
|
|
375
|
-
if mode == "display":
|
|
376
|
-
scale_factor = 3.0
|
|
377
|
-
dpi = dpi or 100
|
|
378
|
-
else: # publication or None
|
|
379
|
-
scale_factor = 1.0
|
|
380
|
-
dpi = dpi or 300
|
|
381
|
-
|
|
382
|
-
# Set defaults - if value is provided, apply scaling; if not, use scaled default
|
|
383
|
-
if axes_width_mm is None:
|
|
384
|
-
axes_width_mm = 30.0 * scale_factor
|
|
385
|
-
elif mode == "display":
|
|
386
|
-
axes_width_mm = axes_width_mm * scale_factor
|
|
387
|
-
|
|
388
|
-
if axes_height_mm is None:
|
|
389
|
-
axes_height_mm = 21.0 * scale_factor
|
|
390
|
-
elif mode == "display":
|
|
391
|
-
axes_height_mm = axes_height_mm * scale_factor
|
|
392
|
-
|
|
393
|
-
margin_left_mm = (
|
|
394
|
-
margin_left_mm if margin_left_mm is not None else (5.0 * scale_factor)
|
|
395
|
-
)
|
|
396
|
-
margin_right_mm = (
|
|
397
|
-
margin_right_mm if margin_right_mm is not None else (2.0 * scale_factor)
|
|
398
|
-
)
|
|
399
|
-
margin_bottom_mm = (
|
|
400
|
-
margin_bottom_mm if margin_bottom_mm is not None else (5.0 * scale_factor)
|
|
401
|
-
)
|
|
402
|
-
margin_top_mm = (
|
|
403
|
-
margin_top_mm if margin_top_mm is not None else (2.0 * scale_factor)
|
|
404
|
-
)
|
|
405
|
-
space_w_mm = space_w_mm if space_w_mm is not None else (3.0 * scale_factor)
|
|
406
|
-
space_h_mm = space_h_mm if space_h_mm is not None else (3.0 * scale_factor)
|
|
407
|
-
|
|
408
|
-
# Handle list vs scalar for axes_width_mm and axes_height_mm
|
|
409
|
-
if isinstance(axes_width_mm, (list, tuple)):
|
|
410
|
-
ax_widths_mm = list(axes_width_mm)
|
|
411
|
-
if len(ax_widths_mm) != n_axes:
|
|
412
|
-
raise ValueError(
|
|
413
|
-
f"axes_width_mm list length ({len(ax_widths_mm)}) must match nrows*ncols ({n_axes})"
|
|
414
|
-
)
|
|
415
|
-
else:
|
|
416
|
-
ax_widths_mm = [axes_width_mm] * n_axes
|
|
417
|
-
|
|
418
|
-
if isinstance(axes_height_mm, (list, tuple)):
|
|
419
|
-
ax_heights_mm = list(axes_height_mm)
|
|
420
|
-
if len(ax_heights_mm) != n_axes:
|
|
421
|
-
raise ValueError(
|
|
422
|
-
f"axes_height_mm list length ({len(ax_heights_mm)}) must match nrows*ncols ({n_axes})"
|
|
423
|
-
)
|
|
424
|
-
else:
|
|
425
|
-
ax_heights_mm = [axes_height_mm] * n_axes
|
|
426
|
-
|
|
427
|
-
# Calculate figure size from axes grid
|
|
428
|
-
# For simplicity, use max width per column and max height per row
|
|
429
|
-
ax_widths_2d = np.array(ax_widths_mm).reshape(nrows, ncols)
|
|
430
|
-
ax_heights_2d = np.array(ax_heights_mm).reshape(nrows, ncols)
|
|
431
|
-
|
|
432
|
-
max_widths_per_col = ax_widths_2d.max(axis=0) # Max width in each column
|
|
433
|
-
max_heights_per_row = ax_heights_2d.max(axis=1) # Max height in each row
|
|
434
|
-
|
|
435
|
-
total_width_mm = (
|
|
436
|
-
margin_left_mm
|
|
437
|
-
+ max_widths_per_col.sum()
|
|
438
|
-
+ (ncols - 1) * space_w_mm
|
|
439
|
-
+ margin_right_mm
|
|
440
|
-
)
|
|
441
|
-
total_height_mm = (
|
|
442
|
-
margin_bottom_mm
|
|
443
|
-
+ max_heights_per_row.sum()
|
|
444
|
-
+ (nrows - 1) * space_h_mm
|
|
445
|
-
+ margin_top_mm
|
|
227
|
+
# Determine figrecipe usage
|
|
228
|
+
if use_figrecipe is None:
|
|
229
|
+
use_figrecipe = self._check_figrecipe()
|
|
230
|
+
|
|
231
|
+
# Create figure with mm-control
|
|
232
|
+
fig, axes = create_with_mm_control(
|
|
233
|
+
*args,
|
|
234
|
+
track=track,
|
|
235
|
+
sharex=sharex,
|
|
236
|
+
sharey=sharey,
|
|
237
|
+
axes_width_mm=axes_width_mm,
|
|
238
|
+
axes_height_mm=axes_height_mm,
|
|
239
|
+
margin_left_mm=margin_left_mm,
|
|
240
|
+
margin_right_mm=margin_right_mm,
|
|
241
|
+
margin_bottom_mm=margin_bottom_mm,
|
|
242
|
+
margin_top_mm=margin_top_mm,
|
|
243
|
+
space_w_mm=space_w_mm,
|
|
244
|
+
space_h_mm=space_h_mm,
|
|
245
|
+
axes_thickness_mm=axes_thickness_mm,
|
|
246
|
+
tick_length_mm=tick_length_mm,
|
|
247
|
+
tick_thickness_mm=tick_thickness_mm,
|
|
248
|
+
trace_thickness_mm=trace_thickness_mm,
|
|
249
|
+
marker_size_mm=marker_size_mm,
|
|
250
|
+
axis_font_size_pt=axis_font_size_pt,
|
|
251
|
+
tick_font_size_pt=tick_font_size_pt,
|
|
252
|
+
title_font_size_pt=title_font_size_pt,
|
|
253
|
+
legend_font_size_pt=legend_font_size_pt,
|
|
254
|
+
suptitle_font_size_pt=suptitle_font_size_pt,
|
|
255
|
+
n_ticks=n_ticks,
|
|
256
|
+
mode=mode,
|
|
257
|
+
dpi=dpi,
|
|
258
|
+
styles=styles,
|
|
259
|
+
transparent=transparent,
|
|
260
|
+
theme=theme,
|
|
261
|
+
**kwargs,
|
|
446
262
|
)
|
|
447
263
|
|
|
448
|
-
#
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
# Create axes array and position each one manually
|
|
461
|
-
axes_mpl_list = []
|
|
462
|
-
ax_idx = 0
|
|
463
|
-
|
|
464
|
-
for row in range(nrows):
|
|
465
|
-
for col in range(ncols):
|
|
466
|
-
# Calculate position for this axes
|
|
467
|
-
# Left position: left margin + sum of previous column widths + spacing
|
|
468
|
-
left_mm = (
|
|
469
|
-
margin_left_mm + max_widths_per_col[:col].sum() + col * space_w_mm
|
|
470
|
-
)
|
|
471
|
-
|
|
472
|
-
# Bottom position: bottom margin + sum of heights above this row + spacing
|
|
473
|
-
# (rows are counted from top in matplotlib)
|
|
474
|
-
rows_below = nrows - row - 1
|
|
475
|
-
bottom_mm = (
|
|
476
|
-
margin_bottom_mm
|
|
477
|
-
+ max_heights_per_row[row + 1 :].sum()
|
|
478
|
-
+ rows_below * space_h_mm
|
|
479
|
-
)
|
|
480
|
-
|
|
481
|
-
# Convert to figure coordinates [0-1]
|
|
482
|
-
left = left_mm / total_width_mm
|
|
483
|
-
bottom = bottom_mm / total_height_mm
|
|
484
|
-
width = ax_widths_mm[ax_idx] / total_width_mm
|
|
485
|
-
height = ax_heights_mm[ax_idx] / total_height_mm
|
|
486
|
-
|
|
487
|
-
# Create axes at exact position with transparent background
|
|
488
|
-
ax_mpl = self._fig_mpl.add_axes([left, bottom, width, height])
|
|
489
|
-
if transparent:
|
|
490
|
-
ax_mpl.patch.set_alpha(0.0) # Make axes background transparent
|
|
491
|
-
axes_mpl_list.append(ax_mpl)
|
|
492
|
-
|
|
493
|
-
# Tag with metadata
|
|
494
|
-
ax_mpl._scitex_metadata = {
|
|
495
|
-
"created_with": "scitex.plt.subplots",
|
|
496
|
-
"mode": mode or "publication",
|
|
497
|
-
"axes_size_mm": (ax_widths_mm[ax_idx], ax_heights_mm[ax_idx]),
|
|
498
|
-
"position_in_grid": (row, col),
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
ax_idx += 1
|
|
502
|
-
|
|
503
|
-
# Apply styling to each axes
|
|
504
|
-
suptitle_font_size_pt = None
|
|
505
|
-
for i, ax_mpl in enumerate(axes_mpl_list):
|
|
506
|
-
# Determine which style dict to use
|
|
507
|
-
if styles is not None:
|
|
508
|
-
if isinstance(styles, list):
|
|
509
|
-
if len(styles) != n_axes:
|
|
510
|
-
raise ValueError(
|
|
511
|
-
f"styles list length ({len(styles)}) must match nrows*ncols ({n_axes})"
|
|
512
|
-
)
|
|
513
|
-
style_dict = styles[i]
|
|
514
|
-
else:
|
|
515
|
-
style_dict = styles
|
|
516
|
-
else:
|
|
517
|
-
# Build style dict from individual parameters
|
|
518
|
-
style_dict = {}
|
|
519
|
-
if axes_thickness_mm is not None:
|
|
520
|
-
style_dict["axis_thickness_mm"] = axes_thickness_mm
|
|
521
|
-
if tick_length_mm is not None:
|
|
522
|
-
style_dict["tick_length_mm"] = tick_length_mm
|
|
523
|
-
if tick_thickness_mm is not None:
|
|
524
|
-
style_dict["tick_thickness_mm"] = tick_thickness_mm
|
|
525
|
-
if trace_thickness_mm is not None:
|
|
526
|
-
style_dict["trace_thickness_mm"] = trace_thickness_mm
|
|
527
|
-
if marker_size_mm is not None:
|
|
528
|
-
style_dict["marker_size_mm"] = marker_size_mm
|
|
529
|
-
if axis_font_size_pt is not None:
|
|
530
|
-
style_dict["axis_font_size_pt"] = axis_font_size_pt
|
|
531
|
-
if tick_font_size_pt is not None:
|
|
532
|
-
style_dict["tick_font_size_pt"] = tick_font_size_pt
|
|
533
|
-
if title_font_size_pt is not None:
|
|
534
|
-
style_dict["title_font_size_pt"] = title_font_size_pt
|
|
535
|
-
if legend_font_size_pt is not None:
|
|
536
|
-
style_dict["legend_font_size_pt"] = legend_font_size_pt
|
|
537
|
-
if suptitle_font_size_pt is not None:
|
|
538
|
-
style_dict["suptitle_font_size_pt"] = suptitle_font_size_pt
|
|
539
|
-
if label_pad_pt is not None:
|
|
540
|
-
style_dict["label_pad_pt"] = label_pad_pt
|
|
541
|
-
if tick_pad_pt is not None:
|
|
542
|
-
style_dict["tick_pad_pt"] = tick_pad_pt
|
|
543
|
-
if title_pad_pt is not None:
|
|
544
|
-
style_dict["title_pad_pt"] = title_pad_pt
|
|
545
|
-
if font_family is not None:
|
|
546
|
-
style_dict["font_family"] = font_family
|
|
547
|
-
if n_ticks is not None:
|
|
548
|
-
style_dict["n_ticks"] = n_ticks
|
|
549
|
-
|
|
550
|
-
# Always add theme to style_dict (default: "light")
|
|
551
|
-
if theme is not None:
|
|
552
|
-
style_dict["theme"] = theme
|
|
553
|
-
|
|
554
|
-
# Extract suptitle font size if available
|
|
555
|
-
if "suptitle_font_size_pt" in style_dict:
|
|
556
|
-
suptitle_font_size_pt_value = style_dict["suptitle_font_size_pt"]
|
|
557
|
-
else:
|
|
558
|
-
suptitle_font_size_pt_value = None
|
|
559
|
-
|
|
560
|
-
# Apply style if not empty
|
|
561
|
-
if style_dict:
|
|
562
|
-
apply_style_mm(ax_mpl, style_dict)
|
|
563
|
-
# Add style to metadata
|
|
564
|
-
ax_mpl._scitex_metadata["style_mm"] = style_dict
|
|
565
|
-
|
|
566
|
-
# Store suptitle font size in figure metadata for later use
|
|
567
|
-
if suptitle_font_size_pt_value is not None:
|
|
568
|
-
self._fig_mpl._scitex_suptitle_font_size_pt = suptitle_font_size_pt_value
|
|
569
|
-
|
|
570
|
-
# Wrap the figure
|
|
571
|
-
self._fig_scitex = FigWrapper(self._fig_mpl)
|
|
572
|
-
|
|
573
|
-
# Reshape axes list to match grid shape
|
|
574
|
-
axes_array_mpl = np.array(axes_mpl_list).reshape(nrows, ncols)
|
|
575
|
-
|
|
576
|
-
# Handle single axis case
|
|
577
|
-
if n_axes == 1:
|
|
578
|
-
ax_mpl_scalar = axes_array_mpl.item()
|
|
579
|
-
self._axis_scitex = AxisWrapper(self._fig_scitex, ax_mpl_scalar, track)
|
|
580
|
-
self._fig_scitex.axes = self._axis_scitex
|
|
581
|
-
# Store reference to scitex wrapper on matplotlib axes for metadata collection
|
|
582
|
-
ax_mpl_scalar._scitex_wrapper = self._axis_scitex
|
|
583
|
-
return self._fig_scitex, self._axis_scitex
|
|
584
|
-
|
|
585
|
-
# Handle multiple axes case
|
|
586
|
-
axes_flat_scitex_list = []
|
|
587
|
-
for ax_mpl in axes_mpl_list:
|
|
588
|
-
ax_scitex = AxisWrapper(self._fig_scitex, ax_mpl, track)
|
|
589
|
-
# Store reference to scitex wrapper on matplotlib axes for metadata collection
|
|
590
|
-
ax_mpl._scitex_wrapper = ax_scitex
|
|
591
|
-
axes_flat_scitex_list.append(ax_scitex)
|
|
592
|
-
|
|
593
|
-
axes_array_scitex = np.array(axes_flat_scitex_list).reshape(nrows, ncols)
|
|
594
|
-
self._axes_scitex = AxesWrapper(self._fig_scitex, axes_array_scitex)
|
|
595
|
-
self._fig_scitex.axes = self._axes_scitex
|
|
596
|
-
|
|
597
|
-
return self._fig_scitex, self._axes_scitex
|
|
598
|
-
|
|
599
|
-
# def __getattr__(self, name):
|
|
600
|
-
# """
|
|
601
|
-
# Fallback to fetch attributes from the original matplotlib.pyplot.subplots function
|
|
602
|
-
# if they are not defined directly in this wrapper instance.
|
|
603
|
-
# This allows accessing attributes like __name__, __doc__ etc. from the original function.
|
|
604
|
-
# """
|
|
605
|
-
# print(f"Attribute of SubplotsWrapper: {name}")
|
|
606
|
-
# # Check if the attribute exists in the counterpart function
|
|
607
|
-
# if hasattr(self._counter_part, name):
|
|
608
|
-
# return getattr(self._counter_part, name)
|
|
609
|
-
# # Raise the standard error if not found in the wrapper or the counterpart
|
|
610
|
-
# raise AttributeError(
|
|
611
|
-
# f"'{type(self).__name__}' object and its counterpart '{self._counter_part.__name__}' have no attribute '{name}'"
|
|
612
|
-
# )
|
|
613
|
-
|
|
614
|
-
def __dir__(
|
|
615
|
-
self,
|
|
616
|
-
):
|
|
617
|
-
"""
|
|
618
|
-
Provide combined directory for tab completion, including
|
|
619
|
-
attributes from this wrapper and the original matplotlib.pyplot.subplots function.
|
|
264
|
+
# If figrecipe enabled, create recording layer
|
|
265
|
+
if use_figrecipe:
|
|
266
|
+
self._attach_figrecipe_recorder(fig)
|
|
267
|
+
|
|
268
|
+
self._fig_scitex = fig
|
|
269
|
+
return fig, axes
|
|
270
|
+
|
|
271
|
+
def _attach_figrecipe_recorder(self, fig_wrapper):
|
|
272
|
+
"""Attach figrecipe recorder to FigWrapper for recipe export.
|
|
273
|
+
|
|
274
|
+
This creates a RecordingFigure layer that wraps the underlying
|
|
275
|
+
matplotlib figure, enabling save_recipe() on the FigWrapper.
|
|
620
276
|
"""
|
|
621
|
-
|
|
277
|
+
try:
|
|
278
|
+
from figrecipe._recorder import Recorder
|
|
279
|
+
|
|
280
|
+
# Get the underlying matplotlib figure
|
|
281
|
+
mpl_fig = fig_wrapper._fig_mpl
|
|
282
|
+
|
|
283
|
+
# Create recorder
|
|
284
|
+
recorder = Recorder()
|
|
285
|
+
figsize = mpl_fig.get_size_inches()
|
|
286
|
+
dpi_val = mpl_fig.dpi
|
|
287
|
+
recorder.start_figure(figsize=tuple(figsize), dpi=int(dpi_val))
|
|
288
|
+
|
|
289
|
+
# Store recorder on FigWrapper for later recipe export
|
|
290
|
+
fig_wrapper._figrecipe_recorder = recorder
|
|
291
|
+
fig_wrapper._figrecipe_enabled = True
|
|
292
|
+
|
|
293
|
+
# Store style info from scitex in the recipe
|
|
294
|
+
if hasattr(mpl_fig, "_scitex_theme"):
|
|
295
|
+
recorder.figure_record.style = {"theme": mpl_fig._scitex_theme}
|
|
296
|
+
|
|
297
|
+
except Exception:
|
|
298
|
+
# Silently fail - figrecipe is optional
|
|
299
|
+
fig_wrapper._figrecipe_enabled = False
|
|
300
|
+
|
|
301
|
+
def __dir__(self):
|
|
302
|
+
"""Provide combined directory for tab completion."""
|
|
622
303
|
local_attrs = set(super().__dir__())
|
|
623
|
-
# Get attributes from the counterpart function
|
|
624
304
|
try:
|
|
625
305
|
counterpart_attrs = set(dir(self._counter_part))
|
|
626
306
|
except Exception:
|
|
627
307
|
counterpart_attrs = set()
|
|
628
|
-
# Return the sorted union
|
|
629
308
|
return sorted(local_attrs.union(counterpart_attrs))
|
|
630
309
|
|
|
631
310
|
|
|
632
|
-
# Instantiate the wrapper
|
|
311
|
+
# Instantiate the wrapper
|
|
633
312
|
subplots = SubplotsWrapper()
|
|
634
313
|
|
|
314
|
+
|
|
635
315
|
if __name__ == "__main__":
|
|
636
316
|
import matplotlib
|
|
317
|
+
|
|
637
318
|
import scitex
|
|
638
319
|
|
|
639
|
-
matplotlib.use("TkAgg")
|
|
320
|
+
matplotlib.use("TkAgg")
|
|
640
321
|
|
|
641
322
|
fig, ax = subplots()
|
|
642
323
|
ax.plot([1, 2, 3], [4, 5, 6], id="plot1")
|
|
643
324
|
ax.plot([4, 5, 6], [1, 2, 3], id="plot2")
|
|
644
325
|
scitex.io.save(fig, "/tmp/subplots_demo/plots.png")
|
|
645
326
|
|
|
646
|
-
# Behaves like native matplotlib.pyplot.subplots without tracking
|
|
647
|
-
fig, ax = subplots(track=False)
|
|
648
|
-
ax.plot([1, 2, 3], [4, 5, 6], id="plot3")
|
|
649
|
-
ax.plot([4, 5, 6], [1, 2, 3], id="plot4")
|
|
650
|
-
scitex.io.save(fig, "/tmp/subplots_demo/plots.png")
|
|
651
|
-
|
|
652
|
-
fig, ax = subplots()
|
|
653
|
-
ax.scatter([1, 2, 3], [4, 5, 6], id="scatter1")
|
|
654
|
-
ax.scatter([4, 5, 6], [1, 2, 3], id="scatter2")
|
|
655
|
-
scitex.io.save(fig, "/tmp/subplots_demo/scatters.png")
|
|
656
|
-
|
|
657
|
-
fig, ax = subplots()
|
|
658
|
-
ax.boxplot([1, 2, 3], id="boxplot1")
|
|
659
|
-
scitex.io.save(fig, "/tmp/subplots_demo/boxplot1.png")
|
|
660
|
-
|
|
661
|
-
fig, ax = subplots()
|
|
662
|
-
ax.bar(["A", "B", "C"], [4, 5, 6], id="bar1")
|
|
663
|
-
scitex.io.save(fig, "/tmp/subplots_demo/bar1.png")
|
|
664
|
-
|
|
665
327
|
print(ax.export_as_csv())
|
|
666
|
-
# plot1_plot_x plot1_plot_y plot2_plot_x ... boxplot1_boxplot_x bar1_bar_x bar1_bar_y
|
|
667
|
-
# 0 1.0 4.0 4.0 ... 1.0 A 4.0
|
|
668
|
-
# 1 2.0 5.0 5.0 ... 2.0 B 5.0
|
|
669
|
-
# 2 3.0 6.0 6.0 ... 3.0 C 6.0
|
|
670
|
-
|
|
671
|
-
print(ax.export_as_csv().keys()) # plot3 and plot 4 are not tracked
|
|
672
|
-
# [3 rows x 11 columns]
|
|
673
|
-
# Index(['plot1_plot_x', 'plot1_plot_y', 'plot2_plot_x', 'plot2_plot_y',
|
|
674
|
-
# 'scatter1_scatter_x', 'scatter1_scatter_y', 'scatter2_scatter_x',
|
|
675
|
-
# 'scatter2_scatter_y', 'boxplot1_boxplot_x', 'bar1_bar_x', 'bar1_bar_y'],
|
|
676
|
-
# dtype='object')
|
|
677
|
-
|
|
678
|
-
# If a path is passed, the sigmaplot-friendly dataframe is saved as a csv file.
|
|
679
|
-
ax.export_as_csv("./tmp/subplots_demo/for_sigmaplot.csv")
|
|
680
|
-
# Saved to: ./tmp/subplots_demo/for_sigmaplot.csv
|
|
681
|
-
|
|
682
|
-
"""
|
|
683
|
-
from matplotlib.pyplot import subplots as counter_part
|
|
684
|
-
from scitex.plt import subplots as msubplots
|
|
685
|
-
print(set(dir(msubplots)) - set(dir(counter_part)))
|
|
686
|
-
is_compatible = np.all([kk in set(dir(msubplots)) for kk in set(dir(counter_part))])
|
|
687
|
-
if is_compatible:
|
|
688
|
-
print(f"{msubplots.__name__} is compatible with {counter_part.__name__}")
|
|
689
|
-
else:
|
|
690
|
-
print(f"{msubplots.__name__} is incompatible with {counter_part.__name__}")
|
|
691
|
-
"""
|
|
692
328
|
|
|
693
329
|
# EOF
|