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,345 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_fig/_composite.py
|
|
4
|
+
|
|
5
|
+
"""Composite figure renderer for FTS bundles.
|
|
6
|
+
|
|
7
|
+
Renders composite figures (kind=figure) containing multiple children.
|
|
8
|
+
ALWAYS re-renders from child's canonical (exports = optional cache only).
|
|
9
|
+
|
|
10
|
+
Design principles:
|
|
11
|
+
- Re-render children from canonical/encoding.json + payload/data.csv
|
|
12
|
+
- Recursively render nested figures (NOT from exports/)
|
|
13
|
+
- Apply container's theme for unified styling
|
|
14
|
+
- Generate geometry_px.json with flattened child geometry
|
|
15
|
+
- Cache key includes canonical_hash + effective_theme_hash + renderer_version
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import io
|
|
19
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from matplotlib.axes import Axes as MplAxes
|
|
23
|
+
from matplotlib.figure import Figure as MplFigure
|
|
24
|
+
|
|
25
|
+
from .._bundle._FTS import FTS
|
|
26
|
+
from ._dataclasses import Theme
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def render_composite(
|
|
30
|
+
children: Dict[str, "FTS"],
|
|
31
|
+
layout: Dict,
|
|
32
|
+
size_mm: Optional[Dict[str, float]] = None,
|
|
33
|
+
theme: Optional["Theme"] = None,
|
|
34
|
+
dpi: int = 300,
|
|
35
|
+
) -> Tuple["MplFigure", Dict]:
|
|
36
|
+
"""Render composite figure with children in grid layout.
|
|
37
|
+
|
|
38
|
+
ALWAYS re-renders from child's canonical (exports = optional cache only).
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
children: Dict mapping child_name -> FTS object
|
|
42
|
+
layout: Layout specification {rows, cols, panels: [...]}
|
|
43
|
+
size_mm: Figure size in mm (default: 170x85 for two-column)
|
|
44
|
+
theme: Theme to apply to all children
|
|
45
|
+
dpi: Output DPI
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
(figure, geometry_data)
|
|
49
|
+
"""
|
|
50
|
+
import matplotlib.pyplot as plt
|
|
51
|
+
from matplotlib.gridspec import GridSpec
|
|
52
|
+
|
|
53
|
+
# Default size
|
|
54
|
+
if size_mm is None:
|
|
55
|
+
size_mm = {"width": 170, "height": 85}
|
|
56
|
+
|
|
57
|
+
# Convert mm to inches (matplotlib uses inches)
|
|
58
|
+
width_in = size_mm.get("width", 170) / 25.4
|
|
59
|
+
height_in = size_mm.get("height", 85) / 25.4
|
|
60
|
+
|
|
61
|
+
# Create figure with gridspec
|
|
62
|
+
rows = layout.get("rows", 1)
|
|
63
|
+
cols = layout.get("cols", 1)
|
|
64
|
+
|
|
65
|
+
fig = plt.figure(figsize=(width_in, height_in), dpi=dpi)
|
|
66
|
+
gs = GridSpec(rows, cols, figure=fig)
|
|
67
|
+
|
|
68
|
+
# Collect geometry data for all elements
|
|
69
|
+
geometry = {
|
|
70
|
+
"elements": [],
|
|
71
|
+
"panels": [],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Render each panel
|
|
75
|
+
panels = layout.get("panels", [])
|
|
76
|
+
for panel_info in panels:
|
|
77
|
+
child_name = panel_info.get("child")
|
|
78
|
+
row = panel_info.get("row", 0)
|
|
79
|
+
col = panel_info.get("col", 0)
|
|
80
|
+
row_span = panel_info.get("row_span", 1)
|
|
81
|
+
col_span = panel_info.get("col_span", 1)
|
|
82
|
+
label = panel_info.get("label")
|
|
83
|
+
|
|
84
|
+
if child_name not in children:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
child = children[child_name]
|
|
88
|
+
|
|
89
|
+
# Create axes for this panel
|
|
90
|
+
ax = fig.add_subplot(gs[row : row + row_span, col : col + col_span])
|
|
91
|
+
|
|
92
|
+
# Render child into axes (ALWAYS re-render from canonical)
|
|
93
|
+
child_geometry = render_child_in_axes(ax, child, theme)
|
|
94
|
+
|
|
95
|
+
# Add panel label if specified
|
|
96
|
+
if label:
|
|
97
|
+
_add_panel_label(ax, label)
|
|
98
|
+
|
|
99
|
+
# Record panel geometry (flatten into parent's figure_px space)
|
|
100
|
+
panel_geometry = {
|
|
101
|
+
"child": child_name,
|
|
102
|
+
"child_id": panel_info.get("child_id"),
|
|
103
|
+
"label": label,
|
|
104
|
+
"position": {"row": row, "col": col, "row_span": row_span, "col_span": col_span},
|
|
105
|
+
"bbox_figure": _get_axes_bbox(ax, fig),
|
|
106
|
+
"child_elements": child_geometry.get("elements", []),
|
|
107
|
+
}
|
|
108
|
+
geometry["panels"].append(panel_geometry)
|
|
109
|
+
|
|
110
|
+
plt.tight_layout()
|
|
111
|
+
|
|
112
|
+
return fig, geometry
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def render_child_in_axes(
|
|
116
|
+
ax: "MplAxes",
|
|
117
|
+
child: "FTS",
|
|
118
|
+
theme: Optional["Theme"] = None,
|
|
119
|
+
) -> Dict:
|
|
120
|
+
"""Render child FTS into axes.
|
|
121
|
+
|
|
122
|
+
ALWAYS re-renders from canonical (exports = optional cache only):
|
|
123
|
+
- kind=plot: Re-render from canonical/spec.json + payload/data.csv
|
|
124
|
+
- kind=figure: Recursively render children (NOT use exports/figure.png)
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
ax: Matplotlib axes to render into
|
|
128
|
+
child: Child FTS bundle
|
|
129
|
+
theme: Theme to apply (overrides child's theme)
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Geometry data for this child
|
|
133
|
+
"""
|
|
134
|
+
geometry = {"elements": []}
|
|
135
|
+
|
|
136
|
+
if child.node.is_leaf_kind():
|
|
137
|
+
# Leaf node: render from encoding + payload
|
|
138
|
+
geometry = _render_leaf_in_axes(ax, child, theme)
|
|
139
|
+
elif child.node.is_composite_kind():
|
|
140
|
+
# Composite: recursive render (nested figure)
|
|
141
|
+
geometry = _render_composite_in_axes(ax, child, theme)
|
|
142
|
+
|
|
143
|
+
return geometry
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _render_leaf_in_axes(
|
|
147
|
+
ax: "MplAxes",
|
|
148
|
+
child: "FTS",
|
|
149
|
+
theme: Optional["Theme"] = None,
|
|
150
|
+
) -> Dict:
|
|
151
|
+
"""Render leaf FTS (plot/table/stats) into axes.
|
|
152
|
+
|
|
153
|
+
Uses child's pre-rendered export (artifacts/exports/figure.png) to embed
|
|
154
|
+
the visualization. This approach ensures visual consistency with the
|
|
155
|
+
original rendered plot.
|
|
156
|
+
"""
|
|
157
|
+
import io
|
|
158
|
+
|
|
159
|
+
import matplotlib.pyplot as plt
|
|
160
|
+
|
|
161
|
+
geometry = {"elements": []}
|
|
162
|
+
|
|
163
|
+
# Try to use pre-rendered export image
|
|
164
|
+
try:
|
|
165
|
+
storage = child.storage
|
|
166
|
+
|
|
167
|
+
# Check for exported image
|
|
168
|
+
if storage.exists("artifacts/exports/figure.png"):
|
|
169
|
+
img_data = storage.read("artifacts/exports/figure.png")
|
|
170
|
+
img = plt.imread(io.BytesIO(img_data), format="png")
|
|
171
|
+
|
|
172
|
+
# Display image in axes, filling the entire axes
|
|
173
|
+
ax.imshow(img, aspect="auto", extent=[0, 1, 0, 1])
|
|
174
|
+
ax.set_xlim(0, 1)
|
|
175
|
+
ax.set_ylim(0, 1)
|
|
176
|
+
ax.axis("off")
|
|
177
|
+
|
|
178
|
+
geometry["elements"].append({
|
|
179
|
+
"type": "embedded_image",
|
|
180
|
+
"source": "artifacts/exports/figure.png",
|
|
181
|
+
})
|
|
182
|
+
return geometry
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
pass # Fall through to placeholder
|
|
186
|
+
|
|
187
|
+
# Fallback: draw placeholder with child name
|
|
188
|
+
ax.text(
|
|
189
|
+
0.5,
|
|
190
|
+
0.5,
|
|
191
|
+
f"[{child.node.name or child.node.id[:8]}]",
|
|
192
|
+
ha="center",
|
|
193
|
+
va="center",
|
|
194
|
+
transform=ax.transAxes,
|
|
195
|
+
fontsize=10,
|
|
196
|
+
)
|
|
197
|
+
ax.set_xlim(0, 1)
|
|
198
|
+
ax.set_ylim(0, 1)
|
|
199
|
+
|
|
200
|
+
return geometry
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _render_composite_in_axes(
|
|
204
|
+
ax: "MplAxes",
|
|
205
|
+
child: "FTS",
|
|
206
|
+
theme: Optional["Theme"] = None,
|
|
207
|
+
) -> Dict:
|
|
208
|
+
"""Render composite FTS (figure) into axes.
|
|
209
|
+
|
|
210
|
+
Creates a nested gridspec for the child's children.
|
|
211
|
+
"""
|
|
212
|
+
from .._bundle._children import load_embedded_children
|
|
213
|
+
|
|
214
|
+
geometry = {"elements": [], "nested_panels": []}
|
|
215
|
+
|
|
216
|
+
# Load child's children
|
|
217
|
+
child_children = child.load_children()
|
|
218
|
+
if not child_children:
|
|
219
|
+
ax.text(
|
|
220
|
+
0.5,
|
|
221
|
+
0.5,
|
|
222
|
+
f"[Empty: {child.node.name or child.node.id[:8]}]",
|
|
223
|
+
ha="center",
|
|
224
|
+
va="center",
|
|
225
|
+
transform=ax.transAxes,
|
|
226
|
+
)
|
|
227
|
+
return geometry
|
|
228
|
+
|
|
229
|
+
# Get child's layout
|
|
230
|
+
child_layout = child.node.layout or {"rows": 1, "cols": 1, "panels": []}
|
|
231
|
+
|
|
232
|
+
# For nested figures, we need to subdivide the axes
|
|
233
|
+
# This is a simplified approach - full implementation would use inset axes
|
|
234
|
+
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
235
|
+
|
|
236
|
+
ax.axis("off") # Hide the parent axes
|
|
237
|
+
|
|
238
|
+
# Create a figure-in-figure effect using inset axes
|
|
239
|
+
# For simplicity, render children side by side
|
|
240
|
+
n_children = len(child_children)
|
|
241
|
+
width = 1.0 / max(n_children, 1)
|
|
242
|
+
|
|
243
|
+
for i, (grandchild_name, grandchild) in enumerate(child_children.items()):
|
|
244
|
+
# Create inset axes
|
|
245
|
+
inset_bounds = [width * i, 0, width * 0.95, 1]
|
|
246
|
+
inset_ax = ax.inset_axes(inset_bounds)
|
|
247
|
+
|
|
248
|
+
# Recursively render grandchild
|
|
249
|
+
grandchild_geometry = render_child_in_axes(inset_ax, grandchild, theme)
|
|
250
|
+
|
|
251
|
+
geometry["nested_panels"].append(
|
|
252
|
+
{
|
|
253
|
+
"child": grandchild_name,
|
|
254
|
+
"child_id": grandchild.node.id if grandchild.node else None,
|
|
255
|
+
"elements": grandchild_geometry.get("elements", []),
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
return geometry
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _add_panel_label(ax: "MplAxes", label: str) -> None:
|
|
263
|
+
"""Add panel label (A, B, C...) to axes."""
|
|
264
|
+
ax.text(
|
|
265
|
+
-0.1,
|
|
266
|
+
1.05,
|
|
267
|
+
label,
|
|
268
|
+
transform=ax.transAxes,
|
|
269
|
+
fontsize=14,
|
|
270
|
+
fontweight="bold",
|
|
271
|
+
va="bottom",
|
|
272
|
+
ha="right",
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _get_axes_bbox(ax: "MplAxes", fig: "MplFigure") -> Dict[str, float]:
|
|
277
|
+
"""Get axes bounding box in figure coordinates."""
|
|
278
|
+
bbox = ax.get_position()
|
|
279
|
+
return {
|
|
280
|
+
"x": bbox.x0,
|
|
281
|
+
"y": bbox.y0,
|
|
282
|
+
"width": bbox.width,
|
|
283
|
+
"height": bbox.height,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def check_cache_valid(
|
|
288
|
+
child: "FTS",
|
|
289
|
+
current_theme_hash: str,
|
|
290
|
+
renderer_version: str = "1.0.0",
|
|
291
|
+
) -> bool:
|
|
292
|
+
"""Check if child's cached exports are still valid.
|
|
293
|
+
|
|
294
|
+
Cache is valid only if ALL match:
|
|
295
|
+
1. artifacts/exports/figure.png exists
|
|
296
|
+
2. artifacts/cache/render_manifest.json exists
|
|
297
|
+
3. render_manifest.canonical_hash matches current hash
|
|
298
|
+
4. render_manifest.effective_theme_hash matches current theme
|
|
299
|
+
5. render_manifest.renderer_version matches current renderer
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
child: Child FTS bundle
|
|
303
|
+
current_theme_hash: Hash of current effective theme
|
|
304
|
+
renderer_version: Current renderer version
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
True if cache is valid and can be used
|
|
308
|
+
"""
|
|
309
|
+
from .._bundle._storage import get_storage
|
|
310
|
+
from .._bundle._saver import compute_canonical_hash
|
|
311
|
+
|
|
312
|
+
storage = get_storage(child.path)
|
|
313
|
+
|
|
314
|
+
# Check exports exist
|
|
315
|
+
if not storage.exists("artifacts/exports/figure.png"):
|
|
316
|
+
return False
|
|
317
|
+
|
|
318
|
+
# Check manifest exists
|
|
319
|
+
manifest = storage.read_json("artifacts/cache/render_manifest.json")
|
|
320
|
+
if manifest is None:
|
|
321
|
+
return False
|
|
322
|
+
|
|
323
|
+
# Check canonical hash
|
|
324
|
+
current_canonical_hash = compute_canonical_hash(storage)
|
|
325
|
+
if manifest.get("canonical_hash") != current_canonical_hash:
|
|
326
|
+
return False
|
|
327
|
+
|
|
328
|
+
# Check theme hash
|
|
329
|
+
if manifest.get("effective_theme_hash") != current_theme_hash:
|
|
330
|
+
return False
|
|
331
|
+
|
|
332
|
+
# Check renderer version
|
|
333
|
+
if manifest.get("renderer_version") != renderer_version:
|
|
334
|
+
return False
|
|
335
|
+
|
|
336
|
+
return True
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
__all__ = [
|
|
340
|
+
"render_composite",
|
|
341
|
+
"render_child_in_axes",
|
|
342
|
+
"check_cache_valid",
|
|
343
|
+
]
|
|
344
|
+
|
|
345
|
+
# EOF
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/ChannelEncoding.py
|
|
4
|
+
|
|
5
|
+
"""ChannelEncoding - Single channel encoding."""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ChannelEncoding:
|
|
13
|
+
"""Encoding for a single visual channel (x, y, color, size, etc.)."""
|
|
14
|
+
|
|
15
|
+
column: Optional[str] = None
|
|
16
|
+
scale: str = "linear" # linear, log, categorical, ordinal, time
|
|
17
|
+
domain: Optional[tuple] = None # Min/max or category list
|
|
18
|
+
range: Optional[tuple] = None # Output range for mapping
|
|
19
|
+
|
|
20
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
21
|
+
result = {}
|
|
22
|
+
if self.column:
|
|
23
|
+
result["column"] = self.column
|
|
24
|
+
if self.scale != "linear":
|
|
25
|
+
result["scale"] = self.scale
|
|
26
|
+
if self.domain:
|
|
27
|
+
result["domain"] = list(self.domain)
|
|
28
|
+
if self.range:
|
|
29
|
+
result["range"] = list(self.range)
|
|
30
|
+
return result
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ChannelEncoding":
|
|
34
|
+
domain = data.get("domain")
|
|
35
|
+
range_ = data.get("range")
|
|
36
|
+
return cls(
|
|
37
|
+
column=data.get("column"),
|
|
38
|
+
scale=data.get("scale", "linear"),
|
|
39
|
+
domain=tuple(domain) if domain else None,
|
|
40
|
+
range=tuple(range_) if range_ else None,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__all__ = ["ChannelEncoding"]
|
|
45
|
+
|
|
46
|
+
# EOF
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/Encoding.py
|
|
4
|
+
|
|
5
|
+
"""Encoding - Complete encoding specification for a bundle."""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any, Dict, List
|
|
10
|
+
|
|
11
|
+
from ._TraceEncoding import TraceEncoding
|
|
12
|
+
|
|
13
|
+
ENCODING_VERSION = "1.0.0"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class AxesConfig:
|
|
18
|
+
"""Axis configuration for encoding."""
|
|
19
|
+
|
|
20
|
+
title: str = ""
|
|
21
|
+
type: str = "quantitative"
|
|
22
|
+
|
|
23
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
24
|
+
result = {}
|
|
25
|
+
if self.title:
|
|
26
|
+
result["title"] = self.title
|
|
27
|
+
if self.type != "quantitative":
|
|
28
|
+
result["type"] = self.type
|
|
29
|
+
return result
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def from_dict(cls, data: Dict[str, Any]) -> "AxesConfig":
|
|
33
|
+
return cls(
|
|
34
|
+
title=data.get("title", ""),
|
|
35
|
+
type=data.get("type", "quantitative"),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class Encoding:
|
|
41
|
+
"""Complete encoding specification for a bundle.
|
|
42
|
+
|
|
43
|
+
Stored in encoding.json at bundle root.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
traces: List[TraceEncoding] = field(default_factory=list)
|
|
47
|
+
axes: Dict[str, AxesConfig] = field(default_factory=dict) # {"x": AxesConfig, "y": AxesConfig}
|
|
48
|
+
|
|
49
|
+
# Schema metadata
|
|
50
|
+
schema_name: str = "fsb.encoding"
|
|
51
|
+
schema_version: str = ENCODING_VERSION
|
|
52
|
+
|
|
53
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
54
|
+
result = {
|
|
55
|
+
"traces": [t.to_dict() for t in self.traces],
|
|
56
|
+
}
|
|
57
|
+
if self.axes:
|
|
58
|
+
result["axes"] = {k: v.to_dict() for k, v in self.axes.items()}
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
def to_json(self, indent: int = 2) -> str:
|
|
62
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Encoding":
|
|
66
|
+
axes = {}
|
|
67
|
+
axes_data = data.get("axes", {})
|
|
68
|
+
for key, val in axes_data.items():
|
|
69
|
+
axes[key] = AxesConfig.from_dict(val) if isinstance(val, dict) else AxesConfig()
|
|
70
|
+
return cls(
|
|
71
|
+
traces=[TraceEncoding.from_dict(t) for t in data.get("traces", [])],
|
|
72
|
+
axes=axes,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_json(cls, json_str: str) -> "Encoding":
|
|
77
|
+
return cls.from_dict(json.loads(json_str))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
__all__ = ["ENCODING_VERSION", "AxesConfig", "Encoding"]
|
|
81
|
+
|
|
82
|
+
# EOF
|