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
scitex/capture/grid.py
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2026-01-01 19:50:00 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/capture/grid.py
|
|
4
|
+
# ----------------------------------------
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
__FILE__ = "./src/scitex/capture/grid.py"
|
|
10
|
+
__DIR__ = os.path.dirname(__FILE__)
|
|
11
|
+
# ----------------------------------------
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
Grid overlay functionality for screenshot coordinate mapping.
|
|
15
|
+
|
|
16
|
+
Provides utilities to draw coordinate grids on screenshots,
|
|
17
|
+
helping AI agents understand screen positions for precise targeting.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Tuple
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def draw_grid_overlay(
|
|
25
|
+
filepath: str,
|
|
26
|
+
grid_spacing: int = 100,
|
|
27
|
+
output_path: str = None,
|
|
28
|
+
grid_color: Tuple[int, int, int] = (255, 0, 0),
|
|
29
|
+
text_color: Tuple[int, int, int] = (255, 255, 0),
|
|
30
|
+
line_width: int = 1,
|
|
31
|
+
show_coordinates: bool = True,
|
|
32
|
+
) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Draw a coordinate grid overlay on a screenshot image.
|
|
35
|
+
|
|
36
|
+
This helps AI agents and users understand screen coordinates for
|
|
37
|
+
precise click targeting and UI element identification.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
filepath : str
|
|
42
|
+
Path to the input image
|
|
43
|
+
grid_spacing : int
|
|
44
|
+
Pixels between grid lines (default: 100)
|
|
45
|
+
output_path : str, optional
|
|
46
|
+
Output path (default: adds '_grid' suffix to input)
|
|
47
|
+
grid_color : tuple
|
|
48
|
+
RGB color for grid lines (default: red)
|
|
49
|
+
text_color : tuple
|
|
50
|
+
RGB color for coordinate labels (default: yellow)
|
|
51
|
+
line_width : int
|
|
52
|
+
Width of grid lines in pixels (default: 1)
|
|
53
|
+
show_coordinates : bool
|
|
54
|
+
Whether to show coordinate labels (default: True)
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
str
|
|
59
|
+
Path to the output image with grid overlay
|
|
60
|
+
|
|
61
|
+
Examples
|
|
62
|
+
--------
|
|
63
|
+
>>> from scitex.capture.grid import draw_grid_overlay
|
|
64
|
+
>>> draw_grid_overlay("/path/to/screenshot.jpg")
|
|
65
|
+
'/path/to/screenshot_grid.jpg'
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
69
|
+
except ImportError:
|
|
70
|
+
raise ImportError(
|
|
71
|
+
"PIL (Pillow) is required for grid overlay. "
|
|
72
|
+
"Install with: pip install Pillow"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Load image
|
|
76
|
+
img = Image.open(filepath)
|
|
77
|
+
draw = ImageDraw.Draw(img)
|
|
78
|
+
width, height = img.size
|
|
79
|
+
|
|
80
|
+
# Try to load a font for coordinate labels
|
|
81
|
+
font = None
|
|
82
|
+
if show_coordinates:
|
|
83
|
+
font = _get_font(size=12)
|
|
84
|
+
|
|
85
|
+
# Draw vertical lines
|
|
86
|
+
for x in range(0, width, grid_spacing):
|
|
87
|
+
draw.line([(x, 0), (x, height)], fill=grid_color, width=line_width)
|
|
88
|
+
if show_coordinates and font:
|
|
89
|
+
draw.text((x + 2, 10), str(x), fill=text_color, font=font)
|
|
90
|
+
|
|
91
|
+
# Draw horizontal lines
|
|
92
|
+
for y in range(0, height, grid_spacing):
|
|
93
|
+
draw.line([(0, y), (width, y)], fill=grid_color, width=line_width)
|
|
94
|
+
if show_coordinates and font:
|
|
95
|
+
draw.text((10, y + 2), str(y), fill=text_color, font=font)
|
|
96
|
+
|
|
97
|
+
# Generate output path
|
|
98
|
+
if output_path is None:
|
|
99
|
+
path_obj = Path(filepath)
|
|
100
|
+
output_path = str(path_obj.parent / f"{path_obj.stem}_grid{path_obj.suffix}")
|
|
101
|
+
|
|
102
|
+
# Save
|
|
103
|
+
if filepath.lower().endswith((".jpg", ".jpeg")):
|
|
104
|
+
img.save(output_path, "JPEG", quality=85)
|
|
105
|
+
else:
|
|
106
|
+
img.save(output_path)
|
|
107
|
+
|
|
108
|
+
return output_path
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _get_font(size: int = 12):
|
|
112
|
+
"""Get a monospace font for coordinate labels."""
|
|
113
|
+
try:
|
|
114
|
+
from PIL import ImageFont
|
|
115
|
+
except ImportError:
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
# Try common system fonts
|
|
119
|
+
font_paths = [
|
|
120
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
121
|
+
"/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf",
|
|
122
|
+
"/usr/share/fonts/truetype/freefont/FreeMono.ttf",
|
|
123
|
+
"C:/Windows/Fonts/consola.ttf",
|
|
124
|
+
"C:/Windows/Fonts/cour.ttf",
|
|
125
|
+
"/System/Library/Fonts/Monaco.ttf",
|
|
126
|
+
"/System/Library/Fonts/Menlo.ttc",
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
for font_path in font_paths:
|
|
130
|
+
if Path(font_path).exists():
|
|
131
|
+
try:
|
|
132
|
+
return ImageFont.truetype(font_path, size)
|
|
133
|
+
except:
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# Fallback to default
|
|
137
|
+
try:
|
|
138
|
+
return ImageFont.load_default()
|
|
139
|
+
except:
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def add_monitor_info_overlay(
|
|
144
|
+
filepath: str,
|
|
145
|
+
monitor_info: dict,
|
|
146
|
+
output_path: str = None,
|
|
147
|
+
) -> str:
|
|
148
|
+
"""
|
|
149
|
+
Add monitor boundary and info overlay to a multi-monitor screenshot.
|
|
150
|
+
|
|
151
|
+
Parameters
|
|
152
|
+
----------
|
|
153
|
+
filepath : str
|
|
154
|
+
Path to the input image
|
|
155
|
+
monitor_info : dict
|
|
156
|
+
Dictionary with monitor information from get_info()
|
|
157
|
+
output_path : str, optional
|
|
158
|
+
Output path (default: adds '_monitors' suffix)
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
str
|
|
163
|
+
Path to the output image with monitor overlay
|
|
164
|
+
"""
|
|
165
|
+
try:
|
|
166
|
+
from PIL import Image, ImageDraw
|
|
167
|
+
except ImportError:
|
|
168
|
+
raise ImportError("PIL (Pillow) is required")
|
|
169
|
+
|
|
170
|
+
img = Image.open(filepath)
|
|
171
|
+
draw = ImageDraw.Draw(img)
|
|
172
|
+
font = _get_font(size=14)
|
|
173
|
+
|
|
174
|
+
# Draw monitor boundaries and labels
|
|
175
|
+
monitors = monitor_info.get("Monitors", {}).get("Details", [])
|
|
176
|
+
colors = [
|
|
177
|
+
(255, 0, 0), # Red
|
|
178
|
+
(0, 255, 0), # Green
|
|
179
|
+
(0, 0, 255), # Blue
|
|
180
|
+
(255, 255, 0), # Yellow
|
|
181
|
+
(255, 0, 255), # Magenta
|
|
182
|
+
(0, 255, 255), # Cyan
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
for i, mon in enumerate(monitors):
|
|
186
|
+
color = colors[i % len(colors)]
|
|
187
|
+
x = mon.get("X", 0)
|
|
188
|
+
y = mon.get("Y", 0)
|
|
189
|
+
w = mon.get("Width", 0)
|
|
190
|
+
h = mon.get("Height", 0)
|
|
191
|
+
|
|
192
|
+
# Offset for combined image (Y might be negative)
|
|
193
|
+
# Find minimum Y to offset
|
|
194
|
+
min_y = min(m.get("Y", 0) for m in monitors) if monitors else 0
|
|
195
|
+
offset_y = -min_y if min_y < 0 else 0
|
|
196
|
+
|
|
197
|
+
# Draw rectangle border
|
|
198
|
+
rect_y = y + offset_y
|
|
199
|
+
draw.rectangle(
|
|
200
|
+
[(x, rect_y), (x + w - 1, rect_y + h - 1)],
|
|
201
|
+
outline=color,
|
|
202
|
+
width=3,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Draw label
|
|
206
|
+
label = f"Monitor {i}: {w}x{h} @ ({x},{y})"
|
|
207
|
+
if mon.get("Primary"):
|
|
208
|
+
label += " [PRIMARY]"
|
|
209
|
+
|
|
210
|
+
if font:
|
|
211
|
+
draw.text((x + 10, rect_y + 10), label, fill=color, font=font)
|
|
212
|
+
|
|
213
|
+
# Generate output path
|
|
214
|
+
if output_path is None:
|
|
215
|
+
path_obj = Path(filepath)
|
|
216
|
+
output_path = str(
|
|
217
|
+
path_obj.parent / f"{path_obj.stem}_monitors{path_obj.suffix}"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Save
|
|
221
|
+
if filepath.lower().endswith((".jpg", ".jpeg")):
|
|
222
|
+
img.save(output_path, "JPEG", quality=85)
|
|
223
|
+
else:
|
|
224
|
+
img.save(output_path)
|
|
225
|
+
|
|
226
|
+
return output_path
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def draw_cursor_overlay(
|
|
230
|
+
filepath: str,
|
|
231
|
+
cursor_pos: Tuple[int, int] = None,
|
|
232
|
+
output_path: str = None,
|
|
233
|
+
marker_color: Tuple[int, int, int] = (0, 255, 0),
|
|
234
|
+
marker_size: int = 20,
|
|
235
|
+
show_coords: bool = True,
|
|
236
|
+
capture_mode: str = "all", # "all" for all monitors, or monitor index "0", "1", etc.
|
|
237
|
+
) -> str:
|
|
238
|
+
"""
|
|
239
|
+
Draw cursor position marker on a screenshot.
|
|
240
|
+
|
|
241
|
+
Helps verify cursor coordinates for UI automation debugging.
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
filepath : str
|
|
246
|
+
Path to the input image
|
|
247
|
+
cursor_pos : tuple, optional
|
|
248
|
+
System coordinates (x, y). If None, gets current cursor position.
|
|
249
|
+
output_path : str, optional
|
|
250
|
+
Output path (default: adds '_cursor' suffix)
|
|
251
|
+
marker_color : tuple
|
|
252
|
+
RGB color for cursor marker (default: green)
|
|
253
|
+
marker_size : int
|
|
254
|
+
Size of the crosshair marker (default: 20)
|
|
255
|
+
show_coords : bool
|
|
256
|
+
Whether to show coordinate text (default: True)
|
|
257
|
+
capture_mode : str
|
|
258
|
+
"all" for all-monitor capture, or "0", "1" etc. for specific monitor
|
|
259
|
+
|
|
260
|
+
Returns
|
|
261
|
+
-------
|
|
262
|
+
str
|
|
263
|
+
Path to the output image with cursor overlay
|
|
264
|
+
"""
|
|
265
|
+
try:
|
|
266
|
+
from PIL import Image, ImageDraw
|
|
267
|
+
except ImportError:
|
|
268
|
+
raise ImportError("PIL (Pillow) is required")
|
|
269
|
+
|
|
270
|
+
# Get cursor position if not provided
|
|
271
|
+
if cursor_pos is None:
|
|
272
|
+
cursor_pos = _get_cursor_position()
|
|
273
|
+
if cursor_pos is None:
|
|
274
|
+
raise RuntimeError("Could not get cursor position")
|
|
275
|
+
|
|
276
|
+
sys_x, sys_y = cursor_pos
|
|
277
|
+
|
|
278
|
+
# Get display info to calculate proper offsets
|
|
279
|
+
display_info = get_display_info()
|
|
280
|
+
monitors = display_info.get("monitors", [])
|
|
281
|
+
|
|
282
|
+
# Calculate image coordinate offset based on capture mode
|
|
283
|
+
if capture_mode == "all" and monitors:
|
|
284
|
+
# For all-monitor capture: offset by the minimum X and Y
|
|
285
|
+
min_x = min(m.get("X", 0) for m in monitors)
|
|
286
|
+
min_y = min(m.get("Y", 0) for m in monitors)
|
|
287
|
+
img_x = sys_x - min_x
|
|
288
|
+
img_y = sys_y - min_y
|
|
289
|
+
elif monitors and capture_mode.isdigit():
|
|
290
|
+
# For single monitor capture: offset by that monitor's position
|
|
291
|
+
mon_idx = int(capture_mode)
|
|
292
|
+
if mon_idx < len(monitors):
|
|
293
|
+
mon = monitors[mon_idx]
|
|
294
|
+
img_x = sys_x - mon.get("X", 0)
|
|
295
|
+
img_y = sys_y - mon.get("Y", 0)
|
|
296
|
+
else:
|
|
297
|
+
img_x, img_y = sys_x, sys_y
|
|
298
|
+
else:
|
|
299
|
+
# Fallback: no offset
|
|
300
|
+
img_x, img_y = sys_x, sys_y
|
|
301
|
+
|
|
302
|
+
img = Image.open(filepath)
|
|
303
|
+
draw = ImageDraw.Draw(img)
|
|
304
|
+
width, height = img.size
|
|
305
|
+
|
|
306
|
+
# Find which monitor the cursor is on
|
|
307
|
+
cursor_monitor = "?"
|
|
308
|
+
for i, mon in enumerate(monitors):
|
|
309
|
+
mx, my = mon.get("X", 0), mon.get("Y", 0)
|
|
310
|
+
mw, mh = mon.get("Width", 0), mon.get("Height", 0)
|
|
311
|
+
if mx <= sys_x < mx + mw and my <= sys_y < my + mh:
|
|
312
|
+
cursor_monitor = str(i)
|
|
313
|
+
break
|
|
314
|
+
|
|
315
|
+
# Check if cursor is within image bounds
|
|
316
|
+
if 0 <= img_x < width and 0 <= img_y < height:
|
|
317
|
+
# Draw crosshair
|
|
318
|
+
half = marker_size // 2
|
|
319
|
+
# Horizontal line
|
|
320
|
+
draw.line(
|
|
321
|
+
[(img_x - half, img_y), (img_x + half, img_y)],
|
|
322
|
+
fill=marker_color,
|
|
323
|
+
width=2,
|
|
324
|
+
)
|
|
325
|
+
# Vertical line
|
|
326
|
+
draw.line(
|
|
327
|
+
[(img_x, img_y - half), (img_x, img_y + half)],
|
|
328
|
+
fill=marker_color,
|
|
329
|
+
width=2,
|
|
330
|
+
)
|
|
331
|
+
# Center dot
|
|
332
|
+
draw.ellipse(
|
|
333
|
+
[(img_x - 3, img_y - 3), (img_x + 3, img_y + 3)],
|
|
334
|
+
fill=marker_color,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Draw coordinate text with monitor info
|
|
338
|
+
if show_coords:
|
|
339
|
+
font = _get_font(size=12)
|
|
340
|
+
text = f"Mon:{cursor_monitor} Sys:({sys_x},{sys_y}) Img:({img_x},{img_y})"
|
|
341
|
+
if font:
|
|
342
|
+
draw.text((img_x + 10, img_y + 10), text, fill=marker_color, font=font)
|
|
343
|
+
else:
|
|
344
|
+
# Cursor outside image - show info at corner
|
|
345
|
+
font = _get_font(size=12)
|
|
346
|
+
text = f"Outside image: Mon:{cursor_monitor} Sys:({sys_x},{sys_y}) Img:({img_x},{img_y})"
|
|
347
|
+
if font:
|
|
348
|
+
draw.text((10, height - 30), text, fill=(255, 0, 0), font=font)
|
|
349
|
+
|
|
350
|
+
# Generate output path
|
|
351
|
+
if output_path is None:
|
|
352
|
+
path_obj = Path(filepath)
|
|
353
|
+
output_path = str(path_obj.parent / f"{path_obj.stem}_cursor{path_obj.suffix}")
|
|
354
|
+
|
|
355
|
+
# Save
|
|
356
|
+
if filepath.lower().endswith((".jpg", ".jpeg")):
|
|
357
|
+
img.save(output_path, "JPEG", quality=85)
|
|
358
|
+
else:
|
|
359
|
+
img.save(output_path)
|
|
360
|
+
|
|
361
|
+
return output_path
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _get_cursor_position() -> Tuple[int, int]:
|
|
365
|
+
"""Get current cursor position from Windows via PowerShell."""
|
|
366
|
+
import subprocess
|
|
367
|
+
|
|
368
|
+
ps_script = """
|
|
369
|
+
Add-Type @"
|
|
370
|
+
using System;
|
|
371
|
+
using System.Runtime.InteropServices;
|
|
372
|
+
public class CursorPos {
|
|
373
|
+
[DllImport("user32.dll")]
|
|
374
|
+
public static extern bool GetCursorPos(out POINT lpPoint);
|
|
375
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
376
|
+
public struct POINT { public int X; public int Y; }
|
|
377
|
+
}
|
|
378
|
+
"@
|
|
379
|
+
$pos = New-Object CursorPos+POINT
|
|
380
|
+
[CursorPos]::GetCursorPos([ref]$pos) | Out-Null
|
|
381
|
+
Write-Output "$($pos.X),$($pos.Y)"
|
|
382
|
+
"""
|
|
383
|
+
try:
|
|
384
|
+
result = subprocess.run(
|
|
385
|
+
["powershell.exe", "-Command", ps_script],
|
|
386
|
+
capture_output=True,
|
|
387
|
+
text=True,
|
|
388
|
+
timeout=5,
|
|
389
|
+
)
|
|
390
|
+
if result.returncode == 0:
|
|
391
|
+
parts = result.stdout.strip().split(",")
|
|
392
|
+
return (int(parts[0]), int(parts[1]))
|
|
393
|
+
except Exception:
|
|
394
|
+
pass
|
|
395
|
+
return None
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _get_dpi_scale() -> float:
|
|
399
|
+
"""Get Windows DPI scaling factor via PowerShell."""
|
|
400
|
+
import subprocess
|
|
401
|
+
|
|
402
|
+
ps_script = """
|
|
403
|
+
Add-Type @"
|
|
404
|
+
using System;
|
|
405
|
+
using System.Runtime.InteropServices;
|
|
406
|
+
public class DpiInfo {
|
|
407
|
+
[DllImport("user32.dll")]
|
|
408
|
+
public static extern IntPtr GetDC(IntPtr hwnd);
|
|
409
|
+
[DllImport("gdi32.dll")]
|
|
410
|
+
public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
|
|
411
|
+
[DllImport("user32.dll")]
|
|
412
|
+
public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);
|
|
413
|
+
public const int LOGPIXELSX = 88;
|
|
414
|
+
}
|
|
415
|
+
"@
|
|
416
|
+
$hdc = [DpiInfo]::GetDC([IntPtr]::Zero)
|
|
417
|
+
$dpi = [DpiInfo]::GetDeviceCaps($hdc, 88)
|
|
418
|
+
[DpiInfo]::ReleaseDC([IntPtr]::Zero, $hdc) | Out-Null
|
|
419
|
+
$scale = $dpi / 96.0
|
|
420
|
+
Write-Output $scale
|
|
421
|
+
"""
|
|
422
|
+
try:
|
|
423
|
+
result = subprocess.run(
|
|
424
|
+
["powershell.exe", "-Command", ps_script],
|
|
425
|
+
capture_output=True,
|
|
426
|
+
text=True,
|
|
427
|
+
timeout=5,
|
|
428
|
+
)
|
|
429
|
+
if result.returncode == 0:
|
|
430
|
+
return float(result.stdout.strip())
|
|
431
|
+
except Exception:
|
|
432
|
+
pass
|
|
433
|
+
return 1.0
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def get_display_info() -> dict:
|
|
437
|
+
"""Get comprehensive display info including DPI, resolution, monitors."""
|
|
438
|
+
import subprocess
|
|
439
|
+
|
|
440
|
+
ps_script = """
|
|
441
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
442
|
+
$screens = [System.Windows.Forms.Screen]::AllScreens
|
|
443
|
+
$info = @()
|
|
444
|
+
foreach ($s in $screens) {
|
|
445
|
+
$info += @{
|
|
446
|
+
Name = $s.DeviceName
|
|
447
|
+
Primary = $s.Primary
|
|
448
|
+
X = $s.Bounds.X
|
|
449
|
+
Y = $s.Bounds.Y
|
|
450
|
+
Width = $s.Bounds.Width
|
|
451
|
+
Height = $s.Bounds.Height
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
$info | ConvertTo-Json -Compress
|
|
455
|
+
"""
|
|
456
|
+
try:
|
|
457
|
+
result = subprocess.run(
|
|
458
|
+
["powershell.exe", "-Command", ps_script],
|
|
459
|
+
capture_output=True,
|
|
460
|
+
text=True,
|
|
461
|
+
timeout=5,
|
|
462
|
+
)
|
|
463
|
+
if result.returncode == 0:
|
|
464
|
+
import json
|
|
465
|
+
|
|
466
|
+
monitors = json.loads(result.stdout.strip())
|
|
467
|
+
if not isinstance(monitors, list):
|
|
468
|
+
monitors = [monitors]
|
|
469
|
+
dpi_scale = _get_dpi_scale()
|
|
470
|
+
return {
|
|
471
|
+
"monitors": monitors,
|
|
472
|
+
"dpi_scale": dpi_scale,
|
|
473
|
+
"dpi_percent": int(dpi_scale * 100),
|
|
474
|
+
}
|
|
475
|
+
except Exception:
|
|
476
|
+
pass
|
|
477
|
+
return {"monitors": [], "dpi_scale": 1.0, "dpi_percent": 100}
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
__all__ = [
|
|
481
|
+
"draw_grid_overlay",
|
|
482
|
+
"add_monitor_info_overlay",
|
|
483
|
+
"draw_cursor_overlay",
|
|
484
|
+
"get_display_info",
|
|
485
|
+
]
|
|
486
|
+
|
|
487
|
+
# EOF
|