scitex 2.10.2__py3-none-any.whl → 2.11.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 +1 -4
- scitex/__version__.py +1 -1
- scitex/_install_guide.py +14 -2
- scitex/bridge/_figrecipe.py +1 -1
- scitex/bridge/_helpers.py +1 -1
- scitex/bridge/_plt_vis.py +1 -1
- scitex/bridge/_stats_plt.py +1 -1
- scitex/bridge/_stats_vis.py +2 -2
- scitex/{fig → canvas}/__init__.py +84 -96
- scitex/{fig → canvas}/backend/_parser.py +1 -1
- scitex/{fig → canvas}/canvas.py +13 -14
- scitex/{fts/_fig/_editor → canvas/editor}/_defaults.py +2 -2
- scitex/{fig → canvas}/editor/edit/__init__.py +11 -14
- scitex/{fig → canvas}/editor/edit/bundle_resolver.py +56 -48
- scitex/{fig → canvas}/editor/edit/editor_launcher.py +79 -26
- scitex/{fts/_fig/_editor/_cui/_panel_loader.py → canvas/editor/edit/panel_loader.py} +8 -8
- scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_bbox.py +2 -1
- scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_core.py +84 -84
- scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_renderer.py +7 -6
- scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/static/css/features/canvas.css +2 -2
- scitex/{fig → canvas}/editor/flask_editor/static/css/features/panel-grid.css +1 -1
- scitex/{fig → canvas}/editor/flask_editor/static/js/core/api.js +3 -4
- scitex/{fig → canvas}/editor/flask_editor/static/js/editor/preview.js +5 -5
- scitex/{fig → canvas}/editor/flask_editor/templates/_html.py +3 -3
- scitex/{fig → canvas}/editor/flask_editor/templates/_scripts.py +10 -10
- scitex/{fig → canvas}/editor/flask_editor/templates/_styles.py +3 -3
- scitex/{fig → canvas}/io/__init__.py +32 -38
- scitex/{fig → canvas}/io/_bundle.py +217 -154
- scitex/{fig → canvas}/io/_canvas.py +1 -1
- scitex/{fig → canvas}/io/_data.py +1 -1
- scitex/{fig → canvas}/io/_export.py +1 -1
- scitex/{fig → canvas}/io/_load.py +1 -1
- scitex/{fig → canvas}/io/_panel.py +1 -1
- scitex/{fig → canvas}/io/_save.py +1 -1
- scitex/{fig → canvas}/model/__init__.py +1 -1
- scitex/{fig → canvas}/model/_annotations.py +1 -1
- scitex/{fig → canvas}/model/_axes.py +1 -1
- scitex/{fig → canvas}/model/_figure.py +1 -1
- scitex/{fig → canvas}/model/_guides.py +1 -1
- scitex/{fig → canvas}/model/_plot.py +1 -1
- scitex/{fig → canvas}/model/_styles.py +1 -1
- scitex/{fig → canvas}/utils/__init__.py +1 -1
- scitex/cli/convert.py +10 -6
- scitex/diagram/README.md +7 -7
- scitex/io/__init__.py +7 -19
- scitex/io/_load.py +15 -19
- scitex/io/_load_modules/_canvas.py +2 -2
- scitex/io/_load_modules/_con.py +5 -5
- scitex/io/_load_modules/_eeg.py +16 -12
- scitex/io/_save.py +11 -16
- scitex/io/_save_modules/__init__.py +6 -10
- scitex/io/_save_modules/_canvas.py +3 -3
- scitex/io/_save_modules/_plot_bundle.py +112 -0
- scitex/io/_save_modules/{_pltz_stx.py → _plot_scitex.py} +7 -7
- scitex/io/_save_modules/_stx_bundle.py +16 -16
- scitex/io/bundle/README.md +89 -80
- scitex/{fts/_bundle/_FTS.py → io/bundle/_Bundle.py} +197 -95
- scitex/io/bundle/__init__.py +67 -35
- scitex/{fts/_bundle → io/bundle}/_children.py +32 -40
- scitex/io/bundle/_core.py +184 -97
- scitex/{fts/_bundle/_dataclasses/_Node.py → io/bundle/_dataclasses/_Spec.py} +29 -23
- scitex/{fts/_bundle/_dataclasses/_NodeRefs.py → io/bundle/_dataclasses/_SpecRefs.py} +6 -6
- scitex/{fts/_bundle → io/bundle}/_dataclasses/__init__.py +4 -4
- scitex/{fts/_bundle → io/bundle}/_loader.py +19 -19
- scitex/io/bundle/_manifest.py +99 -0
- scitex/{fts/_bundle → io/bundle}/_mpl_helpers.py +119 -28
- scitex/io/bundle/_nested.py +113 -100
- scitex/{fts/_bundle → io/bundle}/_saver.py +13 -14
- scitex/{fts/_bundle → io/bundle}/_storage.py +3 -3
- scitex/io/bundle/_types.py +41 -16
- scitex/{fts/_bundle → io/bundle}/_validation.py +20 -18
- scitex/io/bundle/_zip.py +21 -31
- scitex/{fts/_kinds → io/bundle/kinds}/_plot/_backend/_parser.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Annotations.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Axes.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Figure.py +1 -1
- scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_Guides.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Plot.py +1 -1
- scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_Styles.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_plot/_utils/_plot_layout.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/__init__.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_editor/_app.py +1 -1
- scitex/{fts/_tables → io/bundle/kinds/_table}/_latex/_export.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_figure_exporter.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_table_exporter.py +1 -1
- scitex/io/bundle/schemas/__init__.py +30 -0
- scitex/parallel/_run.py +5 -4
- scitex/path/_find.py +60 -83
- scitex/path/_get_module_path.py +23 -21
- scitex/path/_get_spath.py +6 -27
- scitex/path/_getsize.py +23 -9
- scitex/path/_increment_version.py +31 -38
- scitex/path/_mk_spath.py +26 -29
- scitex/path/_path.py +5 -12
- scitex/path/_split.py +27 -15
- scitex/path/_this_path.py +23 -9
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +2 -1
- scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +2 -2
- scitex/plt/gallery/_generate.py +76 -50
- scitex/plt/io/__init__.py +17 -19
- scitex/plt/io/_bundle.py +99 -52
- scitex/plt/io/_layered_bundle.py +303 -168
- scitex/plt/utils/_csv_column_naming.py +250 -118
- scitex/schema/__init__.py +69 -73
- scitex/schema/_canvas.py +1 -1
- scitex/schema/_stats.py +2 -2
- scitex/stats/__init__.py +30 -33
- scitex/stats/_schema.py +1 -1
- scitex/stats/io/__init__.py +10 -11
- scitex/stats/io/_bundle.py +16 -16
- {scitex-2.10.2.dist-info → scitex-2.11.0.dist-info}/METADATA +191 -72
- {scitex-2.10.2.dist-info → scitex-2.11.0.dist-info}/RECORD +237 -360
- scitex/fig/editor/_defaults.py +0 -300
- scitex/fig/editor/edit/panel_loader.py +0 -232
- scitex/fig/editor/flask_editor/_bbox.py +0 -1299
- scitex/fig/editor/flask_editor/_core.py +0 -1429
- scitex/fig/editor/flask_editor/_renderer.py +0 -813
- scitex/fig/editor/flask_editor/static/css/features/canvas.css +0 -176
- scitex/fts/README.md +0 -262
- scitex/fts/TODO.md +0 -66
- scitex/fts/__init__.py +0 -90
- scitex/fts/_bundle/README_IN_BUNDLE.md +0 -102
- scitex/fts/_bundle/__init__.py +0 -38
- scitex/fts/_bundle/_utils/__init__.py +0 -55
- scitex/fts/_bundle/_utils/_const.py +0 -26
- scitex/fts/_bundle/_utils/_errors.py +0 -73
- scitex/fts/_bundle/_utils/_generate.py +0 -21
- scitex/fts/_bundle/_utils/_types.py +0 -76
- scitex/fts/_bundle/_zipbundle.py +0 -165
- scitex/fts/_fig/__init__.py +0 -22
- scitex/fts/_fig/_backend/_parser.py +0 -188
- scitex/fts/_fig/_editor/__init__.py +0 -14
- scitex/fts/_fig/_editor/_cui/__init__.py +0 -33
- scitex/fts/_fig/_editor/_cui/_backend_detector.py +0 -39
- scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +0 -366
- scitex/fts/_fig/_editor/_cui/_editor_launcher.py +0 -175
- scitex/fts/_fig/_editor/_cui/_manual_handler.py +0 -52
- scitex/fts/_fig/_editor/_cui/_path_resolver.py +0 -66
- scitex/fts/_fig/_editor/_gui/__init__.py +0 -11
- scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +0 -20
- scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +0 -664
- scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +0 -79
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +0 -41
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +0 -16
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +0 -85
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +0 -217
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +0 -93
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +0 -57
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +0 -112
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +0 -59
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +0 -212
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +0 -190
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +0 -59
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +0 -45
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +0 -95
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +0 -101
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +0 -138
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +0 -31
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +0 -7
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +0 -56
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +0 -78
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +0 -314
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +0 -107
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +0 -54
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +0 -172
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +0 -258
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +0 -48
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +0 -71
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +0 -288
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +0 -143
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +0 -245
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +0 -992
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +0 -339
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +0 -286
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +0 -371
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +0 -293
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +0 -426
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +0 -152
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +0 -265
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +0 -184
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +0 -57
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +0 -100
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +0 -34
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +0 -124
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +0 -851
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +0 -4932
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +0 -1657
- scitex/fts/_fig/_editor/_gui/_flask_editor.py +0 -36
- scitex/fts/_fig/_models/_Annotations.py +0 -115
- scitex/fts/_fig/_models/_Axes.py +0 -152
- scitex/fts/_fig/_models/_Figure.py +0 -138
- scitex/fts/_fig/_models/_Plot.py +0 -123
- scitex/fts/_fig/_utils/_plot_layout.py +0 -397
- scitex/fts/_kinds/_figure/_composite.py +0 -345
- scitex/fts/_kinds/_plot/_backend/__init__.py +0 -53
- scitex/fts/_kinds/_plot/_backend/_export.py +0 -165
- scitex/fts/_kinds/_plot/_backend/_render.py +0 -538
- scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +0 -46
- scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +0 -82
- scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +0 -441
- scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +0 -52
- scitex/fts/_kinds/_plot/_dataclasses/__init__.py +0 -47
- scitex/fts/_kinds/_plot/_models/_Guides.py +0 -104
- scitex/fts/_kinds/_plot/_models/_Styles.py +0 -245
- scitex/fts/_kinds/_plot/_models/__init__.py +0 -80
- scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +0 -156
- scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +0 -43
- scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +0 -38
- scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +0 -36
- scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +0 -60
- scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +0 -30
- scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +0 -61
- scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +0 -57
- scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +0 -30
- scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +0 -121
- scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +0 -36
- scitex/fts/_kinds/_plot/_utils/__init__.py +0 -129
- scitex/fts/_kinds/_plot/_utils/_auto_layout.py +0 -127
- scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +0 -111
- scitex/fts/_kinds/_plot/_utils/_const_sizes.py +0 -48
- scitex/fts/_kinds/_plot/_utils/_convert_coords.py +0 -77
- scitex/fts/_kinds/_plot/_utils/_get_template.py +0 -178
- scitex/fts/_kinds/_plot/_utils/_normalize.py +0 -73
- scitex/fts/_kinds/_plot/_utils/_validate.py +0 -197
- scitex/fts/_kinds/_table/_latex/_export.py +0 -279
- scitex/fts/_stats/__init__.py +0 -48
- scitex/fts/_stats/_dataclasses/_Stats.py +0 -423
- scitex/fts/_stats/_dataclasses/__init__.py +0 -48
- scitex/fts/_tables/__init__.py +0 -65
- scitex/fts/_tables/_latex/__init__.py +0 -93
- scitex/fts/_tables/_latex/_editor/__init__.py +0 -11
- scitex/fts/_tables/_latex/_editor/_app.py +0 -725
- scitex/fts/_tables/_latex/_figure_exporter.py +0 -153
- scitex/fts/_tables/_latex/_stats_formatter.py +0 -274
- scitex/fts/_tables/_latex/_table_exporter.py +0 -362
- scitex/fts/_tables/_latex/_utils.py +0 -369
- scitex/fts/_tables/_latex/_validator.py +0 -445
- scitex/io/_save_modules/_pltz_bundle.py +0 -356
- /scitex/{fig → canvas}/README.md +0 -0
- /scitex/{fig → canvas}/backend/__init__.py +0 -0
- /scitex/{fig → canvas}/backend/_export.py +0 -0
- /scitex/{fig → canvas}/backend/_render.py +0 -0
- /scitex/{fig → canvas}/docs/CANVAS_ARCHITECTURE.md +0 -0
- /scitex/{fig → canvas}/editor/__init__.py +0 -0
- /scitex/{fig → canvas}/editor/_dearpygui_editor.py +0 -0
- /scitex/{fig → canvas}/editor/_flask_editor.py +0 -0
- /scitex/{fig → canvas}/editor/_mpl_editor.py +0 -0
- /scitex/{fig → canvas}/editor/_qt_editor.py +0 -0
- /scitex/{fig → canvas}/editor/_tkinter_editor.py +0 -0
- /scitex/{fig → canvas}/editor/edit/backend_detector.py +0 -0
- /scitex/{fig → canvas}/editor/edit/manual_handler.py +0 -0
- /scitex/{fig → canvas}/editor/edit/path_resolver.py +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/__init__.py +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/_plotter.py +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/_utils.py +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/base/reset.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/base/typography.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/base/variables.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/components/buttons.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/components/context-menu.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/components/dropdown.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/components/forms.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/components/modal.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/components/sections.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/features/element-inspector.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/features/loading.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/features/overlay.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/features/selection.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/features/statistics.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/index.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/container.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/controls.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/preview.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/axis.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/basic.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/distribute.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/canvas.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/dragging.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/resize.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/selection.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/core/state.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/core/utils.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/dev/element-inspector.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/bbox.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/element-drag.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/overlay.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/main.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/shortcuts/context-menu.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/shortcuts/keyboard.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/controls.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/download.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/help.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/theme.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/templates/__init__.py +0 -0
- /scitex/{fig → canvas}/io/_directory.py +0 -0
- /scitex/{fig → canvas}/model/_plot_types.py +0 -0
- /scitex/{fig → canvas}/utils/_defaults.py +0 -0
- /scitex/{fig → canvas}/utils/_validate.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_conversion/__init__.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_conversion/_bundle2dict.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_conversion/_dict2bundle.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_Axes.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_BBox.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_ColumnDef.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataFormat.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataInfo.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataSource.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_SizeMM.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_extractors/__init__.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_bar.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_line.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_scatter.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/__init__.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_figure/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_figure}/_composite.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_plot/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/_export.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/_render.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_ChannelEncoding.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_Encoding.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_Theme.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_TraceEncoding.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_bar.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_box.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_distribution.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_errorbar.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_histogram.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_image.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_line.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_scatter.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_seaborn.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_violin.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_auto_layout.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_calc_bounds.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_const_sizes.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_convert_coords.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_get_template.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_normalize.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_validate.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_shape/__init__.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_stats/__init__.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_stats/_dataclasses/_Stats.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_stats/_dataclasses/__init__.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_table/__init__.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_editor/__init__.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_stats_formatter.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_utils.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_validator.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_text/__init__.py +0 -0
- /scitex/{fts/_schemas → io/bundle/schemas}/data_info.schema.json +0 -0
- /scitex/{fts/_schemas → io/bundle/schemas}/encoding.schema.json +0 -0
- /scitex/{fts/_schemas → io/bundle/schemas}/node.schema.json +0 -0
- /scitex/{fts/_schemas → io/bundle/schemas}/render_manifest.schema.json +0 -0
- /scitex/{fts/_schemas → io/bundle/schemas}/stats.schema.json +0 -0
- /scitex/{fts/_schemas → io/bundle/schemas}/theme.schema.json +0 -0
- {scitex-2.10.2.dist-info → scitex-2.11.0.dist-info}/WHEEL +0 -0
- {scitex-2.10.2.dist-info → scitex-2.11.0.dist-info}/entry_points.txt +0 -0
- {scitex-2.10.2.dist-info → scitex-2.11.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# Timestamp: 2025-12-20
|
|
3
|
-
# File: /home/ywatanabe/proj/scitex-code/src/scitex/
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/io/bundle/_Bundle.py
|
|
4
4
|
|
|
5
|
-
"""
|
|
5
|
+
"""Bundle Class - Main entry point for scitex bundles.
|
|
6
6
|
|
|
7
7
|
Structure (identical for all kinds):
|
|
8
8
|
- canonical/: Source of truth (spec.json, encoding.json, theme.json)
|
|
@@ -13,26 +13,31 @@ Structure (identical for all kinds):
|
|
|
13
13
|
|
|
14
14
|
import uuid
|
|
15
15
|
from pathlib import Path
|
|
16
|
-
from typing import TYPE_CHECKING, Any, Dict,
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
|
|
17
17
|
|
|
18
18
|
from ._children import ValidationError, embed_child, load_embedded_children
|
|
19
|
-
from ._dataclasses import DataInfo,
|
|
19
|
+
from ._dataclasses import DataInfo, SizeMM, Spec
|
|
20
20
|
from ._loader import load_bundle_components
|
|
21
|
-
from .
|
|
22
|
-
|
|
21
|
+
from ._saver import (
|
|
22
|
+
compute_canonical_hash,
|
|
23
|
+
compute_theme_hash,
|
|
24
|
+
save_bundle_components,
|
|
25
|
+
save_render_outputs,
|
|
26
|
+
)
|
|
23
27
|
from ._storage import Storage, get_storage
|
|
24
|
-
from
|
|
25
|
-
from
|
|
28
|
+
from ._validation import ValidationResult
|
|
29
|
+
from .kinds._plot import Encoding, Theme
|
|
30
|
+
from .kinds._stats import Stats
|
|
26
31
|
|
|
27
32
|
if TYPE_CHECKING:
|
|
28
33
|
from matplotlib.figure import Figure as MplFigure
|
|
29
34
|
|
|
30
35
|
|
|
31
|
-
class
|
|
32
|
-
"""
|
|
36
|
+
class Bundle:
|
|
37
|
+
"""Scitex Bundle - Self-contained figure/plot/stats package.
|
|
33
38
|
|
|
34
39
|
Attributes:
|
|
35
|
-
|
|
40
|
+
spec: Spec metadata (kind, children, layout, payload_schema, etc.)
|
|
36
41
|
encoding: Encoding specification (traces, channels)
|
|
37
42
|
theme: Theme specification (colors, fonts)
|
|
38
43
|
stats: Statistics (for kind=stats)
|
|
@@ -49,7 +54,7 @@ class FTS:
|
|
|
49
54
|
# Legacy support
|
|
50
55
|
node_type: Optional[str] = None,
|
|
51
56
|
):
|
|
52
|
-
"""Initialize
|
|
57
|
+
"""Initialize Bundle.
|
|
53
58
|
|
|
54
59
|
Args:
|
|
55
60
|
path: Bundle path (directory or .zip file)
|
|
@@ -61,7 +66,7 @@ class FTS:
|
|
|
61
66
|
"""
|
|
62
67
|
self._path = Path(path)
|
|
63
68
|
self._is_zip = self._path.suffix == ".zip"
|
|
64
|
-
self.
|
|
69
|
+
self._spec: Optional[Spec] = None
|
|
65
70
|
self._encoding: Optional[Encoding] = None
|
|
66
71
|
self._theme: Optional[Theme] = None
|
|
67
72
|
self._stats: Optional[Stats] = None
|
|
@@ -91,7 +96,7 @@ class FTS:
|
|
|
91
96
|
@property
|
|
92
97
|
def bundle_type(self) -> str:
|
|
93
98
|
"""Bundle kind (figure, plot, table, etc.)."""
|
|
94
|
-
return self.
|
|
99
|
+
return self._spec.kind if self._spec else "unknown"
|
|
95
100
|
|
|
96
101
|
@property
|
|
97
102
|
def is_dirty(self) -> bool:
|
|
@@ -106,16 +111,16 @@ class FTS:
|
|
|
106
111
|
return self._storage
|
|
107
112
|
|
|
108
113
|
@property
|
|
109
|
-
def
|
|
110
|
-
"""
|
|
111
|
-
return self.
|
|
114
|
+
def spec(self) -> Optional[Spec]:
|
|
115
|
+
"""Bundle specification metadata."""
|
|
116
|
+
return self._spec
|
|
112
117
|
|
|
113
|
-
@
|
|
114
|
-
def
|
|
118
|
+
@spec.setter
|
|
119
|
+
def spec(self, value: Union[Spec, Dict[str, Any]]):
|
|
115
120
|
if isinstance(value, dict):
|
|
116
|
-
self.
|
|
121
|
+
self._spec = Spec.from_dict(value)
|
|
117
122
|
else:
|
|
118
|
-
self.
|
|
123
|
+
self._spec = value
|
|
119
124
|
self._dirty = True
|
|
120
125
|
|
|
121
126
|
@property
|
|
@@ -195,15 +200,15 @@ class FTS:
|
|
|
195
200
|
# Note: payload_schema is optional. For plots without data, it's None.
|
|
196
201
|
# For plots with data, from_matplotlib will set it.
|
|
197
202
|
payload_schema = None
|
|
198
|
-
if kind in
|
|
203
|
+
if kind in Spec.LEAF_KINDS and kind != "plot":
|
|
199
204
|
# Only auto-set for non-plot leaf kinds
|
|
200
205
|
payload_schema_map = {
|
|
201
|
-
"table": "scitex.
|
|
202
|
-
"stats": "scitex.
|
|
206
|
+
"table": "scitex.io.bundle.payload.table@1",
|
|
207
|
+
"stats": "scitex.io.bundle.payload.stats@1",
|
|
203
208
|
}
|
|
204
209
|
payload_schema = payload_schema_map.get(kind)
|
|
205
210
|
|
|
206
|
-
self.
|
|
211
|
+
self._spec = Spec(
|
|
207
212
|
id=bundle_id,
|
|
208
213
|
kind=kind,
|
|
209
214
|
name=name,
|
|
@@ -218,10 +223,10 @@ class FTS:
|
|
|
218
223
|
def _load(self):
|
|
219
224
|
"""Load existing bundle."""
|
|
220
225
|
if not self._path.exists():
|
|
221
|
-
raise FileNotFoundError(f"
|
|
226
|
+
raise FileNotFoundError(f"Bundle not found: {self._path}")
|
|
222
227
|
|
|
223
228
|
(
|
|
224
|
-
self.
|
|
229
|
+
self._spec,
|
|
225
230
|
self._encoding,
|
|
226
231
|
self._theme,
|
|
227
232
|
self._stats,
|
|
@@ -230,7 +235,7 @@ class FTS:
|
|
|
230
235
|
|
|
231
236
|
def add_child(
|
|
232
237
|
self,
|
|
233
|
-
child: Union[str, Path, "
|
|
238
|
+
child: Union[str, Path, "Bundle"],
|
|
234
239
|
row: int = 0,
|
|
235
240
|
col: int = 0,
|
|
236
241
|
label: Optional[str] = None,
|
|
@@ -239,11 +244,11 @@ class FTS:
|
|
|
239
244
|
**kwargs,
|
|
240
245
|
) -> str:
|
|
241
246
|
"""Add and embed a child bundle. Returns child_name in children/."""
|
|
242
|
-
if not self.
|
|
243
|
-
raise TypeError(f"kind={self.
|
|
247
|
+
if not self.spec.is_composite_kind():
|
|
248
|
+
raise TypeError(f"kind={self.spec.kind} cannot have children")
|
|
244
249
|
|
|
245
250
|
# Get child path
|
|
246
|
-
if isinstance(child,
|
|
251
|
+
if isinstance(child, Bundle):
|
|
247
252
|
child_path = child.path
|
|
248
253
|
else:
|
|
249
254
|
child_path = Path(child)
|
|
@@ -252,16 +257,20 @@ class FTS:
|
|
|
252
257
|
# Returns (child_name, child_id) tuple
|
|
253
258
|
child_name, child_id = embed_child(self.storage, child_path)
|
|
254
259
|
|
|
255
|
-
# Add to
|
|
256
|
-
self.
|
|
260
|
+
# Add to spec.children
|
|
261
|
+
self._spec.children.append(child_name)
|
|
257
262
|
|
|
258
263
|
# Initialize layout if needed
|
|
259
|
-
if self.
|
|
260
|
-
self.
|
|
264
|
+
if self._spec.layout is None:
|
|
265
|
+
self._spec.layout = {"rows": 2, "cols": 2, "panels": []}
|
|
261
266
|
|
|
262
267
|
# Update grid size if needed
|
|
263
|
-
self.
|
|
264
|
-
|
|
268
|
+
self._spec.layout["rows"] = max(
|
|
269
|
+
self._spec.layout.get("rows", 1), row + row_span
|
|
270
|
+
)
|
|
271
|
+
self._spec.layout["cols"] = max(
|
|
272
|
+
self._spec.layout.get("cols", 1), col + col_span
|
|
273
|
+
)
|
|
265
274
|
|
|
266
275
|
# Add to layout.panels
|
|
267
276
|
panel_info = {
|
|
@@ -276,29 +285,29 @@ class FTS:
|
|
|
276
285
|
if label:
|
|
277
286
|
panel_info["label"] = label
|
|
278
287
|
|
|
279
|
-
self.
|
|
288
|
+
self._spec.layout["panels"].append(panel_info)
|
|
280
289
|
self._dirty = True
|
|
281
290
|
|
|
282
291
|
return child_name
|
|
283
292
|
|
|
284
|
-
def load_children(self) -> Dict[str, "
|
|
285
|
-
"""Load embedded children. Returns dict: child_name ->
|
|
293
|
+
def load_children(self) -> Dict[str, "Bundle"]:
|
|
294
|
+
"""Load embedded children. Returns dict: child_name -> Bundle."""
|
|
286
295
|
return load_embedded_children(self._path)
|
|
287
296
|
|
|
288
297
|
def render(self) -> Optional["MplFigure"]:
|
|
289
298
|
"""Render figure. Composite renders children, leaf renders from encoding."""
|
|
290
|
-
if self.
|
|
299
|
+
if self._spec is None:
|
|
291
300
|
return None
|
|
292
301
|
|
|
293
|
-
if self.
|
|
302
|
+
if self._spec.is_composite_kind():
|
|
294
303
|
return self._render_composite()
|
|
295
|
-
elif self.
|
|
304
|
+
elif self._spec.is_data_leaf_kind():
|
|
296
305
|
# Data kinds (plot, table, stats) need payload data
|
|
297
306
|
return self._render_from_encoding()
|
|
298
|
-
elif self.
|
|
299
|
-
# Annotation kinds (text, shape) render from
|
|
307
|
+
elif self._spec.is_annotation_leaf_kind():
|
|
308
|
+
# Annotation kinds (text, shape) render from spec params
|
|
300
309
|
return self._render_annotation()
|
|
301
|
-
elif self.
|
|
310
|
+
elif self._spec.is_image_leaf_kind():
|
|
302
311
|
# Image kinds render from payload image
|
|
303
312
|
return self._render_image()
|
|
304
313
|
|
|
@@ -308,14 +317,18 @@ class FTS:
|
|
|
308
317
|
"""Render composite figure with children."""
|
|
309
318
|
import scitex.plt as splt
|
|
310
319
|
|
|
311
|
-
size_mm =
|
|
320
|
+
size_mm = (
|
|
321
|
+
self._spec.size_mm.to_dict()
|
|
322
|
+
if self._spec.size_mm
|
|
323
|
+
else {"width": 170, "height": 100}
|
|
324
|
+
)
|
|
312
325
|
|
|
313
326
|
# Get background color from theme
|
|
314
327
|
bg_color = "#ffffff"
|
|
315
328
|
if self._theme and self._theme.colors:
|
|
316
329
|
bg_color = self._theme.colors.background or "#ffffff"
|
|
317
330
|
|
|
318
|
-
if not self.
|
|
331
|
+
if not self._spec.children:
|
|
319
332
|
# Empty container - render blank figure with specified size and background
|
|
320
333
|
fig, ax = splt.subplots(
|
|
321
334
|
figsize_mm=(size_mm.get("width", 170), size_mm.get("height", 100))
|
|
@@ -325,13 +338,13 @@ class FTS:
|
|
|
325
338
|
ax.set_axis_off()
|
|
326
339
|
return fig
|
|
327
340
|
|
|
328
|
-
from
|
|
341
|
+
from .kinds._figure._composite import render_composite
|
|
329
342
|
|
|
330
343
|
children = self.load_children()
|
|
331
344
|
|
|
332
345
|
fig, geometry = render_composite(
|
|
333
346
|
children=children,
|
|
334
|
-
layout=self.
|
|
347
|
+
layout=self._spec.layout or {"rows": 1, "cols": 1, "panels": []},
|
|
335
348
|
size_mm=size_mm,
|
|
336
349
|
theme=self._theme,
|
|
337
350
|
)
|
|
@@ -345,7 +358,11 @@ class FTS:
|
|
|
345
358
|
|
|
346
359
|
import scitex.plt as splt
|
|
347
360
|
|
|
348
|
-
size_mm =
|
|
361
|
+
size_mm = (
|
|
362
|
+
self._spec.size_mm.to_dict()
|
|
363
|
+
if self._spec.size_mm
|
|
364
|
+
else {"width": 85, "height": 85}
|
|
365
|
+
)
|
|
349
366
|
|
|
350
367
|
# Use scitex.plt for proper styling (3-4 ticks, etc.)
|
|
351
368
|
fig, ax = splt.subplots(
|
|
@@ -356,7 +373,7 @@ class FTS:
|
|
|
356
373
|
data = self._load_payload_data()
|
|
357
374
|
|
|
358
375
|
# Render traces
|
|
359
|
-
from
|
|
376
|
+
from .kinds._plot._backend._render import render_traces
|
|
360
377
|
|
|
361
378
|
traces = self._encoding.traces if self._encoding.traces else []
|
|
362
379
|
for trace in traces:
|
|
@@ -375,9 +392,10 @@ class FTS:
|
|
|
375
392
|
|
|
376
393
|
def _load_payload_data(self) -> Optional["pd.DataFrame"]:
|
|
377
394
|
"""Load data from payload/data.csv or legacy data/data.csv."""
|
|
378
|
-
import pandas as pd
|
|
379
395
|
from io import StringIO
|
|
380
396
|
|
|
397
|
+
import pandas as pd
|
|
398
|
+
|
|
381
399
|
# Try new path first, then legacy
|
|
382
400
|
for path in ["payload/data.csv", "data/data.csv"]:
|
|
383
401
|
if self.storage.exists(path):
|
|
@@ -389,10 +407,14 @@ class FTS:
|
|
|
389
407
|
return None
|
|
390
408
|
|
|
391
409
|
def _render_annotation(self) -> Optional["MplFigure"]:
|
|
392
|
-
"""Render annotation (text/shape) from
|
|
410
|
+
"""Render annotation (text/shape) from spec parameters."""
|
|
393
411
|
import scitex.plt as splt
|
|
394
412
|
|
|
395
|
-
size_mm =
|
|
413
|
+
size_mm = (
|
|
414
|
+
self._spec.size_mm.to_dict()
|
|
415
|
+
if self._spec.size_mm
|
|
416
|
+
else {"width": 85, "height": 85}
|
|
417
|
+
)
|
|
396
418
|
|
|
397
419
|
fig, ax = splt.subplots(
|
|
398
420
|
figsize_mm=(size_mm.get("width", 85), size_mm.get("height", 85))
|
|
@@ -406,30 +428,34 @@ class FTS:
|
|
|
406
428
|
ax.set_facecolor(bg_color)
|
|
407
429
|
ax.set_axis_off()
|
|
408
430
|
|
|
409
|
-
if self.
|
|
431
|
+
if self._spec.kind == "text":
|
|
410
432
|
# Render text annotation
|
|
411
|
-
text_obj = self.
|
|
433
|
+
text_obj = self._spec.text
|
|
412
434
|
if text_obj:
|
|
413
|
-
text_content = text_obj.content or self.
|
|
435
|
+
text_content = text_obj.content or self._spec.name or ""
|
|
414
436
|
kwargs = {"ha": text_obj.ha, "va": text_obj.va}
|
|
415
437
|
if text_obj.fontsize:
|
|
416
438
|
kwargs["fontsize"] = text_obj.fontsize
|
|
417
439
|
if text_obj.fontweight:
|
|
418
440
|
kwargs["fontweight"] = text_obj.fontweight
|
|
419
441
|
else:
|
|
420
|
-
text_content = self.
|
|
442
|
+
text_content = self._spec.name or ""
|
|
421
443
|
kwargs = {"ha": "center", "va": "center"}
|
|
422
444
|
ax.text(0.5, 0.5, text_content, transform=ax.transAxes, **kwargs)
|
|
423
445
|
|
|
424
|
-
elif self.
|
|
446
|
+
elif self._spec.kind == "shape":
|
|
425
447
|
# Render shape annotation
|
|
426
|
-
from
|
|
427
|
-
|
|
448
|
+
from .kinds._shape import render_shape
|
|
449
|
+
|
|
450
|
+
shape_obj = self._spec.shape
|
|
428
451
|
if shape_obj:
|
|
429
452
|
render_shape(
|
|
430
453
|
ax,
|
|
431
454
|
shape_type=shape_obj.shape_type,
|
|
432
|
-
x=0.2,
|
|
455
|
+
x=0.2,
|
|
456
|
+
y=0.2,
|
|
457
|
+
width=0.6,
|
|
458
|
+
height=0.6,
|
|
433
459
|
facecolor=shape_obj.color if shape_obj.fill else "none",
|
|
434
460
|
edgecolor=shape_obj.color,
|
|
435
461
|
linewidth=shape_obj.linewidth,
|
|
@@ -440,10 +466,15 @@ class FTS:
|
|
|
440
466
|
|
|
441
467
|
def _render_image(self) -> Optional["MplFigure"]:
|
|
442
468
|
"""Render image from payload."""
|
|
443
|
-
import scitex.plt as splt
|
|
444
469
|
import numpy as np
|
|
445
470
|
|
|
446
|
-
|
|
471
|
+
import scitex.plt as splt
|
|
472
|
+
|
|
473
|
+
size_mm = (
|
|
474
|
+
self._spec.size_mm.to_dict()
|
|
475
|
+
if self._spec.size_mm
|
|
476
|
+
else {"width": 85, "height": 85}
|
|
477
|
+
)
|
|
447
478
|
|
|
448
479
|
fig, ax = splt.subplots(
|
|
449
480
|
figsize_mm=(size_mm.get("width", 85), size_mm.get("height", 85))
|
|
@@ -454,8 +485,10 @@ class FTS:
|
|
|
454
485
|
for ext in ["png", "jpg", "jpeg", "gif", "bmp"]:
|
|
455
486
|
path = f"payload/image.{ext}"
|
|
456
487
|
if self.storage.exists(path):
|
|
457
|
-
from PIL import Image
|
|
458
488
|
from io import BytesIO
|
|
489
|
+
|
|
490
|
+
from PIL import Image
|
|
491
|
+
|
|
459
492
|
img_bytes = self.storage.read(path)
|
|
460
493
|
img = Image.open(BytesIO(img_bytes))
|
|
461
494
|
ax.imshow(np.array(img))
|
|
@@ -464,6 +497,62 @@ class FTS:
|
|
|
464
497
|
fig.tight_layout()
|
|
465
498
|
return fig
|
|
466
499
|
|
|
500
|
+
def _validate_manifest(self) -> tuple:
|
|
501
|
+
"""Validate manifest.json existence and structure.
|
|
502
|
+
|
|
503
|
+
Returns:
|
|
504
|
+
Tuple of (errors: List[str], warnings: List[str])
|
|
505
|
+
"""
|
|
506
|
+
import json
|
|
507
|
+
|
|
508
|
+
errors = []
|
|
509
|
+
warnings = []
|
|
510
|
+
|
|
511
|
+
# Check if bundle path exists
|
|
512
|
+
if not self._path.exists():
|
|
513
|
+
return errors, warnings # Can't validate non-existent bundle
|
|
514
|
+
|
|
515
|
+
# Check manifest.json exists (required)
|
|
516
|
+
manifest_path = "manifest.json"
|
|
517
|
+
if not self.storage.exists(manifest_path):
|
|
518
|
+
errors.append("Missing required manifest.json")
|
|
519
|
+
return errors, warnings
|
|
520
|
+
|
|
521
|
+
# Validate manifest structure
|
|
522
|
+
try:
|
|
523
|
+
content = self.storage.read(manifest_path)
|
|
524
|
+
manifest = json.loads(content.decode("utf-8"))
|
|
525
|
+
|
|
526
|
+
if "scitex" not in manifest:
|
|
527
|
+
errors.append("manifest.json missing 'scitex' key")
|
|
528
|
+
else:
|
|
529
|
+
scitex = manifest["scitex"]
|
|
530
|
+
if "type" not in scitex:
|
|
531
|
+
errors.append("manifest.json missing 'scitex.type'")
|
|
532
|
+
if "version" not in scitex:
|
|
533
|
+
errors.append("manifest.json missing 'scitex.version'")
|
|
534
|
+
|
|
535
|
+
# Validate type matches spec kind
|
|
536
|
+
manifest_type = scitex.get("type")
|
|
537
|
+
if manifest_type and self._spec:
|
|
538
|
+
# Normalize both to compare
|
|
539
|
+
from ._types import BundleType
|
|
540
|
+
|
|
541
|
+
normalized_manifest = BundleType.normalize(manifest_type)
|
|
542
|
+
normalized_node = BundleType.normalize(self._spec.kind)
|
|
543
|
+
if normalized_manifest != normalized_node:
|
|
544
|
+
errors.append(
|
|
545
|
+
f"Type mismatch: manifest says '{manifest_type}', "
|
|
546
|
+
f"spec says '{self._spec.kind}'"
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
except json.JSONDecodeError as e:
|
|
550
|
+
errors.append(f"manifest.json is invalid JSON: {e}")
|
|
551
|
+
except Exception as e:
|
|
552
|
+
errors.append(f"Error reading manifest.json: {e}")
|
|
553
|
+
|
|
554
|
+
return errors, warnings
|
|
555
|
+
|
|
467
556
|
def validate(self, level: str = "schema") -> ValidationResult:
|
|
468
557
|
"""Validate bundle.
|
|
469
558
|
|
|
@@ -475,16 +564,21 @@ class FTS:
|
|
|
475
564
|
"""
|
|
476
565
|
result = ValidationResult(level=level)
|
|
477
566
|
|
|
478
|
-
#
|
|
479
|
-
|
|
480
|
-
|
|
567
|
+
# Manifest validation (returns errors, warnings tuple)
|
|
568
|
+
manifest_errors, manifest_warnings = self._validate_manifest()
|
|
569
|
+
result.errors.extend(manifest_errors)
|
|
570
|
+
result.warnings.extend(manifest_warnings)
|
|
571
|
+
|
|
572
|
+
# Spec logical validation
|
|
573
|
+
if self._spec:
|
|
574
|
+
result.errors.extend(self._spec.validate())
|
|
481
575
|
|
|
482
576
|
# Storage-level validation - check required payload files
|
|
483
|
-
if self.
|
|
484
|
-
required_file = self.
|
|
577
|
+
if self._spec and self._spec.is_leaf_kind():
|
|
578
|
+
required_file = self._spec.get_required_payload_file()
|
|
485
579
|
if required_file:
|
|
486
580
|
# Check both new structure (payload/) and legacy structure (data/)
|
|
487
|
-
# Legacy sio.save() uses data/data.csv, new
|
|
581
|
+
# Legacy sio.save() uses data/data.csv, new Bundle uses payload/data.csv
|
|
488
582
|
legacy_paths = {
|
|
489
583
|
"payload/data.csv": "data/data.csv",
|
|
490
584
|
"payload/table.csv": "data/table.csv",
|
|
@@ -493,25 +587,33 @@ class FTS:
|
|
|
493
587
|
legacy_path = legacy_paths.get(required_file)
|
|
494
588
|
if not self.storage.exists(required_file):
|
|
495
589
|
if not legacy_path or not self.storage.exists(legacy_path):
|
|
496
|
-
result.errors.append(
|
|
590
|
+
result.errors.append(
|
|
591
|
+
f"Missing required payload file: {required_file}"
|
|
592
|
+
)
|
|
497
593
|
|
|
498
594
|
# NOTE: For composite kinds, do NOT validate payload/ emptiness by listing files.
|
|
499
|
-
# Payload prohibition is enforced purely via payload_schema is None (in
|
|
595
|
+
# Payload prohibition is enforced purely via payload_schema is None (in Spec.validate).
|
|
500
596
|
|
|
501
597
|
# Recursively validate embedded children
|
|
502
|
-
if self.
|
|
598
|
+
if self._spec and self._spec.is_composite_kind() and self._spec.children:
|
|
503
599
|
children = self.load_children()
|
|
504
600
|
for child_name, child in children.items():
|
|
505
601
|
child_result = child.validate(level)
|
|
506
|
-
result.errors.extend(
|
|
507
|
-
|
|
602
|
+
result.errors.extend(
|
|
603
|
+
[f"{child_name}: {e}" for e in child_result.errors]
|
|
604
|
+
)
|
|
605
|
+
result.warnings.extend(
|
|
606
|
+
[f"{child_name}: {w}" for w in child_result.warnings]
|
|
607
|
+
)
|
|
508
608
|
|
|
509
609
|
# Schema validation for other components
|
|
510
610
|
if level in ("semantic", "strict"):
|
|
511
611
|
# Additional semantic validation
|
|
512
|
-
if self._encoding and self.
|
|
513
|
-
if self.
|
|
514
|
-
result.errors.append(
|
|
612
|
+
if self._encoding and self._spec:
|
|
613
|
+
if self._spec.is_composite_kind() and self._encoding.traces:
|
|
614
|
+
result.errors.append(
|
|
615
|
+
"Composite kinds should not have encoding traces"
|
|
616
|
+
)
|
|
515
617
|
|
|
516
618
|
return result
|
|
517
619
|
|
|
@@ -543,13 +645,13 @@ class FTS:
|
|
|
543
645
|
raise ValidationError(f"Validation failed: {result.errors}")
|
|
544
646
|
|
|
545
647
|
# Update modified timestamp
|
|
546
|
-
if self.
|
|
547
|
-
self.
|
|
648
|
+
if self._spec:
|
|
649
|
+
self._spec.touch()
|
|
548
650
|
|
|
549
651
|
# Save canonical files
|
|
550
652
|
save_bundle_components(
|
|
551
653
|
self._path,
|
|
552
|
-
|
|
654
|
+
spec=self._spec,
|
|
553
655
|
encoding=self._encoding,
|
|
554
656
|
theme=self._theme,
|
|
555
657
|
stats=self._stats,
|
|
@@ -597,8 +699,8 @@ class FTS:
|
|
|
597
699
|
"is_zip": self._is_zip,
|
|
598
700
|
"kind": self.bundle_type,
|
|
599
701
|
}
|
|
600
|
-
if self.
|
|
601
|
-
result["
|
|
702
|
+
if self._spec:
|
|
703
|
+
result["spec"] = self._spec.to_dict()
|
|
602
704
|
if self._encoding:
|
|
603
705
|
result["encoding"] = self._encoding.to_dict()
|
|
604
706
|
if self._theme:
|
|
@@ -609,7 +711,7 @@ class FTS:
|
|
|
609
711
|
result["data_info"] = self._data_info.to_dict()
|
|
610
712
|
return result
|
|
611
713
|
|
|
612
|
-
def __enter__(self) -> "
|
|
714
|
+
def __enter__(self) -> "Bundle":
|
|
613
715
|
"""Enter context manager."""
|
|
614
716
|
return self
|
|
615
717
|
|
|
@@ -621,8 +723,8 @@ class FTS:
|
|
|
621
723
|
|
|
622
724
|
def __repr__(self) -> str:
|
|
623
725
|
dirty_marker = "*" if self._dirty else ""
|
|
624
|
-
kind = self.
|
|
625
|
-
return f"
|
|
726
|
+
kind = self._spec.kind if self._spec else "unknown"
|
|
727
|
+
return f"Bundle({self._path!r}, kind={kind!r}){dirty_marker}"
|
|
626
728
|
|
|
627
729
|
|
|
628
730
|
# =============================================================================
|
|
@@ -633,9 +735,9 @@ class FTS:
|
|
|
633
735
|
from ._mpl_helpers import from_matplotlib
|
|
634
736
|
|
|
635
737
|
|
|
636
|
-
def load_bundle(path: Union[str, Path]) ->
|
|
637
|
-
"""Load an existing
|
|
638
|
-
return
|
|
738
|
+
def load_bundle(path: Union[str, Path]) -> Bundle:
|
|
739
|
+
"""Load an existing Bundle."""
|
|
740
|
+
return Bundle(path)
|
|
639
741
|
|
|
640
742
|
|
|
641
743
|
def create_bundle(
|
|
@@ -645,13 +747,13 @@ def create_bundle(
|
|
|
645
747
|
size_mm: Optional[Dict[str, float]] = None,
|
|
646
748
|
# Legacy support
|
|
647
749
|
node_type: Optional[str] = None,
|
|
648
|
-
) ->
|
|
649
|
-
"""Create a new
|
|
750
|
+
) -> Bundle:
|
|
751
|
+
"""Create a new Bundle."""
|
|
650
752
|
if node_type is not None:
|
|
651
753
|
kind = node_type
|
|
652
|
-
return
|
|
754
|
+
return Bundle(path, create=True, kind=kind, name=name, size_mm=size_mm)
|
|
653
755
|
|
|
654
756
|
|
|
655
|
-
__all__ = ["
|
|
757
|
+
__all__ = ["Bundle", "load_bundle", "create_bundle", "from_matplotlib"]
|
|
656
758
|
|
|
657
759
|
# EOF
|