scitex 2.8.1__py3-none-any.whl → 2.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scitex/__init__.py +15 -7
- scitex/__version__.py +1 -2
- scitex/_install_guide.py +250 -0
- scitex/_optional_deps.py +206 -39
- scitex/ai/_gen_ai/_Groq.py +2 -4
- scitex/ai/_gen_ai/_OpenAI.py +5 -2
- scitex/ai/_gen_ai/_Perplexity.py +20 -6
- scitex/audio/__init__.py +24 -15
- scitex/audio/_cross_process_lock.py +139 -0
- scitex/audio/_mcp_handlers.py +256 -0
- scitex/audio/_mcp_tool_schemas.py +203 -0
- scitex/audio/engines/elevenlabs_engine.py +5 -2
- scitex/audio/mcp_server.py +98 -457
- scitex/bridge/__init__.py +30 -19
- scitex/bridge/_figrecipe.py +245 -0
- scitex/bridge/_helpers.py +2 -1
- scitex/bridge/_plt_vis.py +23 -10
- scitex/bridge/_stats_plt.py +18 -5
- scitex/bridge/_stats_vis.py +16 -2
- scitex/browser/__init__.py +84 -44
- scitex/browser/automation/__init__.py +5 -1
- scitex/browser/core/BrowserMixin.py +17 -4
- scitex/browser/core/__init__.py +11 -2
- scitex/browser/remote/CaptchaHandler.py +1 -1
- scitex/browser/remote/ZenRowsAPIClient.py +1 -1
- scitex/capture/grid.py +487 -0
- scitex/capture/mcp_handlers.py +401 -0
- scitex/capture/mcp_tool_defs.py +192 -0
- scitex/capture/mcp_tools.py +241 -0
- scitex/capture/mcp_utils.py +30 -0
- scitex/cli/convert.py +421 -0
- scitex/cli/main.py +6 -4
- scitex/datetime/__init__.py +46 -0
- scitex/datetime/_linspace.py +100 -0
- scitex/datetime/_normalize_timestamp.py +306 -0
- scitex/db/_delete_duplicates.py +4 -4
- scitex/db/_sqlite3/_delete_duplicates.py +11 -2
- scitex/dev/plt/__init__.py +61 -62
- scitex/dev/plt/demo_plotters/__init__.py +0 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
- scitex/dev/plt/mpl/get_dir_ax.py +46 -0
- scitex/dev/plt/mpl/get_signatures.py +176 -0
- scitex/dev/plt/mpl/get_signatures_details.py +522 -0
- scitex/dict/_pop_keys.py +1 -7
- scitex/dsp/__init__.py +15 -10
- scitex/dsp/add_noise.py +5 -2
- scitex/dsp/example.py +35 -22
- scitex/dsp/filt.py +8 -3
- scitex/dsp/reference.py +3 -2
- scitex/dsp/utils/__init__.py +2 -1
- scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
- scitex/dt/__init__.py +39 -2
- scitex/errors.py +82 -521
- scitex/fig/__init__.py +4 -4
- scitex/fig/editor/edit/panel_loader.py +1 -1
- scitex/fig/io/_bundle.py +7 -7
- scitex/fts/README.md +262 -0
- scitex/fts/TODO.md +66 -0
- scitex/fts/__init__.py +90 -0
- scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
- scitex/fts/_bundle/_FTS.py +657 -0
- scitex/fts/_bundle/__init__.py +38 -0
- scitex/fts/_bundle/_children.py +216 -0
- scitex/fts/_bundle/_conversion/__init__.py +15 -0
- scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
- scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
- scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
- scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
- scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
- scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
- scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
- scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
- scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
- scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
- scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
- scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
- scitex/fts/_bundle/_extractors/__init__.py +32 -0
- scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
- scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
- scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
- scitex/fts/_bundle/_loader.py +134 -0
- scitex/fts/_bundle/_mpl_helpers.py +389 -0
- scitex/fts/_bundle/_saver.py +269 -0
- scitex/fts/_bundle/_storage.py +200 -0
- scitex/fts/_bundle/_utils/__init__.py +55 -0
- scitex/fts/_bundle/_utils/_const.py +26 -0
- scitex/fts/_bundle/_utils/_errors.py +73 -0
- scitex/fts/_bundle/_utils/_generate.py +21 -0
- scitex/fts/_bundle/_utils/_types.py +76 -0
- scitex/fts/_bundle/_validation.py +434 -0
- scitex/fts/_bundle/_zipbundle.py +165 -0
- scitex/fts/_fig/__init__.py +22 -0
- scitex/fts/_fig/_backend/__init__.py +53 -0
- scitex/fts/_fig/_backend/_export.py +165 -0
- scitex/fts/_fig/_backend/_parser.py +188 -0
- scitex/fts/_fig/_backend/_render.py +538 -0
- scitex/fts/_fig/_composite.py +345 -0
- scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
- scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_fig/_dataclasses/__init__.py +47 -0
- scitex/fts/_fig/_editor/__init__.py +14 -0
- scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
- scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
- scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
- scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
- scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
- scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
- scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
- scitex/fts/_fig/_editor/_defaults.py +300 -0
- scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
- scitex/fts/_fig/_models/_Annotations.py +115 -0
- scitex/fts/_fig/_models/_Axes.py +152 -0
- scitex/fts/_fig/_models/_Figure.py +138 -0
- scitex/fts/_fig/_models/_Guides.py +104 -0
- scitex/fts/_fig/_models/_Plot.py +123 -0
- scitex/fts/_fig/_models/_Styles.py +245 -0
- scitex/fts/_fig/_models/__init__.py +80 -0
- scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
- scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
- scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
- scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_fig/_utils/__init__.py +129 -0
- scitex/fts/_fig/_utils/_auto_layout.py +127 -0
- scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
- scitex/fts/_fig/_utils/_const_sizes.py +48 -0
- scitex/fts/_fig/_utils/_convert_coords.py +77 -0
- scitex/fts/_fig/_utils/_get_template.py +178 -0
- scitex/fts/_fig/_utils/_normalize.py +73 -0
- scitex/fts/_fig/_utils/_plot_layout.py +397 -0
- scitex/fts/_fig/_utils/_validate.py +197 -0
- scitex/fts/_kinds/__init__.py +45 -0
- scitex/fts/_kinds/_figure/__init__.py +19 -0
- scitex/fts/_kinds/_figure/_composite.py +345 -0
- scitex/fts/_kinds/_plot/__init__.py +25 -0
- scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
- scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
- scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
- scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
- scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
- scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
- scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
- scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
- scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
- scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
- scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
- scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
- scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
- scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
- scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
- scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
- scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
- scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
- scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
- scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
- scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
- scitex/fts/_kinds/_shape/__init__.py +141 -0
- scitex/fts/_kinds/_stats/__init__.py +56 -0
- scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_kinds/_table/__init__.py +72 -0
- scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
- scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
- scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
- scitex/fts/_kinds/_table/_latex/_export.py +279 -0
- scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
- scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
- scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
- scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
- scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
- scitex/fts/_kinds/_text/__init__.py +77 -0
- scitex/fts/_schemas/data_info.schema.json +75 -0
- scitex/fts/_schemas/encoding.schema.json +90 -0
- scitex/fts/_schemas/node.schema.json +145 -0
- scitex/fts/_schemas/render_manifest.schema.json +62 -0
- scitex/fts/_schemas/stats.schema.json +132 -0
- scitex/fts/_schemas/theme.schema.json +141 -0
- scitex/fts/_stats/__init__.py +48 -0
- scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_tables/__init__.py +65 -0
- scitex/fts/_tables/_latex/__init__.py +93 -0
- scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
- scitex/fts/_tables/_latex/_editor/_app.py +725 -0
- scitex/fts/_tables/_latex/_export.py +279 -0
- scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
- scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
- scitex/fts/_tables/_latex/_table_exporter.py +362 -0
- scitex/fts/_tables/_latex/_utils.py +369 -0
- scitex/fts/_tables/_latex/_validator.py +445 -0
- scitex/gen/__init__.py +66 -25
- scitex/gen/misc.py +28 -0
- scitex/io/__init__.py +47 -32
- scitex/io/_load.py +87 -36
- scitex/io/_load_modules/__init__.py +10 -7
- scitex/io/_load_modules/_pandas.py +6 -1
- scitex/io/_save.py +299 -1556
- scitex/io/_save_modules/__init__.py +76 -19
- scitex/io/_save_modules/_figure_utils.py +90 -0
- scitex/io/_save_modules/_image_csv.py +497 -0
- scitex/io/_save_modules/_legends.py +91 -0
- scitex/io/_save_modules/_pltz_bundle.py +356 -0
- scitex/io/_save_modules/_pltz_stx.py +536 -0
- scitex/io/_save_modules/_stx_bundle.py +104 -0
- scitex/io/_save_modules/_symlink.py +96 -0
- scitex/io/_save_modules/_yaml.py +1 -1
- scitex/io/_save_modules/_zarr.py +64 -18
- scitex/io/bundle/README.md +212 -0
- scitex/io/bundle/__init__.py +110 -0
- scitex/io/{_bundle.py → bundle/_core.py} +168 -97
- scitex/io/bundle/_nested.py +713 -0
- scitex/io/bundle/_types.py +74 -0
- scitex/io/{_zip_bundle.py → bundle/_zip.py} +93 -45
- scitex/io/utils/h5_to_zarr.py +1 -1
- scitex/logging/__init__.py +108 -13
- scitex/logging/_errors.py +508 -0
- scitex/logging/_formatters.py +30 -6
- scitex/logging/_warnings.py +261 -0
- scitex/plt/__init__.py +4 -1
- scitex/plt/_figrecipe.py +236 -0
- scitex/plt/_subplots/_AxisWrapper.py +6 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
- scitex/plt/_subplots/_FigWrapper.py +15 -0
- scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
- scitex/plt/_subplots/_export_as_csv.py +11 -0
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
- scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
- scitex/plt/_subplots/_fonts.py +71 -0
- scitex/plt/_subplots/_mm_layout.py +282 -0
- scitex/plt/gallery/__init__.py +99 -2
- scitex/plt/styles/_plot_postprocess.py +3 -1
- scitex/plt/utils/_configure_mpl.py +16 -19
- scitex/repro/_RandomStateManager.py +13 -8
- scitex/resource/__init__.py +19 -1
- scitex/resource/_utils/_get_env_info.py +13 -25
- scitex/schema/__init__.py +149 -160
- scitex/schema/_encoding.py +273 -0
- scitex/schema/_figure_elements.py +406 -0
- scitex/schema/_theme.py +360 -0
- scitex/schema/_validation.py +0 -98
- scitex/scholar/__init__.py +56 -14
- scitex/scholar/auth/ScholarAuthManager.py +1 -1
- scitex/scholar/auth/__init__.py +11 -2
- scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
- scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
- scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
- scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
- scitex/scholar/config/ScholarConfig.py +1 -1
- scitex/scholar/core/Scholar.py +1 -1
- scitex/session/_decorator.py +18 -16
- scitex/session/_lifecycle.py +9 -11
- scitex/session/template.py +9 -8
- scitex/sh/test_sh.py +72 -0
- scitex/sh/test_sh_simple.py +61 -0
- scitex/stats/__init__.py +221 -97
- scitex/stats/_schema.py +21 -22
- scitex/stats/descriptive/_circular.py +212 -351
- scitex/stats/descriptive/_describe.py +81 -132
- scitex/stats/descriptive/_nan.py +205 -433
- scitex/stats/descriptive/_real.py +127 -141
- scitex/str/_format_plot_text.py +5 -5
- scitex/str/_latex.py +26 -84
- scitex/str/_latex_fallback.py +53 -47
- scitex/web/_search_pubmed.py +5 -4
- scitex/writer/tests/test_diff_between.py +451 -0
- scitex/writer/tests/test_document_section.py +311 -0
- scitex/writer/tests/test_document_workflow.py +393 -0
- scitex/writer/tests/test_writer.py +361 -0
- scitex/writer/tests/test_writer_integration.py +303 -0
- {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/METADATA +364 -181
- {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/RECORD +412 -97
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
- {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/WHEEL +0 -0
- {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/entry_points.txt +0 -0
- {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/DataSource.py
|
|
4
|
+
|
|
5
|
+
"""DataSource - Original data source information."""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class DataSource:
|
|
13
|
+
"""Original data source information."""
|
|
14
|
+
|
|
15
|
+
path: Optional[str] = None
|
|
16
|
+
sha256: Optional[str] = None
|
|
17
|
+
created_at: Optional[str] = None
|
|
18
|
+
description: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
21
|
+
result = {}
|
|
22
|
+
if self.path:
|
|
23
|
+
result["path"] = self.path
|
|
24
|
+
if self.sha256:
|
|
25
|
+
result["sha256"] = self.sha256
|
|
26
|
+
if self.created_at:
|
|
27
|
+
result["created_at"] = self.created_at
|
|
28
|
+
if self.description:
|
|
29
|
+
result["description"] = self.description
|
|
30
|
+
return result
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_dict(cls, data: Dict[str, Any]) -> "DataSource":
|
|
34
|
+
return cls(
|
|
35
|
+
path=data.get("path"),
|
|
36
|
+
sha256=data.get("sha256"),
|
|
37
|
+
created_at=data.get("created_at"),
|
|
38
|
+
description=data.get("description"),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__all__ = ["DataSource"]
|
|
43
|
+
|
|
44
|
+
# EOF
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-21
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_dataclasses/_Node.py
|
|
4
|
+
|
|
5
|
+
"""Node - Core FTS Node model with kind-based constraints."""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Any, ClassVar, Dict, List, Optional, Set
|
|
10
|
+
|
|
11
|
+
from ._Axes import Axes
|
|
12
|
+
from ._BBox import BBox
|
|
13
|
+
from ._NodeRefs import NodeRefs
|
|
14
|
+
from ._SizeMM import SizeMM
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class TextContent:
|
|
19
|
+
"""Text content for kind=text nodes."""
|
|
20
|
+
|
|
21
|
+
content: str = ""
|
|
22
|
+
fontsize: Optional[float] = None
|
|
23
|
+
fontweight: Optional[str] = None # "normal" | "bold"
|
|
24
|
+
ha: str = "center" # "left" | "center" | "right"
|
|
25
|
+
va: str = "center" # "top" | "center" | "bottom"
|
|
26
|
+
|
|
27
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
28
|
+
result = {"content": self.content, "ha": self.ha, "va": self.va}
|
|
29
|
+
if self.fontsize is not None:
|
|
30
|
+
result["fontsize"] = self.fontsize
|
|
31
|
+
if self.fontweight is not None:
|
|
32
|
+
result["fontweight"] = self.fontweight
|
|
33
|
+
return result
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_dict(cls, data: Dict[str, Any]) -> "TextContent":
|
|
37
|
+
return cls(
|
|
38
|
+
content=data.get("content", ""),
|
|
39
|
+
fontsize=data.get("fontsize"),
|
|
40
|
+
fontweight=data.get("fontweight"),
|
|
41
|
+
ha=data.get("ha", "center"),
|
|
42
|
+
va=data.get("va", "center"),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class ShapeParams:
|
|
48
|
+
"""Shape parameters for kind=shape nodes."""
|
|
49
|
+
|
|
50
|
+
shape_type: str = "rectangle" # "rectangle" | "ellipse" | "arrow" | "line"
|
|
51
|
+
color: str = "#000000"
|
|
52
|
+
linewidth: float = 1.0
|
|
53
|
+
fill: bool = False
|
|
54
|
+
|
|
55
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
56
|
+
return {
|
|
57
|
+
"shape_type": self.shape_type,
|
|
58
|
+
"color": self.color,
|
|
59
|
+
"linewidth": self.linewidth,
|
|
60
|
+
"fill": self.fill,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ShapeParams":
|
|
65
|
+
return cls(
|
|
66
|
+
shape_type=data.get("shape_type", "rectangle"),
|
|
67
|
+
color=data.get("color", "#000000"),
|
|
68
|
+
linewidth=data.get("linewidth", 1.0),
|
|
69
|
+
fill=data.get("fill", False),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class Node:
|
|
75
|
+
"""Core FTS Node model with kind-based constraints.
|
|
76
|
+
|
|
77
|
+
The central structural element of an FTS bundle.
|
|
78
|
+
Stored in canonical/spec.json.
|
|
79
|
+
|
|
80
|
+
Kind categories:
|
|
81
|
+
- Data leaf kinds (plot, table, stats): require payload data files
|
|
82
|
+
- Annotation leaf kinds (text, shape): no payload required, params in node
|
|
83
|
+
- Image leaf kinds (image): require payload image file
|
|
84
|
+
- Composite kinds (figure): contain children, no payload
|
|
85
|
+
|
|
86
|
+
All bundles have IDENTICAL directory structure:
|
|
87
|
+
- canonical/: Source of truth (spec.json, encoding.json, theme.json, etc.)
|
|
88
|
+
- payload/: ALWAYS exists (empty for composites/annotations, populated for data/image)
|
|
89
|
+
- artifacts/: Derived files (exports/, cache/)
|
|
90
|
+
- children/: ALWAYS exists (empty for leaves, populated for composites)
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
# Required fields
|
|
94
|
+
id: str
|
|
95
|
+
kind: str # "figure" | "plot" | "table" | "stats" | "text" | "shape" | "image"
|
|
96
|
+
|
|
97
|
+
# Schema versioning for forward compatibility
|
|
98
|
+
scitex_schema: str = "scitex.fts.spec"
|
|
99
|
+
scitex_schema_version: str = "1.0.0"
|
|
100
|
+
|
|
101
|
+
# Children and layout (for composite kinds)
|
|
102
|
+
children: List[str] = field(default_factory=list)
|
|
103
|
+
layout: Optional[Dict] = None
|
|
104
|
+
|
|
105
|
+
# Payload schema (for data leaf kinds)
|
|
106
|
+
payload_schema: Optional[str] = None
|
|
107
|
+
|
|
108
|
+
# Visual properties
|
|
109
|
+
bbox_norm: BBox = field(default_factory=BBox)
|
|
110
|
+
name: Optional[str] = None
|
|
111
|
+
size_mm: Optional[SizeMM] = None
|
|
112
|
+
axes: Optional[Axes] = None
|
|
113
|
+
|
|
114
|
+
# Kind-specific content
|
|
115
|
+
text: Optional[TextContent] = None # For kind=text
|
|
116
|
+
shape: Optional[ShapeParams] = None # For kind=shape
|
|
117
|
+
|
|
118
|
+
# References and timestamps
|
|
119
|
+
refs: NodeRefs = field(default_factory=NodeRefs)
|
|
120
|
+
created_at: Optional[str] = None
|
|
121
|
+
modified_at: Optional[str] = None
|
|
122
|
+
|
|
123
|
+
# === Kind Constants ===
|
|
124
|
+
# Data leaf kinds: require payload data files, forbid children
|
|
125
|
+
DATA_LEAF_KINDS: ClassVar[Set[str]] = {"plot", "table", "stats"}
|
|
126
|
+
# Annotation leaf kinds: no payload required, forbid children
|
|
127
|
+
ANNOTATION_LEAF_KINDS: ClassVar[Set[str]] = {"text", "shape"}
|
|
128
|
+
# Image leaf kinds: require payload image file, forbid children
|
|
129
|
+
IMAGE_LEAF_KINDS: ClassVar[Set[str]] = {"image"}
|
|
130
|
+
# All leaf kinds (for convenience)
|
|
131
|
+
LEAF_KINDS: ClassVar[Set[str]] = DATA_LEAF_KINDS | ANNOTATION_LEAF_KINDS | IMAGE_LEAF_KINDS
|
|
132
|
+
# Composite kinds: allow children, forbid payload
|
|
133
|
+
COMPOSITE_KINDS: ClassVar[Set[str]] = {"figure"}
|
|
134
|
+
# All valid kinds
|
|
135
|
+
ALL_KINDS: ClassVar[Set[str]] = LEAF_KINDS | COMPOSITE_KINDS
|
|
136
|
+
|
|
137
|
+
# Payload schema -> required file mapping
|
|
138
|
+
PAYLOAD_REQUIRED_FILES: ClassVar[Dict[str, str]] = {
|
|
139
|
+
"scitex.fts.payload.plot@1": "payload/data.csv",
|
|
140
|
+
"scitex.fts.payload.table@1": "payload/table.csv",
|
|
141
|
+
"scitex.fts.payload.stats@1": "payload/stats.json",
|
|
142
|
+
"scitex.fts.payload.image@1": "payload/image.png",
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
def __post_init__(self):
|
|
146
|
+
if self.created_at is None:
|
|
147
|
+
self.created_at = datetime.utcnow().isoformat() + "Z"
|
|
148
|
+
if self.modified_at is None:
|
|
149
|
+
self.modified_at = self.created_at
|
|
150
|
+
|
|
151
|
+
# === Kind Methods ===
|
|
152
|
+
|
|
153
|
+
def is_leaf_kind(self) -> bool:
|
|
154
|
+
"""Check if this is any leaf kind (forbids children)."""
|
|
155
|
+
return self.kind in self.LEAF_KINDS
|
|
156
|
+
|
|
157
|
+
def is_data_leaf_kind(self) -> bool:
|
|
158
|
+
"""Check if this is a data leaf kind (requires payload data)."""
|
|
159
|
+
return self.kind in self.DATA_LEAF_KINDS
|
|
160
|
+
|
|
161
|
+
def is_annotation_leaf_kind(self) -> bool:
|
|
162
|
+
"""Check if this is an annotation leaf kind (no payload required)."""
|
|
163
|
+
return self.kind in self.ANNOTATION_LEAF_KINDS
|
|
164
|
+
|
|
165
|
+
def is_image_leaf_kind(self) -> bool:
|
|
166
|
+
"""Check if this is an image leaf kind (requires payload image)."""
|
|
167
|
+
return self.kind in self.IMAGE_LEAF_KINDS
|
|
168
|
+
|
|
169
|
+
def is_composite_kind(self) -> bool:
|
|
170
|
+
"""Check if this is a composite kind (allows children, forbids payload)."""
|
|
171
|
+
return self.kind in self.COMPOSITE_KINDS
|
|
172
|
+
|
|
173
|
+
def get_required_payload_file(self) -> Optional[str]:
|
|
174
|
+
"""Get required payload file path based on payload_schema."""
|
|
175
|
+
return self.PAYLOAD_REQUIRED_FILES.get(self.payload_schema)
|
|
176
|
+
|
|
177
|
+
# === Validation ===
|
|
178
|
+
|
|
179
|
+
def validate(self) -> List[str]:
|
|
180
|
+
"""Validate logical constraints (not file existence).
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
List of error messages (empty if valid)
|
|
184
|
+
"""
|
|
185
|
+
errors = []
|
|
186
|
+
|
|
187
|
+
# Check: kind is valid
|
|
188
|
+
if self.kind not in self.ALL_KINDS:
|
|
189
|
+
errors.append(f"Unknown kind: {self.kind}. Valid kinds: {sorted(self.ALL_KINDS)}")
|
|
190
|
+
return errors # Early return - other checks don't make sense
|
|
191
|
+
|
|
192
|
+
# Check: children list has no duplicates
|
|
193
|
+
if len(self.children) != len(set(self.children)):
|
|
194
|
+
errors.append("children list has duplicates")
|
|
195
|
+
|
|
196
|
+
if self.is_leaf_kind():
|
|
197
|
+
# All leaf kinds: children must be empty
|
|
198
|
+
if self.children:
|
|
199
|
+
errors.append(f"kind={self.kind} cannot have children")
|
|
200
|
+
|
|
201
|
+
# Data leaf kinds: payload_schema is optional but recommended
|
|
202
|
+
# Annotation leaf kinds: should not have payload_schema
|
|
203
|
+
if self.is_annotation_leaf_kind() and self.payload_schema:
|
|
204
|
+
errors.append(f"kind={self.kind} should not have payload_schema")
|
|
205
|
+
|
|
206
|
+
elif self.is_composite_kind():
|
|
207
|
+
# Composite kinds: payload_schema must be None
|
|
208
|
+
if self.payload_schema:
|
|
209
|
+
errors.append(f"kind={self.kind} should not have payload_schema")
|
|
210
|
+
|
|
211
|
+
# Validate layout if present
|
|
212
|
+
if self.layout:
|
|
213
|
+
panels = self.layout.get("panels", [])
|
|
214
|
+
panel_children = [p.get("child") for p in panels]
|
|
215
|
+
|
|
216
|
+
# Check: panel child references must be subset of children
|
|
217
|
+
for child_ref in panel_children:
|
|
218
|
+
if child_ref not in self.children:
|
|
219
|
+
errors.append(f"layout.panels references unknown child: {child_ref}")
|
|
220
|
+
|
|
221
|
+
# Check: no duplicate panel child references
|
|
222
|
+
if len(panel_children) != len(set(panel_children)):
|
|
223
|
+
errors.append("layout.panels has duplicate child references")
|
|
224
|
+
|
|
225
|
+
return errors
|
|
226
|
+
|
|
227
|
+
# === Serialization ===
|
|
228
|
+
|
|
229
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
230
|
+
"""Convert to dictionary for JSON serialization."""
|
|
231
|
+
result = {
|
|
232
|
+
"id": self.id,
|
|
233
|
+
"kind": self.kind,
|
|
234
|
+
"scitex_schema": self.scitex_schema,
|
|
235
|
+
"scitex_schema_version": self.scitex_schema_version,
|
|
236
|
+
"bbox_norm": self.bbox_norm.to_dict(),
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if self.name:
|
|
240
|
+
result["name"] = self.name
|
|
241
|
+
if self.size_mm:
|
|
242
|
+
result["size_mm"] = self.size_mm.to_dict()
|
|
243
|
+
if self.axes:
|
|
244
|
+
result["axes"] = self.axes.to_dict()
|
|
245
|
+
if self.children:
|
|
246
|
+
result["children"] = self.children
|
|
247
|
+
if self.layout:
|
|
248
|
+
result["layout"] = self.layout
|
|
249
|
+
if self.payload_schema:
|
|
250
|
+
result["payload_schema"] = self.payload_schema
|
|
251
|
+
if self.text:
|
|
252
|
+
result["text"] = self.text.to_dict()
|
|
253
|
+
if self.shape:
|
|
254
|
+
result["shape"] = self.shape.to_dict()
|
|
255
|
+
|
|
256
|
+
result["refs"] = self.refs.to_dict()
|
|
257
|
+
result["created_at"] = self.created_at
|
|
258
|
+
result["modified_at"] = self.modified_at
|
|
259
|
+
return result
|
|
260
|
+
|
|
261
|
+
@classmethod
|
|
262
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Node":
|
|
263
|
+
"""Create Node from dictionary."""
|
|
264
|
+
# Handle legacy 'type' field
|
|
265
|
+
kind = data.get("kind") or data.get("type", "plot")
|
|
266
|
+
|
|
267
|
+
# Handle payload_schema
|
|
268
|
+
payload_schema = data.get("payload_schema")
|
|
269
|
+
|
|
270
|
+
# Handle size_mm from different formats
|
|
271
|
+
size_mm_data = data.get("size_mm")
|
|
272
|
+
if size_mm_data is None and "size" in data:
|
|
273
|
+
size = data["size"]
|
|
274
|
+
if isinstance(size, dict):
|
|
275
|
+
size_mm_data = {
|
|
276
|
+
"width": size.get("width_mm", size.get("width", 85)),
|
|
277
|
+
"height": size.get("height_mm", size.get("height", 85)),
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# Handle name from 'title' field (legacy format)
|
|
281
|
+
name = data.get("name") or data.get("title")
|
|
282
|
+
|
|
283
|
+
# Handle text content
|
|
284
|
+
text = None
|
|
285
|
+
if "text" in data and isinstance(data["text"], dict):
|
|
286
|
+
text = TextContent.from_dict(data["text"])
|
|
287
|
+
|
|
288
|
+
# Handle shape params
|
|
289
|
+
shape = None
|
|
290
|
+
if "shape" in data and isinstance(data["shape"], dict):
|
|
291
|
+
shape = ShapeParams.from_dict(data["shape"])
|
|
292
|
+
|
|
293
|
+
return cls(
|
|
294
|
+
id=data.get("id", "unknown"),
|
|
295
|
+
kind=kind,
|
|
296
|
+
scitex_schema=data.get("scitex_schema", "scitex.fts.spec"),
|
|
297
|
+
scitex_schema_version=data.get("scitex_schema_version", "1.0.0"),
|
|
298
|
+
children=data.get("children", []),
|
|
299
|
+
layout=data.get("layout"),
|
|
300
|
+
payload_schema=payload_schema,
|
|
301
|
+
bbox_norm=BBox.from_dict(data.get("bbox_norm", {})),
|
|
302
|
+
name=name,
|
|
303
|
+
size_mm=SizeMM.from_dict(size_mm_data) if size_mm_data else None,
|
|
304
|
+
axes=Axes.from_dict(data["axes"]) if "axes" in data else None,
|
|
305
|
+
text=text,
|
|
306
|
+
shape=shape,
|
|
307
|
+
refs=NodeRefs.from_dict(data.get("refs", {})),
|
|
308
|
+
created_at=data.get("created_at"),
|
|
309
|
+
modified_at=data.get("modified_at"),
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
def touch(self):
|
|
313
|
+
"""Update modified timestamp."""
|
|
314
|
+
self.modified_at = datetime.utcnow().isoformat() + "Z"
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
__all__ = ["Node", "TextContent", "ShapeParams"]
|
|
318
|
+
|
|
319
|
+
# EOF
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/NodeRefs.py
|
|
4
|
+
|
|
5
|
+
"""NodeRefs - References to associated files within the bundle."""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class NodeRefs:
|
|
13
|
+
"""References to associated files within the bundle.
|
|
14
|
+
|
|
15
|
+
All paths are relative to the bundle root.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
encoding: str = "encoding.json"
|
|
19
|
+
theme: str = "theme.json"
|
|
20
|
+
data: Optional[str] = None # data/ directory or specific file
|
|
21
|
+
stats: str = "stats/stats.json"
|
|
22
|
+
|
|
23
|
+
def to_dict(self) -> Dict[str, str]:
|
|
24
|
+
result = {
|
|
25
|
+
"encoding": self.encoding,
|
|
26
|
+
"theme": self.theme,
|
|
27
|
+
"stats": self.stats,
|
|
28
|
+
}
|
|
29
|
+
if self.data:
|
|
30
|
+
result["data"] = self.data
|
|
31
|
+
return result
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_dict(cls, data: Dict[str, Any]) -> "NodeRefs":
|
|
35
|
+
return cls(
|
|
36
|
+
encoding=data.get("encoding", "encoding.json"),
|
|
37
|
+
theme=data.get("theme", "theme.json"),
|
|
38
|
+
data=data.get("data"),
|
|
39
|
+
stats=data.get("stats", "stats/stats.json"),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = ["NodeRefs"]
|
|
44
|
+
|
|
45
|
+
# EOF
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/SizeMM.py
|
|
4
|
+
|
|
5
|
+
"""SizeMM - Physical size in millimeters."""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Dict, Tuple
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class SizeMM:
|
|
13
|
+
"""Physical size in millimeters.
|
|
14
|
+
|
|
15
|
+
Used for print-ready figure dimensions.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
width: float = 170.0 # Single column default
|
|
19
|
+
height: float = 120.0
|
|
20
|
+
|
|
21
|
+
def to_dict(self) -> Dict[str, float]:
|
|
22
|
+
return {"width": self.width, "height": self.height}
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def from_dict(cls, data: Dict[str, Any]) -> "SizeMM":
|
|
26
|
+
return cls(
|
|
27
|
+
width=data.get("width", 170.0),
|
|
28
|
+
height=data.get("height", 120.0),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def to_inches(self) -> Tuple[float, float]:
|
|
32
|
+
"""Convert to inches (for matplotlib)."""
|
|
33
|
+
return (self.width / 25.4, self.height / 25.4)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = ["SizeMM"]
|
|
37
|
+
|
|
38
|
+
# EOF
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-21
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_dataclasses/__init__.py
|
|
4
|
+
|
|
5
|
+
"""FTS Dataclasses - Core shared data models for bundles."""
|
|
6
|
+
|
|
7
|
+
# Core models (shared between fig and stats)
|
|
8
|
+
from ._Axes import Axes
|
|
9
|
+
from ._BBox import BBox
|
|
10
|
+
from ._ColumnDef import ColumnDef
|
|
11
|
+
from ._DataFormat import DataFormat
|
|
12
|
+
from ._DataInfo import DATA_INFO_VERSION, DataInfo
|
|
13
|
+
from ._DataSource import DataSource
|
|
14
|
+
from ._Node import Node, ShapeParams, TextContent
|
|
15
|
+
from ._NodeRefs import NodeRefs
|
|
16
|
+
from ._SizeMM import SizeMM
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
# Core models
|
|
20
|
+
"BBox",
|
|
21
|
+
"SizeMM",
|
|
22
|
+
"Axes",
|
|
23
|
+
"NodeRefs",
|
|
24
|
+
"Node",
|
|
25
|
+
"TextContent",
|
|
26
|
+
"ShapeParams",
|
|
27
|
+
# Data Info
|
|
28
|
+
"DATA_INFO_VERSION",
|
|
29
|
+
"DataSource",
|
|
30
|
+
"DataFormat",
|
|
31
|
+
"ColumnDef",
|
|
32
|
+
"DataInfo",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
# EOF
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_extractors/__init__.py
|
|
4
|
+
|
|
5
|
+
"""Plot type extractors for FTS bundle creation.
|
|
6
|
+
|
|
7
|
+
Each extractor handles data extraction and encoding building for a specific plot type.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Line plots
|
|
11
|
+
from ._extract_line import build_line_traces, extract_line_data
|
|
12
|
+
|
|
13
|
+
# Scatter plots (PathCollection)
|
|
14
|
+
from ._extract_scatter import build_scatter_traces, extract_scatter_data
|
|
15
|
+
|
|
16
|
+
# Bar charts (Rectangle patches)
|
|
17
|
+
from ._extract_bar import build_bar_traces, count_valid_bars, extract_bar_data
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
# Line
|
|
21
|
+
"extract_line_data",
|
|
22
|
+
"build_line_traces",
|
|
23
|
+
# Scatter
|
|
24
|
+
"extract_scatter_data",
|
|
25
|
+
"build_scatter_traces",
|
|
26
|
+
# Bar
|
|
27
|
+
"extract_bar_data",
|
|
28
|
+
"count_valid_bars",
|
|
29
|
+
"build_bar_traces",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
# EOF
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_extractors/_extract_bar.py
|
|
4
|
+
|
|
5
|
+
"""Bar chart data and encoding extraction."""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Dict, List, Tuple
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from matplotlib.axes import Axes
|
|
13
|
+
|
|
14
|
+
from ..._fig._dataclasses import TraceEncoding
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_valid_bar(rect, xlim, ylim, axes_width, axes_height) -> bool:
|
|
18
|
+
"""Check if a Rectangle is a valid bar (not UI element).
|
|
19
|
+
|
|
20
|
+
Filters out legend boxes, axes frames, and other UI elements.
|
|
21
|
+
"""
|
|
22
|
+
w = rect.get_width()
|
|
23
|
+
h = rect.get_height()
|
|
24
|
+
x = rect.get_x()
|
|
25
|
+
y = rect.get_y()
|
|
26
|
+
|
|
27
|
+
# Filter out: zero/negative dimensions
|
|
28
|
+
if w <= 0 or h == 0:
|
|
29
|
+
return False
|
|
30
|
+
# Filter out: full-width elements (likely axes frame)
|
|
31
|
+
if abs(w - axes_width) < 0.01 * axes_width:
|
|
32
|
+
return False
|
|
33
|
+
# Filter out: full-height elements
|
|
34
|
+
if abs(h - axes_height) < 0.01 * axes_height:
|
|
35
|
+
return False
|
|
36
|
+
# Filter out: very thin bars (likely spines)
|
|
37
|
+
if w < 0.01 * axes_width:
|
|
38
|
+
return False
|
|
39
|
+
# Filter out: elements outside data area
|
|
40
|
+
if x < xlim[0] - 0.1 * axes_width or x > xlim[1]:
|
|
41
|
+
return False
|
|
42
|
+
if y < ylim[0] - 0.1 * axes_height:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def extract_bar_data(ax: "Axes", ax_idx: int) -> Dict[str, np.ndarray]:
|
|
49
|
+
"""Extract bar chart data from axes (Rectangle patches).
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
ax: Matplotlib axes
|
|
53
|
+
ax_idx: Axes index for column naming
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Dict mapping column names to data arrays
|
|
57
|
+
"""
|
|
58
|
+
from matplotlib.patches import Rectangle
|
|
59
|
+
|
|
60
|
+
xlim = ax.get_xlim()
|
|
61
|
+
ylim = ax.get_ylim()
|
|
62
|
+
axes_width = xlim[1] - xlim[0]
|
|
63
|
+
axes_height = ylim[1] - ylim[0]
|
|
64
|
+
|
|
65
|
+
bars_x = []
|
|
66
|
+
bars_height = []
|
|
67
|
+
|
|
68
|
+
for child in ax.get_children():
|
|
69
|
+
if isinstance(child, Rectangle):
|
|
70
|
+
if _is_valid_bar(child, xlim, ylim, axes_width, axes_height):
|
|
71
|
+
w = child.get_width()
|
|
72
|
+
h = child.get_height()
|
|
73
|
+
x = child.get_x()
|
|
74
|
+
bars_x.append(x + w / 2)
|
|
75
|
+
bars_height.append(h)
|
|
76
|
+
|
|
77
|
+
data = {}
|
|
78
|
+
if len(bars_x) >= 2: # At least 2 bars to be a bar chart
|
|
79
|
+
data[f"ax{ax_idx}_bar_x"] = np.array(bars_x, dtype=float)
|
|
80
|
+
data[f"ax{ax_idx}_bar_height"] = np.array(bars_height, dtype=float)
|
|
81
|
+
|
|
82
|
+
return data
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def count_valid_bars(ax: "Axes") -> int:
|
|
86
|
+
"""Count valid bar rectangles in axes."""
|
|
87
|
+
from matplotlib.patches import Rectangle
|
|
88
|
+
|
|
89
|
+
xlim = ax.get_xlim()
|
|
90
|
+
ylim = ax.get_ylim()
|
|
91
|
+
axes_width = xlim[1] - xlim[0]
|
|
92
|
+
axes_height = ylim[1] - ylim[0]
|
|
93
|
+
|
|
94
|
+
count = 0
|
|
95
|
+
for child in ax.get_children():
|
|
96
|
+
if isinstance(child, Rectangle):
|
|
97
|
+
if _is_valid_bar(child, xlim, ylim, axes_width, axes_height):
|
|
98
|
+
count += 1
|
|
99
|
+
|
|
100
|
+
return count
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def build_bar_traces(ax: "Axes", ax_idx: int) -> List["TraceEncoding"]:
|
|
104
|
+
"""Build encoding traces for bar charts.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
ax: Matplotlib axes
|
|
108
|
+
ax_idx: Axes index for trace ID
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
List of TraceEncoding objects
|
|
112
|
+
"""
|
|
113
|
+
from ..._fig._dataclasses import ChannelEncoding, TraceEncoding
|
|
114
|
+
|
|
115
|
+
traces = []
|
|
116
|
+
bar_count = count_valid_bars(ax)
|
|
117
|
+
|
|
118
|
+
if bar_count >= 2:
|
|
119
|
+
trace = TraceEncoding(
|
|
120
|
+
trace_id=f"bar_{ax_idx}",
|
|
121
|
+
x=ChannelEncoding(column=f"ax{ax_idx}_bar_x"),
|
|
122
|
+
y=ChannelEncoding(column=f"ax{ax_idx}_bar_height"),
|
|
123
|
+
)
|
|
124
|
+
traces.append(trace)
|
|
125
|
+
|
|
126
|
+
return traces
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
__all__ = ["extract_bar_data", "count_valid_bars", "build_bar_traces"]
|
|
130
|
+
|
|
131
|
+
# EOF
|