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.
Files changed (415) hide show
  1. scitex/__init__.py +15 -7
  2. scitex/__version__.py +1 -2
  3. scitex/_install_guide.py +250 -0
  4. scitex/_optional_deps.py +206 -39
  5. scitex/ai/_gen_ai/_Groq.py +2 -4
  6. scitex/ai/_gen_ai/_OpenAI.py +5 -2
  7. scitex/ai/_gen_ai/_Perplexity.py +20 -6
  8. scitex/audio/__init__.py +24 -15
  9. scitex/audio/_cross_process_lock.py +139 -0
  10. scitex/audio/_mcp_handlers.py +256 -0
  11. scitex/audio/_mcp_tool_schemas.py +203 -0
  12. scitex/audio/engines/elevenlabs_engine.py +5 -2
  13. scitex/audio/mcp_server.py +98 -457
  14. scitex/bridge/__init__.py +30 -19
  15. scitex/bridge/_figrecipe.py +245 -0
  16. scitex/bridge/_helpers.py +2 -1
  17. scitex/bridge/_plt_vis.py +23 -10
  18. scitex/bridge/_stats_plt.py +18 -5
  19. scitex/bridge/_stats_vis.py +16 -2
  20. scitex/browser/__init__.py +84 -44
  21. scitex/browser/automation/__init__.py +5 -1
  22. scitex/browser/core/BrowserMixin.py +17 -4
  23. scitex/browser/core/__init__.py +11 -2
  24. scitex/browser/remote/CaptchaHandler.py +1 -1
  25. scitex/browser/remote/ZenRowsAPIClient.py +1 -1
  26. scitex/capture/grid.py +487 -0
  27. scitex/capture/mcp_handlers.py +401 -0
  28. scitex/capture/mcp_tool_defs.py +192 -0
  29. scitex/capture/mcp_tools.py +241 -0
  30. scitex/capture/mcp_utils.py +30 -0
  31. scitex/cli/convert.py +421 -0
  32. scitex/cli/main.py +6 -4
  33. scitex/datetime/__init__.py +46 -0
  34. scitex/datetime/_linspace.py +100 -0
  35. scitex/datetime/_normalize_timestamp.py +306 -0
  36. scitex/db/_delete_duplicates.py +4 -4
  37. scitex/db/_sqlite3/_delete_duplicates.py +11 -2
  38. scitex/dev/plt/__init__.py +61 -62
  39. scitex/dev/plt/demo_plotters/__init__.py +0 -0
  40. scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
  41. scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
  42. scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
  43. scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
  44. scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
  45. scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
  46. scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
  47. scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
  48. scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
  49. scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
  50. scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
  51. scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
  52. scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
  53. scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
  54. scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
  55. scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
  56. scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
  57. scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
  58. scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
  59. scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
  60. scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
  61. scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
  62. scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
  63. scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
  64. scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
  65. scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
  66. scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
  67. scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
  68. scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
  69. scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
  70. scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
  71. scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
  72. scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
  73. scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
  74. scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
  75. scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
  76. scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
  77. scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
  78. scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
  79. scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
  80. scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
  81. scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
  82. scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
  83. scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
  84. scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
  85. scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
  86. scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
  87. scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
  88. scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
  89. scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
  90. scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
  91. scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
  92. scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
  93. scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
  94. scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
  95. scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
  96. scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
  97. scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
  98. scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
  99. scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
  100. scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
  101. scitex/dev/plt/mpl/get_dir_ax.py +46 -0
  102. scitex/dev/plt/mpl/get_signatures.py +176 -0
  103. scitex/dev/plt/mpl/get_signatures_details.py +522 -0
  104. scitex/dict/_pop_keys.py +1 -7
  105. scitex/dsp/__init__.py +15 -10
  106. scitex/dsp/add_noise.py +5 -2
  107. scitex/dsp/example.py +35 -22
  108. scitex/dsp/filt.py +8 -3
  109. scitex/dsp/reference.py +3 -2
  110. scitex/dsp/utils/__init__.py +2 -1
  111. scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
  112. scitex/dt/__init__.py +39 -2
  113. scitex/errors.py +82 -521
  114. scitex/fig/__init__.py +4 -4
  115. scitex/fig/editor/edit/panel_loader.py +1 -1
  116. scitex/fig/io/_bundle.py +7 -7
  117. scitex/fts/README.md +262 -0
  118. scitex/fts/TODO.md +66 -0
  119. scitex/fts/__init__.py +90 -0
  120. scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
  121. scitex/fts/_bundle/_FTS.py +657 -0
  122. scitex/fts/_bundle/__init__.py +38 -0
  123. scitex/fts/_bundle/_children.py +216 -0
  124. scitex/fts/_bundle/_conversion/__init__.py +15 -0
  125. scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
  126. scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
  127. scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
  128. scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
  129. scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
  130. scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
  131. scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
  132. scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
  133. scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
  134. scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
  135. scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
  136. scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
  137. scitex/fts/_bundle/_extractors/__init__.py +32 -0
  138. scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
  139. scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
  140. scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
  141. scitex/fts/_bundle/_loader.py +134 -0
  142. scitex/fts/_bundle/_mpl_helpers.py +389 -0
  143. scitex/fts/_bundle/_saver.py +269 -0
  144. scitex/fts/_bundle/_storage.py +200 -0
  145. scitex/fts/_bundle/_utils/__init__.py +55 -0
  146. scitex/fts/_bundle/_utils/_const.py +26 -0
  147. scitex/fts/_bundle/_utils/_errors.py +73 -0
  148. scitex/fts/_bundle/_utils/_generate.py +21 -0
  149. scitex/fts/_bundle/_utils/_types.py +76 -0
  150. scitex/fts/_bundle/_validation.py +434 -0
  151. scitex/fts/_bundle/_zipbundle.py +165 -0
  152. scitex/fts/_fig/__init__.py +22 -0
  153. scitex/fts/_fig/_backend/__init__.py +53 -0
  154. scitex/fts/_fig/_backend/_export.py +165 -0
  155. scitex/fts/_fig/_backend/_parser.py +188 -0
  156. scitex/fts/_fig/_backend/_render.py +538 -0
  157. scitex/fts/_fig/_composite.py +345 -0
  158. scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
  159. scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
  160. scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
  161. scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
  162. scitex/fts/_fig/_dataclasses/__init__.py +47 -0
  163. scitex/fts/_fig/_editor/__init__.py +14 -0
  164. scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
  165. scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
  166. scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
  167. scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
  168. scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
  169. scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
  170. scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
  171. scitex/fts/_fig/_editor/_defaults.py +300 -0
  172. scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
  173. scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
  174. scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
  175. scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
  176. scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
  177. scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
  178. scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
  179. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
  180. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
  181. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
  182. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
  183. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
  184. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
  185. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
  186. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
  187. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
  188. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
  189. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
  190. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
  191. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
  192. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
  193. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
  194. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
  195. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
  196. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
  197. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
  198. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
  199. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
  200. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
  201. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
  202. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
  203. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
  204. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
  205. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
  206. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
  207. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
  208. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
  209. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
  210. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
  211. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
  212. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
  213. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
  214. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
  215. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
  216. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
  217. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
  218. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
  219. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
  220. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
  221. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
  222. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
  223. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
  224. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
  225. scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
  226. scitex/fts/_fig/_models/_Annotations.py +115 -0
  227. scitex/fts/_fig/_models/_Axes.py +152 -0
  228. scitex/fts/_fig/_models/_Figure.py +138 -0
  229. scitex/fts/_fig/_models/_Guides.py +104 -0
  230. scitex/fts/_fig/_models/_Plot.py +123 -0
  231. scitex/fts/_fig/_models/_Styles.py +245 -0
  232. scitex/fts/_fig/_models/__init__.py +80 -0
  233. scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
  234. scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
  235. scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
  236. scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
  237. scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
  238. scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
  239. scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
  240. scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
  241. scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
  242. scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
  243. scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
  244. scitex/fts/_fig/_utils/__init__.py +129 -0
  245. scitex/fts/_fig/_utils/_auto_layout.py +127 -0
  246. scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
  247. scitex/fts/_fig/_utils/_const_sizes.py +48 -0
  248. scitex/fts/_fig/_utils/_convert_coords.py +77 -0
  249. scitex/fts/_fig/_utils/_get_template.py +178 -0
  250. scitex/fts/_fig/_utils/_normalize.py +73 -0
  251. scitex/fts/_fig/_utils/_plot_layout.py +397 -0
  252. scitex/fts/_fig/_utils/_validate.py +197 -0
  253. scitex/fts/_kinds/__init__.py +45 -0
  254. scitex/fts/_kinds/_figure/__init__.py +19 -0
  255. scitex/fts/_kinds/_figure/_composite.py +345 -0
  256. scitex/fts/_kinds/_plot/__init__.py +25 -0
  257. scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
  258. scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
  259. scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
  260. scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
  261. scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
  262. scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
  263. scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
  264. scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
  265. scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
  266. scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
  267. scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
  268. scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
  269. scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
  270. scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
  271. scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
  272. scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
  273. scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
  274. scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
  275. scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
  276. scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
  277. scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
  278. scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
  279. scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
  280. scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
  281. scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
  282. scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
  283. scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
  284. scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
  285. scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
  286. scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
  287. scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
  288. scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
  289. scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
  290. scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
  291. scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
  292. scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
  293. scitex/fts/_kinds/_shape/__init__.py +141 -0
  294. scitex/fts/_kinds/_stats/__init__.py +56 -0
  295. scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
  296. scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
  297. scitex/fts/_kinds/_table/__init__.py +72 -0
  298. scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
  299. scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
  300. scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
  301. scitex/fts/_kinds/_table/_latex/_export.py +279 -0
  302. scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
  303. scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
  304. scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
  305. scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
  306. scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
  307. scitex/fts/_kinds/_text/__init__.py +77 -0
  308. scitex/fts/_schemas/data_info.schema.json +75 -0
  309. scitex/fts/_schemas/encoding.schema.json +90 -0
  310. scitex/fts/_schemas/node.schema.json +145 -0
  311. scitex/fts/_schemas/render_manifest.schema.json +62 -0
  312. scitex/fts/_schemas/stats.schema.json +132 -0
  313. scitex/fts/_schemas/theme.schema.json +141 -0
  314. scitex/fts/_stats/__init__.py +48 -0
  315. scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
  316. scitex/fts/_stats/_dataclasses/__init__.py +48 -0
  317. scitex/fts/_tables/__init__.py +65 -0
  318. scitex/fts/_tables/_latex/__init__.py +93 -0
  319. scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
  320. scitex/fts/_tables/_latex/_editor/_app.py +725 -0
  321. scitex/fts/_tables/_latex/_export.py +279 -0
  322. scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
  323. scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
  324. scitex/fts/_tables/_latex/_table_exporter.py +362 -0
  325. scitex/fts/_tables/_latex/_utils.py +369 -0
  326. scitex/fts/_tables/_latex/_validator.py +445 -0
  327. scitex/gen/__init__.py +66 -25
  328. scitex/gen/misc.py +28 -0
  329. scitex/io/__init__.py +47 -32
  330. scitex/io/_load.py +87 -36
  331. scitex/io/_load_modules/__init__.py +10 -7
  332. scitex/io/_load_modules/_pandas.py +6 -1
  333. scitex/io/_save.py +299 -1556
  334. scitex/io/_save_modules/__init__.py +76 -19
  335. scitex/io/_save_modules/_figure_utils.py +90 -0
  336. scitex/io/_save_modules/_image_csv.py +497 -0
  337. scitex/io/_save_modules/_legends.py +91 -0
  338. scitex/io/_save_modules/_pltz_bundle.py +356 -0
  339. scitex/io/_save_modules/_pltz_stx.py +536 -0
  340. scitex/io/_save_modules/_stx_bundle.py +104 -0
  341. scitex/io/_save_modules/_symlink.py +96 -0
  342. scitex/io/_save_modules/_yaml.py +1 -1
  343. scitex/io/_save_modules/_zarr.py +64 -18
  344. scitex/io/bundle/README.md +212 -0
  345. scitex/io/bundle/__init__.py +110 -0
  346. scitex/io/{_bundle.py → bundle/_core.py} +168 -97
  347. scitex/io/bundle/_nested.py +713 -0
  348. scitex/io/bundle/_types.py +74 -0
  349. scitex/io/{_zip_bundle.py → bundle/_zip.py} +93 -45
  350. scitex/io/utils/h5_to_zarr.py +1 -1
  351. scitex/logging/__init__.py +108 -13
  352. scitex/logging/_errors.py +508 -0
  353. scitex/logging/_formatters.py +30 -6
  354. scitex/logging/_warnings.py +261 -0
  355. scitex/plt/__init__.py +4 -1
  356. scitex/plt/_figrecipe.py +236 -0
  357. scitex/plt/_subplots/_AxisWrapper.py +6 -0
  358. scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
  359. scitex/plt/_subplots/_FigWrapper.py +15 -0
  360. scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
  361. scitex/plt/_subplots/_export_as_csv.py +11 -0
  362. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
  363. scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
  364. scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
  365. scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
  366. scitex/plt/_subplots/_fonts.py +71 -0
  367. scitex/plt/_subplots/_mm_layout.py +282 -0
  368. scitex/plt/gallery/__init__.py +99 -2
  369. scitex/plt/styles/_plot_postprocess.py +3 -1
  370. scitex/plt/utils/_configure_mpl.py +16 -19
  371. scitex/repro/_RandomStateManager.py +13 -8
  372. scitex/resource/__init__.py +19 -1
  373. scitex/resource/_utils/_get_env_info.py +13 -25
  374. scitex/schema/__init__.py +149 -160
  375. scitex/schema/_encoding.py +273 -0
  376. scitex/schema/_figure_elements.py +406 -0
  377. scitex/schema/_theme.py +360 -0
  378. scitex/schema/_validation.py +0 -98
  379. scitex/scholar/__init__.py +56 -14
  380. scitex/scholar/auth/ScholarAuthManager.py +1 -1
  381. scitex/scholar/auth/__init__.py +11 -2
  382. scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
  383. scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
  384. scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
  385. scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
  386. scitex/scholar/config/ScholarConfig.py +1 -1
  387. scitex/scholar/core/Scholar.py +1 -1
  388. scitex/session/_decorator.py +18 -16
  389. scitex/session/_lifecycle.py +9 -11
  390. scitex/session/template.py +9 -8
  391. scitex/sh/test_sh.py +72 -0
  392. scitex/sh/test_sh_simple.py +61 -0
  393. scitex/stats/__init__.py +221 -97
  394. scitex/stats/_schema.py +21 -22
  395. scitex/stats/descriptive/_circular.py +212 -351
  396. scitex/stats/descriptive/_describe.py +81 -132
  397. scitex/stats/descriptive/_nan.py +205 -433
  398. scitex/stats/descriptive/_real.py +127 -141
  399. scitex/str/_format_plot_text.py +5 -5
  400. scitex/str/_latex.py +26 -84
  401. scitex/str/_latex_fallback.py +53 -47
  402. scitex/web/_search_pubmed.py +5 -4
  403. scitex/writer/tests/test_diff_between.py +451 -0
  404. scitex/writer/tests/test_document_section.py +311 -0
  405. scitex/writer/tests/test_document_workflow.py +393 -0
  406. scitex/writer/tests/test_writer.py +361 -0
  407. scitex/writer/tests/test_writer_integration.py +303 -0
  408. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/METADATA +364 -181
  409. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/RECORD +412 -97
  410. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
  411. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
  412. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
  413. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/WHEEL +0 -0
  414. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/entry_points.txt +0 -0
  415. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -24,7 +24,7 @@ from pathlib import Path
24
24
  import aiohttp
25
25
 
26
26
  from scitex import logging
27
- from scitex.errors import ScholarError
27
+ from scitex.logging import ScholarError
28
28
 
29
29
  logger = logging.getLogger(__name__)
30
30
 
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