scitex 2.8.1__py3-none-any.whl → 2.10.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scitex/__init__.py +15 -7
- scitex/__version__.py +1 -2
- scitex/_install_guide.py +250 -0
- scitex/_optional_deps.py +206 -39
- scitex/ai/_gen_ai/_Groq.py +2 -4
- scitex/ai/_gen_ai/_OpenAI.py +5 -2
- scitex/ai/_gen_ai/_Perplexity.py +20 -6
- scitex/audio/__init__.py +24 -15
- scitex/audio/_cross_process_lock.py +139 -0
- scitex/audio/_mcp_handlers.py +256 -0
- scitex/audio/_mcp_tool_schemas.py +203 -0
- scitex/audio/engines/elevenlabs_engine.py +5 -2
- scitex/audio/mcp_server.py +98 -457
- scitex/bridge/__init__.py +30 -19
- scitex/bridge/_figrecipe.py +245 -0
- scitex/bridge/_helpers.py +2 -1
- scitex/bridge/_plt_vis.py +23 -10
- scitex/bridge/_stats_plt.py +18 -5
- scitex/bridge/_stats_vis.py +16 -2
- scitex/browser/__init__.py +84 -44
- scitex/browser/automation/__init__.py +5 -1
- scitex/browser/core/BrowserMixin.py +17 -4
- scitex/browser/core/__init__.py +11 -2
- scitex/browser/remote/CaptchaHandler.py +1 -1
- scitex/browser/remote/ZenRowsAPIClient.py +1 -1
- scitex/capture/grid.py +487 -0
- scitex/capture/mcp_handlers.py +401 -0
- scitex/capture/mcp_tool_defs.py +192 -0
- scitex/capture/mcp_tools.py +241 -0
- scitex/capture/mcp_utils.py +30 -0
- scitex/cli/convert.py +421 -0
- scitex/cli/main.py +6 -4
- scitex/datetime/__init__.py +46 -0
- scitex/datetime/_linspace.py +100 -0
- scitex/datetime/_normalize_timestamp.py +306 -0
- scitex/db/_delete_duplicates.py +4 -4
- scitex/db/_sqlite3/_delete_duplicates.py +11 -2
- scitex/dev/plt/__init__.py +61 -62
- scitex/dev/plt/demo_plotters/__init__.py +0 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
- scitex/dev/plt/mpl/get_dir_ax.py +46 -0
- scitex/dev/plt/mpl/get_signatures.py +176 -0
- scitex/dev/plt/mpl/get_signatures_details.py +522 -0
- scitex/dict/_pop_keys.py +1 -7
- scitex/dsp/__init__.py +15 -10
- scitex/dsp/add_noise.py +5 -2
- scitex/dsp/example.py +35 -22
- scitex/dsp/filt.py +8 -3
- scitex/dsp/reference.py +3 -2
- scitex/dsp/utils/__init__.py +2 -1
- scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
- scitex/dt/__init__.py +39 -2
- scitex/errors.py +82 -521
- scitex/fig/__init__.py +4 -4
- scitex/fig/editor/edit/panel_loader.py +1 -1
- scitex/fig/io/_bundle.py +7 -7
- scitex/fts/README.md +262 -0
- scitex/fts/TODO.md +66 -0
- scitex/fts/__init__.py +90 -0
- scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
- scitex/fts/_bundle/_FTS.py +657 -0
- scitex/fts/_bundle/__init__.py +38 -0
- scitex/fts/_bundle/_children.py +216 -0
- scitex/fts/_bundle/_conversion/__init__.py +15 -0
- scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
- scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
- scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
- scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
- scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
- scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
- scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
- scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
- scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
- scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
- scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
- scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
- scitex/fts/_bundle/_extractors/__init__.py +32 -0
- scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
- scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
- scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
- scitex/fts/_bundle/_loader.py +134 -0
- scitex/fts/_bundle/_mpl_helpers.py +389 -0
- scitex/fts/_bundle/_saver.py +269 -0
- scitex/fts/_bundle/_storage.py +200 -0
- scitex/fts/_bundle/_utils/__init__.py +55 -0
- scitex/fts/_bundle/_utils/_const.py +26 -0
- scitex/fts/_bundle/_utils/_errors.py +73 -0
- scitex/fts/_bundle/_utils/_generate.py +21 -0
- scitex/fts/_bundle/_utils/_types.py +76 -0
- scitex/fts/_bundle/_validation.py +434 -0
- scitex/fts/_bundle/_zipbundle.py +165 -0
- scitex/fts/_fig/__init__.py +22 -0
- scitex/fts/_fig/_backend/__init__.py +53 -0
- scitex/fts/_fig/_backend/_export.py +165 -0
- scitex/fts/_fig/_backend/_parser.py +188 -0
- scitex/fts/_fig/_backend/_render.py +538 -0
- scitex/fts/_fig/_composite.py +345 -0
- scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
- scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_fig/_dataclasses/__init__.py +47 -0
- scitex/fts/_fig/_editor/__init__.py +14 -0
- scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
- scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
- scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
- scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
- scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
- scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
- scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
- scitex/fts/_fig/_editor/_defaults.py +300 -0
- scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
- scitex/fts/_fig/_models/_Annotations.py +115 -0
- scitex/fts/_fig/_models/_Axes.py +152 -0
- scitex/fts/_fig/_models/_Figure.py +138 -0
- scitex/fts/_fig/_models/_Guides.py +104 -0
- scitex/fts/_fig/_models/_Plot.py +123 -0
- scitex/fts/_fig/_models/_Styles.py +245 -0
- scitex/fts/_fig/_models/__init__.py +80 -0
- scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
- scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
- scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
- scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_fig/_utils/__init__.py +129 -0
- scitex/fts/_fig/_utils/_auto_layout.py +127 -0
- scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
- scitex/fts/_fig/_utils/_const_sizes.py +48 -0
- scitex/fts/_fig/_utils/_convert_coords.py +77 -0
- scitex/fts/_fig/_utils/_get_template.py +178 -0
- scitex/fts/_fig/_utils/_normalize.py +73 -0
- scitex/fts/_fig/_utils/_plot_layout.py +397 -0
- scitex/fts/_fig/_utils/_validate.py +197 -0
- scitex/fts/_kinds/__init__.py +45 -0
- scitex/fts/_kinds/_figure/__init__.py +19 -0
- scitex/fts/_kinds/_figure/_composite.py +345 -0
- scitex/fts/_kinds/_plot/__init__.py +25 -0
- scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
- scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
- scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
- scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
- scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
- scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
- scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
- scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
- scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
- scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
- scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
- scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
- scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
- scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
- scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
- scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
- scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
- scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
- scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
- scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
- scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
- scitex/fts/_kinds/_shape/__init__.py +141 -0
- scitex/fts/_kinds/_stats/__init__.py +56 -0
- scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_kinds/_table/__init__.py +72 -0
- scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
- scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
- scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
- scitex/fts/_kinds/_table/_latex/_export.py +279 -0
- scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
- scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
- scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
- scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
- scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
- scitex/fts/_kinds/_text/__init__.py +77 -0
- scitex/fts/_schemas/data_info.schema.json +75 -0
- scitex/fts/_schemas/encoding.schema.json +90 -0
- scitex/fts/_schemas/node.schema.json +145 -0
- scitex/fts/_schemas/render_manifest.schema.json +62 -0
- scitex/fts/_schemas/stats.schema.json +132 -0
- scitex/fts/_schemas/theme.schema.json +141 -0
- scitex/fts/_stats/__init__.py +48 -0
- scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_tables/__init__.py +65 -0
- scitex/fts/_tables/_latex/__init__.py +93 -0
- scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
- scitex/fts/_tables/_latex/_editor/_app.py +725 -0
- scitex/fts/_tables/_latex/_export.py +279 -0
- scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
- scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
- scitex/fts/_tables/_latex/_table_exporter.py +362 -0
- scitex/fts/_tables/_latex/_utils.py +369 -0
- scitex/fts/_tables/_latex/_validator.py +445 -0
- scitex/gen/__init__.py +66 -25
- scitex/gen/misc.py +28 -0
- scitex/io/__init__.py +47 -32
- scitex/io/_load.py +87 -36
- scitex/io/_load_modules/__init__.py +10 -7
- scitex/io/_load_modules/_pandas.py +6 -1
- scitex/io/_save.py +299 -1556
- scitex/io/_save_modules/__init__.py +76 -19
- scitex/io/_save_modules/_figure_utils.py +90 -0
- scitex/io/_save_modules/_image_csv.py +497 -0
- scitex/io/_save_modules/_legends.py +91 -0
- scitex/io/_save_modules/_pltz_bundle.py +356 -0
- scitex/io/_save_modules/_pltz_stx.py +536 -0
- scitex/io/_save_modules/_stx_bundle.py +104 -0
- scitex/io/_save_modules/_symlink.py +96 -0
- scitex/io/_save_modules/_yaml.py +1 -1
- scitex/io/_save_modules/_zarr.py +64 -18
- scitex/io/bundle/README.md +212 -0
- scitex/io/bundle/__init__.py +110 -0
- scitex/io/{_bundle.py → bundle/_core.py} +168 -97
- scitex/io/bundle/_nested.py +713 -0
- scitex/io/bundle/_types.py +74 -0
- scitex/io/{_zip_bundle.py → bundle/_zip.py} +93 -45
- scitex/io/utils/h5_to_zarr.py +1 -1
- scitex/logging/__init__.py +108 -13
- scitex/logging/_errors.py +508 -0
- scitex/logging/_formatters.py +30 -6
- scitex/logging/_warnings.py +261 -0
- scitex/plt/__init__.py +4 -1
- scitex/plt/_figrecipe.py +236 -0
- scitex/plt/_subplots/_AxisWrapper.py +6 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
- scitex/plt/_subplots/_FigWrapper.py +15 -0
- scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
- scitex/plt/_subplots/_export_as_csv.py +11 -0
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
- scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
- scitex/plt/_subplots/_fonts.py +71 -0
- scitex/plt/_subplots/_mm_layout.py +282 -0
- scitex/plt/gallery/__init__.py +99 -2
- scitex/plt/styles/_plot_postprocess.py +3 -1
- scitex/plt/utils/_configure_mpl.py +16 -19
- scitex/repro/_RandomStateManager.py +13 -8
- scitex/resource/__init__.py +19 -1
- scitex/resource/_utils/_get_env_info.py +13 -25
- scitex/schema/__init__.py +149 -160
- scitex/schema/_encoding.py +273 -0
- scitex/schema/_figure_elements.py +406 -0
- scitex/schema/_theme.py +360 -0
- scitex/schema/_validation.py +0 -98
- scitex/scholar/__init__.py +56 -14
- scitex/scholar/auth/ScholarAuthManager.py +1 -1
- scitex/scholar/auth/__init__.py +11 -2
- scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
- scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
- scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
- scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
- scitex/scholar/config/ScholarConfig.py +1 -1
- scitex/scholar/core/Scholar.py +1 -1
- scitex/session/_decorator.py +18 -16
- scitex/session/_lifecycle.py +9 -11
- scitex/session/template.py +9 -8
- scitex/sh/test_sh.py +72 -0
- scitex/sh/test_sh_simple.py +61 -0
- scitex/stats/__init__.py +221 -97
- scitex/stats/_schema.py +21 -22
- scitex/stats/descriptive/_circular.py +212 -351
- scitex/stats/descriptive/_describe.py +81 -132
- scitex/stats/descriptive/_nan.py +205 -433
- scitex/stats/descriptive/_real.py +127 -141
- scitex/str/_format_plot_text.py +5 -5
- scitex/str/_latex.py +26 -84
- scitex/str/_latex_fallback.py +53 -47
- scitex/web/_search_pubmed.py +5 -4
- scitex/writer/tests/test_diff_between.py +451 -0
- scitex/writer/tests/test_document_section.py +311 -0
- scitex/writer/tests/test_document_workflow.py +393 -0
- scitex/writer/tests/test_writer.py +361 -0
- scitex/writer/tests/test_writer_integration.py +303 -0
- {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/METADATA +368 -183
- {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/RECORD +412 -97
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
- {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/WHEEL +0 -0
- {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/entry_points.txt +0 -0
- {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_fig/_utils/_plot_layout.py
|
|
4
|
+
|
|
5
|
+
"""Blueprint-style visualization for FTS layout and coordinate system.
|
|
6
|
+
|
|
7
|
+
Provides architectural drawing style visualizations with:
|
|
8
|
+
- Canvas boundaries with dimension annotations
|
|
9
|
+
- Element bounding boxes with labels
|
|
10
|
+
- Rulers (horizontal and vertical)
|
|
11
|
+
- Grid lines
|
|
12
|
+
- Before/after comparison for auto-crop
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
from scitex.fts._fig._utils import plot_layout, plot_auto_crop_comparison
|
|
16
|
+
|
|
17
|
+
# Single layout visualization
|
|
18
|
+
fig, ax = plot_layout(elements, canvas_size, title="My Figure")
|
|
19
|
+
|
|
20
|
+
# Before/after auto-crop comparison
|
|
21
|
+
fig = plot_auto_crop_comparison(elements_before, elements_after,
|
|
22
|
+
size_before, size_after)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
26
|
+
|
|
27
|
+
import matplotlib.patches as mpatches
|
|
28
|
+
import matplotlib.pyplot as plt
|
|
29
|
+
from matplotlib.axes import Axes
|
|
30
|
+
from matplotlib.figure import Figure
|
|
31
|
+
|
|
32
|
+
from ._calc_bounds import element_bounds
|
|
33
|
+
from ._normalize import normalize_size
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"plot_layout",
|
|
37
|
+
"plot_auto_crop_comparison",
|
|
38
|
+
"BLUEPRINT_STYLE",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
# Blueprint color scheme
|
|
42
|
+
BLUEPRINT_STYLE = {
|
|
43
|
+
"bg_color": "#1a2744", # Dark blue background
|
|
44
|
+
"grid_color": "#2a3f5f", # Subtle grid
|
|
45
|
+
"canvas_color": "#ffffff", # White canvas
|
|
46
|
+
"canvas_edge": "#4a90d9", # Blue canvas border
|
|
47
|
+
"element_fill": "#e8f4fc", # Light blue element fill
|
|
48
|
+
"element_edge": "#2171b5", # Blue element border
|
|
49
|
+
"ruler_color": "#ff6b35", # Orange rulers
|
|
50
|
+
"text_color": "#333333", # Dark text
|
|
51
|
+
"dimension_color": "#d62728", # Red dimensions
|
|
52
|
+
"origin_color": "#2ca02c", # Green origin marker
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def plot_layout(
|
|
57
|
+
elements: List[Dict[str, Any]],
|
|
58
|
+
canvas_size: Dict[str, float],
|
|
59
|
+
title: str = "Layout",
|
|
60
|
+
ax: Optional[Axes] = None,
|
|
61
|
+
show_rulers: bool = True,
|
|
62
|
+
show_grid: bool = True,
|
|
63
|
+
show_dimensions: bool = True,
|
|
64
|
+
show_origin: bool = True,
|
|
65
|
+
style: Optional[Dict[str, str]] = None,
|
|
66
|
+
) -> Tuple[Figure, Axes]:
|
|
67
|
+
"""Plot layout with blueprint-style visualization.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
elements: List of element specifications
|
|
71
|
+
canvas_size: Canvas size {"width_mm", "height_mm"}
|
|
72
|
+
title: Plot title
|
|
73
|
+
ax: Existing axes to plot on (creates new if None)
|
|
74
|
+
show_rulers: Show ruler markings
|
|
75
|
+
show_grid: Show background grid
|
|
76
|
+
show_dimensions: Show dimension annotations
|
|
77
|
+
show_origin: Show origin marker
|
|
78
|
+
style: Custom style dict (uses BLUEPRINT_STYLE if None)
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Tuple of (Figure, Axes)
|
|
82
|
+
"""
|
|
83
|
+
s = style or BLUEPRINT_STYLE
|
|
84
|
+
size = normalize_size(canvas_size)
|
|
85
|
+
w, h = size["width_mm"], size["height_mm"]
|
|
86
|
+
|
|
87
|
+
# Create figure if needed
|
|
88
|
+
if ax is None:
|
|
89
|
+
# Add space for rulers
|
|
90
|
+
ruler_margin = 15 if show_rulers else 5
|
|
91
|
+
fig_w = (w + ruler_margin * 2) / 25.4 # Convert mm to inches
|
|
92
|
+
fig_h = (h + ruler_margin * 2) / 25.4
|
|
93
|
+
fig, ax = plt.subplots(figsize=(fig_w * 1.5, fig_h * 1.5))
|
|
94
|
+
else:
|
|
95
|
+
fig = ax.figure
|
|
96
|
+
|
|
97
|
+
# Set background
|
|
98
|
+
ax.set_facecolor(s["bg_color"])
|
|
99
|
+
|
|
100
|
+
# Draw grid
|
|
101
|
+
if show_grid:
|
|
102
|
+
_draw_grid(ax, w, h, s)
|
|
103
|
+
|
|
104
|
+
# Draw canvas
|
|
105
|
+
_draw_canvas(ax, w, h, s)
|
|
106
|
+
|
|
107
|
+
# Draw origin marker
|
|
108
|
+
if show_origin:
|
|
109
|
+
_draw_origin(ax, s)
|
|
110
|
+
|
|
111
|
+
# Draw elements
|
|
112
|
+
for i, elem in enumerate(elements):
|
|
113
|
+
_draw_element(ax, elem, i, s, show_dimensions)
|
|
114
|
+
|
|
115
|
+
# Draw rulers
|
|
116
|
+
if show_rulers:
|
|
117
|
+
_draw_rulers(ax, w, h, s)
|
|
118
|
+
|
|
119
|
+
# Draw canvas dimensions
|
|
120
|
+
if show_dimensions:
|
|
121
|
+
_draw_canvas_dimensions(ax, w, h, s)
|
|
122
|
+
|
|
123
|
+
# Set axis properties
|
|
124
|
+
margin = 20 if show_rulers else 5
|
|
125
|
+
ax.set_xlim(-margin, w + margin)
|
|
126
|
+
ax.set_ylim(h + margin, -margin) # Inverted Y (origin at top-left)
|
|
127
|
+
ax.set_aspect("equal")
|
|
128
|
+
ax.set_title(title, fontsize=12, fontweight="bold", color=s["text_color"])
|
|
129
|
+
ax.axis("off")
|
|
130
|
+
|
|
131
|
+
return fig, ax
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def plot_auto_crop_comparison(
|
|
135
|
+
elements_before: List[Dict[str, Any]],
|
|
136
|
+
elements_after: List[Dict[str, Any]],
|
|
137
|
+
size_before: Dict[str, float],
|
|
138
|
+
size_after: Dict[str, float],
|
|
139
|
+
title: str = "Auto-Crop Comparison",
|
|
140
|
+
style: Optional[Dict[str, str]] = None,
|
|
141
|
+
) -> Figure:
|
|
142
|
+
"""Plot before/after comparison for auto-crop.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
elements_before: Elements before auto-crop
|
|
146
|
+
elements_after: Elements after auto-crop
|
|
147
|
+
size_before: Canvas size before
|
|
148
|
+
size_after: Canvas size after
|
|
149
|
+
title: Overall title
|
|
150
|
+
style: Custom style dict
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Figure with side-by-side comparison
|
|
154
|
+
"""
|
|
155
|
+
s = style or BLUEPRINT_STYLE
|
|
156
|
+
|
|
157
|
+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 8))
|
|
158
|
+
fig.suptitle(title, fontsize=14, fontweight="bold")
|
|
159
|
+
|
|
160
|
+
# Before
|
|
161
|
+
plot_layout(
|
|
162
|
+
elements_before,
|
|
163
|
+
size_before,
|
|
164
|
+
title="Before Auto-Crop",
|
|
165
|
+
ax=ax1,
|
|
166
|
+
style=s,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# After
|
|
170
|
+
plot_layout(
|
|
171
|
+
elements_after,
|
|
172
|
+
size_after,
|
|
173
|
+
title="After Auto-Crop",
|
|
174
|
+
ax=ax2,
|
|
175
|
+
style=s,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Add size annotations
|
|
179
|
+
sb = normalize_size(size_before)
|
|
180
|
+
sa = normalize_size(size_after)
|
|
181
|
+
ax1.text(
|
|
182
|
+
0.5,
|
|
183
|
+
-0.05,
|
|
184
|
+
f"Canvas: {sb['width_mm']:.1f} x {sb['height_mm']:.1f} mm",
|
|
185
|
+
transform=ax1.transAxes,
|
|
186
|
+
ha="center",
|
|
187
|
+
fontsize=10,
|
|
188
|
+
)
|
|
189
|
+
ax2.text(
|
|
190
|
+
0.5,
|
|
191
|
+
-0.05,
|
|
192
|
+
f"Canvas: {sa['width_mm']:.1f} x {sa['height_mm']:.1f} mm",
|
|
193
|
+
transform=ax2.transAxes,
|
|
194
|
+
ha="center",
|
|
195
|
+
fontsize=10,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
plt.tight_layout()
|
|
199
|
+
return fig
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _draw_grid(ax: Axes, w: float, h: float, s: Dict[str, str]) -> None:
|
|
203
|
+
"""Draw background grid."""
|
|
204
|
+
# Major grid every 10mm
|
|
205
|
+
for x in range(0, int(w) + 1, 10):
|
|
206
|
+
ax.axvline(x, color=s["grid_color"], linewidth=0.5, alpha=0.5)
|
|
207
|
+
for y in range(0, int(h) + 1, 10):
|
|
208
|
+
ax.axhline(y, color=s["grid_color"], linewidth=0.5, alpha=0.5)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _draw_canvas(ax: Axes, w: float, h: float, s: Dict[str, str]) -> None:
|
|
212
|
+
"""Draw canvas rectangle."""
|
|
213
|
+
rect = mpatches.Rectangle(
|
|
214
|
+
(0, 0),
|
|
215
|
+
w,
|
|
216
|
+
h,
|
|
217
|
+
linewidth=2,
|
|
218
|
+
edgecolor=s["canvas_edge"],
|
|
219
|
+
facecolor=s["canvas_color"],
|
|
220
|
+
alpha=0.95,
|
|
221
|
+
zorder=1,
|
|
222
|
+
)
|
|
223
|
+
ax.add_patch(rect)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _draw_origin(ax: Axes, s: Dict[str, str]) -> None:
|
|
227
|
+
"""Draw origin marker at (0,0)."""
|
|
228
|
+
# Origin cross
|
|
229
|
+
ax.plot([-3, 3], [0, 0], color=s["origin_color"], linewidth=2, zorder=10)
|
|
230
|
+
ax.plot([0, 0], [-3, 3], color=s["origin_color"], linewidth=2, zorder=10)
|
|
231
|
+
# Origin label
|
|
232
|
+
ax.text(
|
|
233
|
+
-5,
|
|
234
|
+
-5,
|
|
235
|
+
"(0,0)",
|
|
236
|
+
fontsize=8,
|
|
237
|
+
color=s["origin_color"],
|
|
238
|
+
ha="right",
|
|
239
|
+
va="bottom",
|
|
240
|
+
fontweight="bold",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _draw_element(
|
|
245
|
+
ax: Axes,
|
|
246
|
+
elem: Dict[str, Any],
|
|
247
|
+
index: int,
|
|
248
|
+
s: Dict[str, str],
|
|
249
|
+
show_dimensions: bool,
|
|
250
|
+
) -> None:
|
|
251
|
+
"""Draw a single element with bounding box."""
|
|
252
|
+
bounds = element_bounds(elem)
|
|
253
|
+
x, y = bounds["x_mm"], bounds["y_mm"]
|
|
254
|
+
w, h = bounds["width_mm"], bounds["height_mm"]
|
|
255
|
+
|
|
256
|
+
# Element rectangle
|
|
257
|
+
rect = mpatches.Rectangle(
|
|
258
|
+
(x, y),
|
|
259
|
+
w,
|
|
260
|
+
h,
|
|
261
|
+
linewidth=1.5,
|
|
262
|
+
edgecolor=s["element_edge"],
|
|
263
|
+
facecolor=s["element_fill"],
|
|
264
|
+
alpha=0.8,
|
|
265
|
+
zorder=5,
|
|
266
|
+
)
|
|
267
|
+
ax.add_patch(rect)
|
|
268
|
+
|
|
269
|
+
# Element label
|
|
270
|
+
elem_id = elem.get("id", f"E{index}")
|
|
271
|
+
elem_type = elem.get("type", "unknown")
|
|
272
|
+
label = f"{elem_id}\n({elem_type})"
|
|
273
|
+
ax.text(
|
|
274
|
+
x + w / 2,
|
|
275
|
+
y + h / 2,
|
|
276
|
+
label,
|
|
277
|
+
ha="center",
|
|
278
|
+
va="center",
|
|
279
|
+
fontsize=9,
|
|
280
|
+
color=s["text_color"],
|
|
281
|
+
fontweight="bold",
|
|
282
|
+
zorder=6,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Position annotation
|
|
286
|
+
if show_dimensions:
|
|
287
|
+
ax.text(
|
|
288
|
+
x,
|
|
289
|
+
y - 2,
|
|
290
|
+
f"({x:.0f}, {y:.0f})",
|
|
291
|
+
fontsize=7,
|
|
292
|
+
color=s["dimension_color"],
|
|
293
|
+
ha="left",
|
|
294
|
+
va="bottom",
|
|
295
|
+
)
|
|
296
|
+
# Size annotation
|
|
297
|
+
ax.text(
|
|
298
|
+
x + w,
|
|
299
|
+
y + h + 2,
|
|
300
|
+
f"{w:.0f}x{h:.0f}",
|
|
301
|
+
fontsize=7,
|
|
302
|
+
color=s["dimension_color"],
|
|
303
|
+
ha="right",
|
|
304
|
+
va="top",
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _draw_rulers(ax: Axes, w: float, h: float, s: Dict[str, str]) -> None:
|
|
309
|
+
"""Draw rulers along edges."""
|
|
310
|
+
ruler_offset = -12
|
|
311
|
+
|
|
312
|
+
# Horizontal ruler (top)
|
|
313
|
+
ax.plot([0, w], [ruler_offset, ruler_offset], color=s["ruler_color"], linewidth=1)
|
|
314
|
+
for x in range(0, int(w) + 1, 10):
|
|
315
|
+
tick_len = 3 if x % 50 == 0 else 1.5
|
|
316
|
+
ax.plot(
|
|
317
|
+
[x, x],
|
|
318
|
+
[ruler_offset, ruler_offset + tick_len],
|
|
319
|
+
color=s["ruler_color"],
|
|
320
|
+
linewidth=1,
|
|
321
|
+
)
|
|
322
|
+
if x % 50 == 0:
|
|
323
|
+
ax.text(
|
|
324
|
+
x,
|
|
325
|
+
ruler_offset - 2,
|
|
326
|
+
str(x),
|
|
327
|
+
fontsize=7,
|
|
328
|
+
ha="center",
|
|
329
|
+
va="bottom",
|
|
330
|
+
color=s["ruler_color"],
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# Vertical ruler (left)
|
|
334
|
+
ax.plot([ruler_offset, ruler_offset], [0, h], color=s["ruler_color"], linewidth=1)
|
|
335
|
+
for y in range(0, int(h) + 1, 10):
|
|
336
|
+
tick_len = 3 if y % 50 == 0 else 1.5
|
|
337
|
+
ax.plot(
|
|
338
|
+
[ruler_offset, ruler_offset + tick_len],
|
|
339
|
+
[y, y],
|
|
340
|
+
color=s["ruler_color"],
|
|
341
|
+
linewidth=1,
|
|
342
|
+
)
|
|
343
|
+
if y % 50 == 0:
|
|
344
|
+
ax.text(
|
|
345
|
+
ruler_offset - 2,
|
|
346
|
+
y,
|
|
347
|
+
str(y),
|
|
348
|
+
fontsize=7,
|
|
349
|
+
ha="right",
|
|
350
|
+
va="center",
|
|
351
|
+
color=s["ruler_color"],
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _draw_canvas_dimensions(ax: Axes, w: float, h: float, s: Dict[str, str]) -> None:
|
|
356
|
+
"""Draw canvas dimension annotations."""
|
|
357
|
+
# Width dimension (bottom)
|
|
358
|
+
y_pos = h + 8
|
|
359
|
+
ax.annotate(
|
|
360
|
+
"",
|
|
361
|
+
xy=(w, y_pos),
|
|
362
|
+
xytext=(0, y_pos),
|
|
363
|
+
arrowprops=dict(arrowstyle="<->", color=s["dimension_color"], lw=1.5),
|
|
364
|
+
)
|
|
365
|
+
ax.text(
|
|
366
|
+
w / 2,
|
|
367
|
+
y_pos + 3,
|
|
368
|
+
f"{w:.0f} mm",
|
|
369
|
+
ha="center",
|
|
370
|
+
va="bottom",
|
|
371
|
+
fontsize=9,
|
|
372
|
+
color=s["dimension_color"],
|
|
373
|
+
fontweight="bold",
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Height dimension (right)
|
|
377
|
+
x_pos = w + 8
|
|
378
|
+
ax.annotate(
|
|
379
|
+
"",
|
|
380
|
+
xy=(x_pos, h),
|
|
381
|
+
xytext=(x_pos, 0),
|
|
382
|
+
arrowprops=dict(arrowstyle="<->", color=s["dimension_color"], lw=1.5),
|
|
383
|
+
)
|
|
384
|
+
ax.text(
|
|
385
|
+
x_pos + 3,
|
|
386
|
+
h / 2,
|
|
387
|
+
f"{h:.0f} mm",
|
|
388
|
+
ha="left",
|
|
389
|
+
va="center",
|
|
390
|
+
fontsize=9,
|
|
391
|
+
color=s["dimension_color"],
|
|
392
|
+
fontweight="bold",
|
|
393
|
+
rotation=90,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
# EOF
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# File: ./src/scitex/vis/utils/validate.py
|
|
3
|
+
"""Validation utilities for figure JSON specifications."""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def validate_json_structure(fig_json: Dict[str, Any]) -> bool:
|
|
9
|
+
"""
|
|
10
|
+
Validate basic JSON structure requirements.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
fig_json : Dict[str, Any]
|
|
15
|
+
Figure JSON to validate
|
|
16
|
+
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
bool
|
|
20
|
+
True if valid, raises ValueError otherwise
|
|
21
|
+
|
|
22
|
+
Raises
|
|
23
|
+
------
|
|
24
|
+
ValueError
|
|
25
|
+
If JSON structure is invalid
|
|
26
|
+
"""
|
|
27
|
+
# Required fields
|
|
28
|
+
required_fields = ["width_mm", "height_mm"]
|
|
29
|
+
|
|
30
|
+
for field in required_fields:
|
|
31
|
+
if field not in fig_json:
|
|
32
|
+
raise ValueError(f"Missing required field: {field}")
|
|
33
|
+
|
|
34
|
+
# Type validation
|
|
35
|
+
if not isinstance(fig_json["width_mm"], (int, float)):
|
|
36
|
+
raise ValueError("width_mm must be a number")
|
|
37
|
+
|
|
38
|
+
if not isinstance(fig_json["height_mm"], (int, float)):
|
|
39
|
+
raise ValueError("height_mm must be a number")
|
|
40
|
+
|
|
41
|
+
# Value validation
|
|
42
|
+
if fig_json["width_mm"] <= 0:
|
|
43
|
+
raise ValueError(f"width_mm must be positive, got {fig_json['width_mm']}")
|
|
44
|
+
|
|
45
|
+
if fig_json["height_mm"] <= 0:
|
|
46
|
+
raise ValueError(f"height_mm must be positive, got {fig_json['height_mm']}")
|
|
47
|
+
|
|
48
|
+
# Optional fields validation
|
|
49
|
+
if "nrows" in fig_json:
|
|
50
|
+
if not isinstance(fig_json["nrows"], int) or fig_json["nrows"] <= 0:
|
|
51
|
+
raise ValueError("nrows must be a positive integer")
|
|
52
|
+
|
|
53
|
+
if "ncols" in fig_json:
|
|
54
|
+
if not isinstance(fig_json["ncols"], int) or fig_json["ncols"] <= 0:
|
|
55
|
+
raise ValueError("ncols must be a positive integer")
|
|
56
|
+
|
|
57
|
+
if "dpi" in fig_json:
|
|
58
|
+
if not isinstance(fig_json["dpi"], int) or fig_json["dpi"] <= 0:
|
|
59
|
+
raise ValueError("dpi must be a positive integer")
|
|
60
|
+
|
|
61
|
+
if "axes" in fig_json:
|
|
62
|
+
if not isinstance(fig_json["axes"], list):
|
|
63
|
+
raise ValueError("axes must be a list")
|
|
64
|
+
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def validate_plot_data(plot_data: Dict[str, Any]) -> bool:
|
|
69
|
+
"""
|
|
70
|
+
Validate plot data contains required fields for plot type.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
plot_data : Dict[str, Any]
|
|
75
|
+
Plot configuration with data
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
bool
|
|
80
|
+
True if valid, raises ValueError otherwise
|
|
81
|
+
|
|
82
|
+
Raises
|
|
83
|
+
------
|
|
84
|
+
ValueError
|
|
85
|
+
If plot data is invalid
|
|
86
|
+
"""
|
|
87
|
+
if "plot_type" not in plot_data:
|
|
88
|
+
raise ValueError("Plot must specify plot_type")
|
|
89
|
+
|
|
90
|
+
plot_type = plot_data["plot_type"]
|
|
91
|
+
data = plot_data.get("data", {})
|
|
92
|
+
|
|
93
|
+
# Type-specific requirements
|
|
94
|
+
if plot_type in ["line", "scatter", "errorbar"]:
|
|
95
|
+
if "x" not in data or "y" not in data:
|
|
96
|
+
raise ValueError(f"{plot_type} requires 'x' and 'y' data")
|
|
97
|
+
|
|
98
|
+
# Validate arrays have same length
|
|
99
|
+
x_len = len(data["x"]) if hasattr(data["x"], "__len__") else 1
|
|
100
|
+
y_len = len(data["y"]) if hasattr(data["y"], "__len__") else 1
|
|
101
|
+
|
|
102
|
+
if x_len != y_len:
|
|
103
|
+
raise ValueError(f"x and y data must have same length: {x_len} != {y_len}")
|
|
104
|
+
|
|
105
|
+
elif plot_type in ["bar", "barh"]:
|
|
106
|
+
if "x" not in data:
|
|
107
|
+
raise ValueError(f"{plot_type} requires 'x' data")
|
|
108
|
+
|
|
109
|
+
if "height" not in data and "y" not in data:
|
|
110
|
+
raise ValueError(f"{plot_type} requires 'height' or 'y' data")
|
|
111
|
+
|
|
112
|
+
elif plot_type == "hist":
|
|
113
|
+
if "x" not in data:
|
|
114
|
+
raise ValueError("hist requires 'x' data")
|
|
115
|
+
|
|
116
|
+
elif plot_type in ["heatmap", "imshow"]:
|
|
117
|
+
if "z" not in data and "img" not in data:
|
|
118
|
+
raise ValueError(f"{plot_type} requires 'z' or 'img' data")
|
|
119
|
+
|
|
120
|
+
elif plot_type in ["contour", "contourf"]:
|
|
121
|
+
if "x" not in data or "y" not in data or "z" not in data:
|
|
122
|
+
raise ValueError(f"{plot_type} requires 'x', 'y', and 'z' data")
|
|
123
|
+
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def check_schema_version(fig_json: Dict[str, Any]) -> str:
|
|
128
|
+
"""
|
|
129
|
+
Check and return schema version.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
fig_json : Dict[str, Any]
|
|
134
|
+
Figure JSON
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
str
|
|
139
|
+
Schema version (defaults to "1.0.0" if not specified)
|
|
140
|
+
"""
|
|
141
|
+
return fig_json.get("schema_version", "1.0.0")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def validate_color(color: str) -> bool:
|
|
145
|
+
"""
|
|
146
|
+
Validate color specification.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
color : str
|
|
151
|
+
Color specification (name, hex, rgb, etc.)
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
bool
|
|
156
|
+
True if valid, raises ValueError otherwise
|
|
157
|
+
"""
|
|
158
|
+
if not isinstance(color, str):
|
|
159
|
+
raise ValueError(f"Color must be a string, got {type(color)}")
|
|
160
|
+
|
|
161
|
+
# Basic validation - matplotlib will do deeper validation
|
|
162
|
+
if not color:
|
|
163
|
+
raise ValueError("Color cannot be empty string")
|
|
164
|
+
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def validate_axes_layout(nrows: int, ncols: int, num_axes: int) -> bool:
|
|
169
|
+
"""
|
|
170
|
+
Validate axes layout is consistent.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
nrows : int
|
|
175
|
+
Number of rows
|
|
176
|
+
ncols : int
|
|
177
|
+
Number of columns
|
|
178
|
+
num_axes : int
|
|
179
|
+
Number of axes configurations
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
bool
|
|
184
|
+
True if valid, raises ValueError otherwise
|
|
185
|
+
"""
|
|
186
|
+
max_axes = nrows * ncols
|
|
187
|
+
|
|
188
|
+
if num_axes > max_axes:
|
|
189
|
+
raise ValueError(
|
|
190
|
+
f"Too many axes: {num_axes} axes for {nrows}x{ncols} layout "
|
|
191
|
+
f"(max {max_axes})"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# EOF
|