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,241 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MCP tool definitions for SciTeX Capture.
|
|
4
|
+
|
|
5
|
+
This module contains the tool handler implementations that are
|
|
6
|
+
registered with the MCP server.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import base64
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, Optional
|
|
14
|
+
|
|
15
|
+
from scitex import capture
|
|
16
|
+
|
|
17
|
+
from .grid import draw_cursor_overlay, draw_grid_overlay
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_capture_dir() -> Path:
|
|
21
|
+
"""Get the screenshot capture directory."""
|
|
22
|
+
import os
|
|
23
|
+
import shutil
|
|
24
|
+
|
|
25
|
+
SCITEX_BASE_DIR = Path(os.getenv("SCITEX_DIR", Path.home() / ".scitex"))
|
|
26
|
+
new_dir = SCITEX_BASE_DIR / "capture"
|
|
27
|
+
old_dir = Path.home() / ".cache" / "cammy"
|
|
28
|
+
|
|
29
|
+
new_dir.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
|
|
31
|
+
if old_dir.exists():
|
|
32
|
+
new_screenshots = list(new_dir.glob("*.jpg"))
|
|
33
|
+
if not new_screenshots or len(new_screenshots) == 0:
|
|
34
|
+
try:
|
|
35
|
+
for img in old_dir.glob("*.jpg"):
|
|
36
|
+
shutil.move(str(img), str(new_dir / img.name))
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
return new_dir
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class OverlayTools:
|
|
44
|
+
"""Tools for adding overlays to screenshots."""
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
async def add_cursor_overlay(
|
|
48
|
+
image_path: str,
|
|
49
|
+
cursor_x: Optional[int] = None,
|
|
50
|
+
cursor_y: Optional[int] = None,
|
|
51
|
+
output_path: Optional[str] = None,
|
|
52
|
+
monitor_offset_y: int = 1080,
|
|
53
|
+
) -> Dict[str, Any]:
|
|
54
|
+
"""Add cursor position marker overlay to a screenshot."""
|
|
55
|
+
try:
|
|
56
|
+
loop = asyncio.get_event_loop()
|
|
57
|
+
|
|
58
|
+
cursor_pos = None
|
|
59
|
+
if cursor_x is not None and cursor_y is not None:
|
|
60
|
+
cursor_pos = (cursor_x, cursor_y)
|
|
61
|
+
|
|
62
|
+
result_path = await loop.run_in_executor(
|
|
63
|
+
None,
|
|
64
|
+
lambda: draw_cursor_overlay(
|
|
65
|
+
filepath=image_path,
|
|
66
|
+
cursor_pos=cursor_pos,
|
|
67
|
+
output_path=output_path,
|
|
68
|
+
monitor_offset_y=monitor_offset_y,
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"success": True,
|
|
74
|
+
"path": result_path,
|
|
75
|
+
"message": f"Cursor overlay added to {result_path}",
|
|
76
|
+
"cursor_position": cursor_pos,
|
|
77
|
+
"monitor_offset_y": monitor_offset_y,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
except Exception as e:
|
|
81
|
+
return {"success": False, "error": str(e)}
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
async def add_grid_overlay(
|
|
85
|
+
image_path: str,
|
|
86
|
+
grid_spacing: int = 100,
|
|
87
|
+
output_path: Optional[str] = None,
|
|
88
|
+
) -> Dict[str, Any]:
|
|
89
|
+
"""Add coordinate grid overlay to a screenshot."""
|
|
90
|
+
try:
|
|
91
|
+
loop = asyncio.get_event_loop()
|
|
92
|
+
|
|
93
|
+
result_path = await loop.run_in_executor(
|
|
94
|
+
None,
|
|
95
|
+
lambda: draw_grid_overlay(
|
|
96
|
+
filepath=image_path,
|
|
97
|
+
grid_spacing=grid_spacing,
|
|
98
|
+
output_path=output_path,
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
"success": True,
|
|
104
|
+
"path": result_path,
|
|
105
|
+
"message": f"Grid overlay ({grid_spacing}px) added to {result_path}",
|
|
106
|
+
"grid_spacing": grid_spacing,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
return {"success": False, "error": str(e)}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class CaptureTools:
|
|
114
|
+
"""Tools for screenshot capture operations."""
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
async def capture_screenshot(
|
|
118
|
+
message=None,
|
|
119
|
+
monitor_id=0,
|
|
120
|
+
all=False,
|
|
121
|
+
app=None,
|
|
122
|
+
url=None,
|
|
123
|
+
quality=85,
|
|
124
|
+
return_base64=False,
|
|
125
|
+
) -> Dict[str, Any]:
|
|
126
|
+
"""Capture a screenshot."""
|
|
127
|
+
try:
|
|
128
|
+
loop = asyncio.get_event_loop()
|
|
129
|
+
|
|
130
|
+
def do_capture():
|
|
131
|
+
return capture.snap(
|
|
132
|
+
message=message,
|
|
133
|
+
quality=quality,
|
|
134
|
+
monitor_id=monitor_id,
|
|
135
|
+
all=all,
|
|
136
|
+
app=app,
|
|
137
|
+
url=url,
|
|
138
|
+
verbose=True,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
path = await loop.run_in_executor(None, do_capture)
|
|
142
|
+
|
|
143
|
+
if not path:
|
|
144
|
+
return {"success": False, "error": "Failed to capture screenshot"}
|
|
145
|
+
|
|
146
|
+
category = "stderr" if "-stderr.jpg" in path else "stdout"
|
|
147
|
+
|
|
148
|
+
result = {
|
|
149
|
+
"success": True,
|
|
150
|
+
"path": path,
|
|
151
|
+
"category": category,
|
|
152
|
+
"message": f"Screenshot saved to {path}",
|
|
153
|
+
"timestamp": datetime.now().isoformat(),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if return_base64 and path:
|
|
157
|
+
with open(path, "rb") as f:
|
|
158
|
+
result["base64"] = base64.b64encode(f.read()).decode()
|
|
159
|
+
|
|
160
|
+
return result
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
return {"success": False, "error": str(e)}
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
async def capture_window_tool(
|
|
167
|
+
window_handle: int, output_path: str = None, quality: int = 85
|
|
168
|
+
) -> Dict[str, Any]:
|
|
169
|
+
"""Capture a specific window by handle."""
|
|
170
|
+
try:
|
|
171
|
+
loop = asyncio.get_event_loop()
|
|
172
|
+
path = await loop.run_in_executor(
|
|
173
|
+
None, capture.capture_window, window_handle, output_path
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if path:
|
|
177
|
+
return {
|
|
178
|
+
"success": True,
|
|
179
|
+
"path": path,
|
|
180
|
+
"window_handle": window_handle,
|
|
181
|
+
"message": f"Window captured to {path}",
|
|
182
|
+
}
|
|
183
|
+
else:
|
|
184
|
+
return {
|
|
185
|
+
"success": False,
|
|
186
|
+
"error": f"Failed to capture window {window_handle}",
|
|
187
|
+
}
|
|
188
|
+
except Exception as e:
|
|
189
|
+
return {"success": False, "error": str(e)}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class InfoTools:
|
|
193
|
+
"""Tools for getting system information."""
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
async def get_info() -> Dict[str, Any]:
|
|
197
|
+
"""Enumerate all monitors and virtual desktops."""
|
|
198
|
+
try:
|
|
199
|
+
loop = asyncio.get_event_loop()
|
|
200
|
+
info = await loop.run_in_executor(None, capture.get_info)
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
"success": True,
|
|
204
|
+
"monitors": info.get("Monitors", {}),
|
|
205
|
+
"virtual_desktops": info.get("VirtualDesktops", {}),
|
|
206
|
+
"windows": info.get("Windows", {}),
|
|
207
|
+
"timestamp": info.get("Timestamp", ""),
|
|
208
|
+
}
|
|
209
|
+
except Exception as e:
|
|
210
|
+
return {"success": False, "error": str(e)}
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
async def list_windows() -> Dict[str, Any]:
|
|
214
|
+
"""List all visible windows."""
|
|
215
|
+
try:
|
|
216
|
+
loop = asyncio.get_event_loop()
|
|
217
|
+
info = await loop.run_in_executor(None, capture.get_info)
|
|
218
|
+
|
|
219
|
+
windows = info.get("Windows", {})
|
|
220
|
+
window_list = windows.get("Details", [])
|
|
221
|
+
|
|
222
|
+
formatted_windows = []
|
|
223
|
+
for win in window_list:
|
|
224
|
+
formatted_windows.append(
|
|
225
|
+
{
|
|
226
|
+
"handle": win.get("Handle"),
|
|
227
|
+
"title": win.get("Title"),
|
|
228
|
+
"process_name": win.get("ProcessName"),
|
|
229
|
+
"process_id": win.get("ProcessId"),
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
"success": True,
|
|
235
|
+
"windows": formatted_windows,
|
|
236
|
+
"count": len(formatted_windows),
|
|
237
|
+
"visible_count": windows.get("VisibleCount", 0),
|
|
238
|
+
"message": f"Found {len(formatted_windows)} windows",
|
|
239
|
+
}
|
|
240
|
+
except Exception as e:
|
|
241
|
+
return {"success": False, "error": str(e)}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""MCP utility functions for SciTeX Capture."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
# Directory configuration
|
|
9
|
+
SCITEX_BASE_DIR = Path(os.getenv("SCITEX_DIR", Path.home() / ".scitex"))
|
|
10
|
+
SCITEX_CAPTURE_DIR = SCITEX_BASE_DIR / "capture"
|
|
11
|
+
LEGACY_CAPTURE_DIR = Path.home() / ".cache" / "cammy"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_capture_dir() -> Path:
|
|
15
|
+
"""Get screenshot capture directory, migrating from legacy if needed."""
|
|
16
|
+
new_dir = SCITEX_CAPTURE_DIR
|
|
17
|
+
old_dir = LEGACY_CAPTURE_DIR
|
|
18
|
+
|
|
19
|
+
new_dir.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
|
|
21
|
+
if old_dir.exists():
|
|
22
|
+
new_screenshots = list(new_dir.glob("*.jpg"))
|
|
23
|
+
if not new_screenshots:
|
|
24
|
+
try:
|
|
25
|
+
for img in old_dir.glob("*.jpg"):
|
|
26
|
+
shutil.move(str(img), str(new_dir / img.name))
|
|
27
|
+
except Exception:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
return new_dir
|
scitex/cli/convert.py
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2025-12-19 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/cli/convert.py
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
CLI commands for converting legacy bundle formats to unified .stx format.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
scitex convert old_figure.figz # Convert to old_figure.stx
|
|
10
|
+
scitex convert old_figure.figz output.stx # Convert with custom output
|
|
11
|
+
scitex convert --batch ./figures/*.figz # Batch convert
|
|
12
|
+
scitex convert --validate output.stx # Validate bundle
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import List, Optional
|
|
18
|
+
|
|
19
|
+
import click
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.group()
|
|
23
|
+
def convert():
|
|
24
|
+
"""Convert and validate SciTeX bundle files.
|
|
25
|
+
|
|
26
|
+
\b
|
|
27
|
+
Convert legacy formats (.figz, .pltz, .statsz) to unified .stx format.
|
|
28
|
+
Supports single file conversion, batch conversion, and validation.
|
|
29
|
+
|
|
30
|
+
\b
|
|
31
|
+
Examples:
|
|
32
|
+
scitex convert file old_figure.figz # Convert single file
|
|
33
|
+
scitex convert file old_figure.figz -o new.stx # Custom output name
|
|
34
|
+
scitex convert batch ./figures/*.figz # Batch convert
|
|
35
|
+
scitex convert validate output.stx # Validate bundle
|
|
36
|
+
"""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@convert.command("file")
|
|
41
|
+
@click.argument("input_path", type=click.Path(exists=True))
|
|
42
|
+
@click.option(
|
|
43
|
+
"-o",
|
|
44
|
+
"--output",
|
|
45
|
+
type=click.Path(),
|
|
46
|
+
help="Output path (default: same name with .stx extension)",
|
|
47
|
+
)
|
|
48
|
+
@click.option(
|
|
49
|
+
"--overwrite",
|
|
50
|
+
is_flag=True,
|
|
51
|
+
help="Overwrite output file if it exists",
|
|
52
|
+
)
|
|
53
|
+
@click.option(
|
|
54
|
+
"--dry-run",
|
|
55
|
+
is_flag=True,
|
|
56
|
+
help="Show what would be done without writing files",
|
|
57
|
+
)
|
|
58
|
+
def convert_file(
|
|
59
|
+
input_path: str, output: Optional[str], overwrite: bool, dry_run: bool
|
|
60
|
+
):
|
|
61
|
+
"""Convert a single legacy bundle to .stx format.
|
|
62
|
+
|
|
63
|
+
\b
|
|
64
|
+
Supported input formats:
|
|
65
|
+
.figz - Figure bundles
|
|
66
|
+
.pltz - Plot bundles
|
|
67
|
+
.statsz - Statistics bundles
|
|
68
|
+
|
|
69
|
+
\b
|
|
70
|
+
Examples:
|
|
71
|
+
scitex convert file old_figure.figz
|
|
72
|
+
scitex convert file plot.pltz -o converted_plot.stx
|
|
73
|
+
scitex convert file stats.statsz --dry-run
|
|
74
|
+
"""
|
|
75
|
+
input_file = Path(input_path)
|
|
76
|
+
|
|
77
|
+
# Determine output path
|
|
78
|
+
if output:
|
|
79
|
+
output_file = Path(output)
|
|
80
|
+
else:
|
|
81
|
+
output_file = input_file.with_suffix(".stx")
|
|
82
|
+
|
|
83
|
+
# Check if already .stx
|
|
84
|
+
if input_file.suffix == ".stx":
|
|
85
|
+
click.secho(f"File is already in .stx format: {input_file}", fg="yellow")
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
# Validate input format
|
|
89
|
+
valid_extensions = (".figz", ".pltz", ".statsz")
|
|
90
|
+
if input_file.suffix not in valid_extensions:
|
|
91
|
+
click.secho(
|
|
92
|
+
f"Unsupported format: {input_file.suffix}. "
|
|
93
|
+
f"Supported: {', '.join(valid_extensions)}",
|
|
94
|
+
fg="red",
|
|
95
|
+
err=True,
|
|
96
|
+
)
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
|
|
99
|
+
# Check output exists
|
|
100
|
+
if output_file.exists() and not overwrite:
|
|
101
|
+
click.secho(
|
|
102
|
+
f"Output file exists: {output_file}. Use --overwrite to replace.",
|
|
103
|
+
fg="red",
|
|
104
|
+
err=True,
|
|
105
|
+
)
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
|
|
108
|
+
if dry_run:
|
|
109
|
+
click.echo(f"Would convert: {input_file} -> {output_file}")
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# Perform conversion
|
|
113
|
+
try:
|
|
114
|
+
_convert_bundle(input_file, output_file)
|
|
115
|
+
click.secho(f"Converted: {input_file} -> {output_file}", fg="green")
|
|
116
|
+
except Exception as e:
|
|
117
|
+
click.secho(f"Error converting {input_file}: {e}", fg="red", err=True)
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@convert.command("batch")
|
|
122
|
+
@click.argument("pattern", nargs=-1, required=True)
|
|
123
|
+
@click.option(
|
|
124
|
+
"-o",
|
|
125
|
+
"--output-dir",
|
|
126
|
+
type=click.Path(),
|
|
127
|
+
help="Output directory (default: same as input)",
|
|
128
|
+
)
|
|
129
|
+
@click.option(
|
|
130
|
+
"--overwrite",
|
|
131
|
+
is_flag=True,
|
|
132
|
+
help="Overwrite existing files",
|
|
133
|
+
)
|
|
134
|
+
@click.option(
|
|
135
|
+
"--dry-run",
|
|
136
|
+
is_flag=True,
|
|
137
|
+
help="Show what would be done without writing files",
|
|
138
|
+
)
|
|
139
|
+
def convert_batch(
|
|
140
|
+
pattern: tuple, output_dir: Optional[str], overwrite: bool, dry_run: bool
|
|
141
|
+
):
|
|
142
|
+
"""Batch convert multiple legacy bundles to .stx format.
|
|
143
|
+
|
|
144
|
+
\b
|
|
145
|
+
Examples:
|
|
146
|
+
scitex convert batch ./figures/*.figz
|
|
147
|
+
scitex convert batch ./plots/*.pltz -o ./converted/
|
|
148
|
+
scitex convert batch ./**/*.figz ./**/*.pltz --dry-run
|
|
149
|
+
"""
|
|
150
|
+
import glob
|
|
151
|
+
|
|
152
|
+
# Collect all files matching patterns
|
|
153
|
+
files: List[Path] = []
|
|
154
|
+
for pat in pattern:
|
|
155
|
+
matches = glob.glob(pat, recursive=True)
|
|
156
|
+
files.extend(Path(m) for m in matches)
|
|
157
|
+
|
|
158
|
+
# Filter to valid extensions
|
|
159
|
+
valid_extensions = (".figz", ".pltz", ".statsz")
|
|
160
|
+
files = [f for f in files if f.suffix in valid_extensions]
|
|
161
|
+
|
|
162
|
+
if not files:
|
|
163
|
+
click.secho("No matching files found.", fg="yellow")
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
click.echo(f"Found {len(files)} file(s) to convert")
|
|
167
|
+
|
|
168
|
+
# Determine output directory
|
|
169
|
+
out_dir = Path(output_dir) if output_dir else None
|
|
170
|
+
if out_dir and not dry_run:
|
|
171
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
172
|
+
|
|
173
|
+
# Convert each file
|
|
174
|
+
converted = 0
|
|
175
|
+
errors = 0
|
|
176
|
+
for input_file in files:
|
|
177
|
+
if out_dir:
|
|
178
|
+
output_file = out_dir / input_file.with_suffix(".stx").name
|
|
179
|
+
else:
|
|
180
|
+
output_file = input_file.with_suffix(".stx")
|
|
181
|
+
|
|
182
|
+
if output_file.exists() and not overwrite:
|
|
183
|
+
click.secho(f"Skipping (exists): {output_file}", fg="yellow")
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
if dry_run:
|
|
187
|
+
click.echo(f"Would convert: {input_file} -> {output_file}")
|
|
188
|
+
converted += 1
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
_convert_bundle(input_file, output_file)
|
|
193
|
+
click.secho(
|
|
194
|
+
f"Converted: {input_file.name} -> {output_file.name}", fg="green"
|
|
195
|
+
)
|
|
196
|
+
converted += 1
|
|
197
|
+
except Exception as e:
|
|
198
|
+
click.secho(f"Error: {input_file}: {e}", fg="red", err=True)
|
|
199
|
+
errors += 1
|
|
200
|
+
|
|
201
|
+
# Summary
|
|
202
|
+
click.echo()
|
|
203
|
+
click.echo(f"Converted: {converted}, Errors: {errors}")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@convert.command("validate")
|
|
207
|
+
@click.argument("paths", nargs=-1, required=True, type=click.Path(exists=True))
|
|
208
|
+
@click.option(
|
|
209
|
+
"--verbose",
|
|
210
|
+
"-v",
|
|
211
|
+
is_flag=True,
|
|
212
|
+
help="Show detailed validation info",
|
|
213
|
+
)
|
|
214
|
+
def validate_bundles(paths: tuple, verbose: bool):
|
|
215
|
+
"""Validate one or more .stx bundles.
|
|
216
|
+
|
|
217
|
+
\b
|
|
218
|
+
Checks:
|
|
219
|
+
- Valid ZIP structure
|
|
220
|
+
- spec.json present and valid
|
|
221
|
+
- Schema version
|
|
222
|
+
- Depth limits
|
|
223
|
+
- Circular references
|
|
224
|
+
|
|
225
|
+
\b
|
|
226
|
+
Examples:
|
|
227
|
+
scitex convert validate output.stx
|
|
228
|
+
scitex convert validate ./figures/*.stx --verbose
|
|
229
|
+
"""
|
|
230
|
+
# Use FTS instead of deprecated io.bundle
|
|
231
|
+
try:
|
|
232
|
+
from scitex.fts import FTS as ZipBundle
|
|
233
|
+
def validate_stx_bundle(path):
|
|
234
|
+
try:
|
|
235
|
+
bundle = ZipBundle(path)
|
|
236
|
+
return bundle.validate(level="strict")
|
|
237
|
+
except Exception as e:
|
|
238
|
+
return {"valid": False, "errors": [str(e)]}
|
|
239
|
+
except ImportError:
|
|
240
|
+
click.echo("Error: scitex.fts not available", err=True)
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
valid = 0
|
|
244
|
+
invalid = 0
|
|
245
|
+
|
|
246
|
+
for path_str in paths:
|
|
247
|
+
path = Path(path_str)
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
with ZipBundle(path, mode="r") as zb:
|
|
251
|
+
spec = zb.read_json("spec.json")
|
|
252
|
+
|
|
253
|
+
# Check schema
|
|
254
|
+
schema = spec.get("schema", {})
|
|
255
|
+
schema_name = schema.get("name", "unknown")
|
|
256
|
+
schema_version = schema.get("version", "unknown")
|
|
257
|
+
bundle_type = spec.get("type", "unknown")
|
|
258
|
+
bundle_id = spec.get("bundle_id", "missing")
|
|
259
|
+
|
|
260
|
+
if verbose:
|
|
261
|
+
click.echo(f"\n{path}:")
|
|
262
|
+
click.echo(f" Schema: {schema_name} v{schema_version}")
|
|
263
|
+
click.echo(f" Type: {bundle_type}")
|
|
264
|
+
click.echo(f" ID: {bundle_id}")
|
|
265
|
+
constraints = spec.get("constraints", {})
|
|
266
|
+
click.echo(f" Constraints: {constraints}")
|
|
267
|
+
|
|
268
|
+
# Validate structure
|
|
269
|
+
validate_stx_bundle(spec)
|
|
270
|
+
|
|
271
|
+
click.secho(f"VALID: {path}", fg="green")
|
|
272
|
+
valid += 1
|
|
273
|
+
|
|
274
|
+
except FileNotFoundError as e:
|
|
275
|
+
click.secho(f"INVALID: {path} - File not found: {e}", fg="red")
|
|
276
|
+
invalid += 1
|
|
277
|
+
except Exception as e:
|
|
278
|
+
click.secho(f"INVALID: {path} - {e}", fg="red")
|
|
279
|
+
invalid += 1
|
|
280
|
+
|
|
281
|
+
# Summary
|
|
282
|
+
click.echo()
|
|
283
|
+
click.echo(f"Valid: {valid}, Invalid: {invalid}")
|
|
284
|
+
|
|
285
|
+
if invalid > 0:
|
|
286
|
+
sys.exit(1)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@convert.command("info")
|
|
290
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
291
|
+
def bundle_info(path: str):
|
|
292
|
+
"""Show information about a bundle file.
|
|
293
|
+
|
|
294
|
+
\b
|
|
295
|
+
Displays:
|
|
296
|
+
- Format (stx vs legacy)
|
|
297
|
+
- Schema version
|
|
298
|
+
- Bundle type
|
|
299
|
+
- Contents summary
|
|
300
|
+
|
|
301
|
+
\b
|
|
302
|
+
Example:
|
|
303
|
+
scitex convert info figure.stx
|
|
304
|
+
"""
|
|
305
|
+
try:
|
|
306
|
+
from scitex.fts import FTS as ZipBundle
|
|
307
|
+
except ImportError:
|
|
308
|
+
click.echo("Error: scitex.fts not available", err=True)
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
bundle_path = Path(path)
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
with ZipBundle(bundle_path, mode="r") as zb:
|
|
315
|
+
spec = zb.read_json("spec.json")
|
|
316
|
+
files = zb.namelist()
|
|
317
|
+
|
|
318
|
+
# Basic info
|
|
319
|
+
click.echo(f"\nBundle: {bundle_path}")
|
|
320
|
+
click.echo(f"Extension: {bundle_path.suffix}")
|
|
321
|
+
click.echo(f"Size: {bundle_path.stat().st_size:,} bytes")
|
|
322
|
+
|
|
323
|
+
# Schema info
|
|
324
|
+
schema = spec.get("schema", {})
|
|
325
|
+
click.echo(
|
|
326
|
+
f"\nSchema: {schema.get('name', 'unknown')} v{schema.get('version', 'unknown')}"
|
|
327
|
+
)
|
|
328
|
+
click.echo(f"Type: {spec.get('type', 'unknown')}")
|
|
329
|
+
click.echo(f"Bundle ID: {spec.get('bundle_id', 'not set')}")
|
|
330
|
+
|
|
331
|
+
# Constraints
|
|
332
|
+
constraints = spec.get("constraints", {})
|
|
333
|
+
if constraints:
|
|
334
|
+
click.echo("\nConstraints:")
|
|
335
|
+
click.echo(f" allow_children: {constraints.get('allow_children', 'N/A')}")
|
|
336
|
+
click.echo(f" max_depth: {constraints.get('max_depth', 'N/A')}")
|
|
337
|
+
|
|
338
|
+
# Contents
|
|
339
|
+
click.echo(f"\nContents ({len(files)} files):")
|
|
340
|
+
for f in sorted(files)[:20]: # Show first 20
|
|
341
|
+
click.echo(f" {f}")
|
|
342
|
+
if len(files) > 20:
|
|
343
|
+
click.echo(f" ... and {len(files) - 20} more")
|
|
344
|
+
|
|
345
|
+
# Type-specific info
|
|
346
|
+
if spec.get("type") == "figure":
|
|
347
|
+
panels = spec.get("panels", [])
|
|
348
|
+
elements = spec.get("elements", [])
|
|
349
|
+
click.echo(f"\nPanels: {len(panels)}")
|
|
350
|
+
click.echo(f"Elements: {len(elements)}")
|
|
351
|
+
elif spec.get("type") == "plot":
|
|
352
|
+
click.echo(f"\nPlot type: {spec.get('plot_type', 'unknown')}")
|
|
353
|
+
elif spec.get("type") == "stats":
|
|
354
|
+
comparisons = spec.get("comparisons", [])
|
|
355
|
+
click.echo(f"\nComparisons: {len(comparisons)}")
|
|
356
|
+
|
|
357
|
+
except Exception as e:
|
|
358
|
+
click.secho(f"Error reading bundle: {e}", fg="red", err=True)
|
|
359
|
+
sys.exit(1)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _convert_bundle(input_path: Path, output_path: Path) -> None:
|
|
363
|
+
"""Convert a legacy bundle to .stx format.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
input_path: Path to legacy bundle (.figz, .pltz, .statsz)
|
|
367
|
+
output_path: Path for output .stx bundle
|
|
368
|
+
"""
|
|
369
|
+
import json
|
|
370
|
+
import tempfile
|
|
371
|
+
import zipfile
|
|
372
|
+
|
|
373
|
+
# Generate bundle ID and normalize spec - inline functions since io.bundle is deprecated
|
|
374
|
+
import uuid
|
|
375
|
+
def generate_bundle_id():
|
|
376
|
+
return str(uuid.uuid4())[:8]
|
|
377
|
+
def normalize_spec(spec):
|
|
378
|
+
return spec # FTS handles normalization internally
|
|
379
|
+
|
|
380
|
+
# Determine bundle type from extension
|
|
381
|
+
ext = input_path.suffix
|
|
382
|
+
type_map = {
|
|
383
|
+
".figz": "figure",
|
|
384
|
+
".pltz": "plot",
|
|
385
|
+
".statsz": "stats",
|
|
386
|
+
}
|
|
387
|
+
bundle_type = type_map.get(ext)
|
|
388
|
+
|
|
389
|
+
# Read input bundle
|
|
390
|
+
with zipfile.ZipFile(input_path, "r") as zf:
|
|
391
|
+
# Read spec
|
|
392
|
+
spec_data = zf.read("spec.json")
|
|
393
|
+
spec = json.loads(spec_data)
|
|
394
|
+
|
|
395
|
+
# Normalize to v2.0.0
|
|
396
|
+
normalized_spec = normalize_spec(spec, bundle_type)
|
|
397
|
+
|
|
398
|
+
# Ensure bundle_id
|
|
399
|
+
if "bundle_id" not in normalized_spec:
|
|
400
|
+
normalized_spec["bundle_id"] = generate_bundle_id()
|
|
401
|
+
|
|
402
|
+
# Copy all files to new bundle
|
|
403
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
404
|
+
# Extract all
|
|
405
|
+
zf.extractall(tmpdir)
|
|
406
|
+
|
|
407
|
+
# Write updated spec
|
|
408
|
+
spec_path = Path(tmpdir) / "spec.json"
|
|
409
|
+
with open(spec_path, "w") as f:
|
|
410
|
+
json.dump(normalized_spec, f, indent=2)
|
|
411
|
+
|
|
412
|
+
# Create output bundle
|
|
413
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
414
|
+
with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as out_zf:
|
|
415
|
+
for file_path in Path(tmpdir).rglob("*"):
|
|
416
|
+
if file_path.is_file():
|
|
417
|
+
arcname = file_path.relative_to(tmpdir)
|
|
418
|
+
out_zf.write(file_path, arcname)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
# EOF
|