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
@@ -1,63 +1,69 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # Timestamp: "2025-09-20 17:17:21 (ywatanabe)"
4
- # File: /ssh:sp:/home/ywatanabe/proj/scitex_repo/src/scitex/stats/desc/_circular.py
5
- # ----------------------------------------
2
+ # Timestamp: "2025-12-27 (refactored)"
3
+ # File: scitex/stats/descriptive/_circular.py
4
+ """
5
+ Circular statistics for angular data.
6
+
7
+ Uses torch when available (preserves tensor type), falls back to numpy.
8
+ """
9
+
6
10
  from __future__ import annotations
11
+
7
12
  import os
13
+ import warnings
14
+ from typing import List, Optional, Tuple, Union
15
+
16
+ import numpy as np
8
17
 
9
18
  __FILE__ = __file__
10
19
  __DIR__ = os.path.dirname(__FILE__)
11
- # ----------------------------------------
12
20
 
13
- from typing import List, Optional, Tuple, Union
21
+ # Optional torch support
22
+ try:
23
+ import torch
14
24
 
15
- """
16
- Functionalities:
17
- - Computes circular mean of angles with histogram values
18
- - Calculates circular concentration (mean resultant length)
19
- - Computes circular skewness for asymmetry measurement
20
- - Calculates circular kurtosis for tail behavior analysis
21
- - Warns if input appears to be in degrees instead of radians
22
- - Demonstrates circular statistics with synthetic data
23
- - Saves visualization and statistical results
24
-
25
- Dependencies:
26
- - packages:
27
- - torch
28
- - numpy
29
- - scitex
30
- - matplotlib
31
-
32
- IO:
33
- - input-files:
34
- - angles in radians as torch.Tensor
35
- - histogram values as torch.Tensor
36
- - output-files:
37
- - ./circular_stats_demo.jpg
38
- - ./circular_statistics.pkl
39
- """
25
+ HAS_TORCH = True
26
+ except ImportError:
27
+ torch = None
28
+ HAS_TORCH = False
40
29
 
41
- """Imports"""
42
- import argparse
43
30
 
44
- import numpy as np
45
- import scitex as stx
46
- import torch
47
- from scitex import logging
31
+ def _is_torch_tensor(x):
32
+ """Check if x is a torch tensor."""
33
+ return HAS_TORCH and isinstance(x, torch.Tensor)
34
+
48
35
 
49
- from scitex.decorators import batch_fn, torch_fn
36
+ def _normalize_axis(axis, dim):
37
+ """Normalize axis/dim parameter."""
38
+ return dim if dim is not None else axis
50
39
 
51
- logger = logging.getLogger(__name__)
52
40
 
53
- """Functions & Classes"""
41
+ def _ensure_more_than_2d(data) -> None:
42
+ """Ensure data has at least 2 dimensions."""
43
+ ndim = data.ndim if hasattr(data, "ndim") else np.asarray(data).ndim
44
+ assert ndim >= 2, (
45
+ f"Input must be at least 2 dimensional with batch dimension as first axis, got {ndim}"
46
+ )
47
+
48
+
49
+ def _check_angle_units(angles) -> None:
50
+ """Check if angles might be in degrees and warn user."""
51
+ if _is_torch_tensor(angles):
52
+ max_val = torch.max(torch.abs(angles)).item()
53
+ else:
54
+ max_val = np.max(np.abs(angles))
55
+
56
+ if max_val > 2 * np.pi:
57
+ warnings.warn(
58
+ f"Maximum angle value is {max_val:.2f} (>2π). "
59
+ f"Consider using radians or angle wrapping.",
60
+ UserWarning,
61
+ )
54
62
 
55
63
 
56
- # @batch_fn
57
- @torch_fn
58
64
  def describe_circular(
59
- angles: torch.Tensor,
60
- values: torch.Tensor,
65
+ angles,
66
+ values,
61
67
  axis: int = -1,
62
68
  dim: Optional[Union[int, Tuple[int, ...]]] = None,
63
69
  keepdims: bool = False,
@@ -67,16 +73,16 @@ def describe_circular(
67
73
  "circular_skewness",
68
74
  "circular_kurtosis",
69
75
  ],
70
- device: Optional[torch.device] = None,
76
+ device=None,
71
77
  batch_size: int = -1,
72
- ) -> Tuple[torch.Tensor, List[str]]:
73
- """Computes various circular descriptive statistics.
78
+ ) -> Tuple[np.ndarray, List[str]]:
79
+ """Compute various circular descriptive statistics.
74
80
 
75
81
  Parameters
76
82
  ----------
77
- angles : torch.Tensor
83
+ angles : array-like
78
84
  Input angles in radians with batch dimension as first axis
79
- values : torch.Tensor
85
+ values : array-like
80
86
  Histogram values for each angle (must match angles shape)
81
87
  axis : int, default=-1
82
88
  Deprecated. Use dim instead
@@ -86,18 +92,18 @@ def describe_circular(
86
92
  Whether to keep reduced dimensions
87
93
  funcs : list of str or "all"
88
94
  Circular statistical functions to compute
89
- device : torch.device, optional
90
- Device to use for computation
95
+ device : optional
96
+ Device for torch tensors (ignored for numpy)
91
97
  batch_size : int, default=-1
92
- Batch size for processing (handled by decorator)
98
+ Batch size for processing (currently unused)
93
99
 
94
100
  Returns
95
101
  -------
96
- Tuple[torch.Tensor, List[str]]
102
+ Tuple[ndarray or Tensor, List[str]]
97
103
  Computed circular statistics and their names
98
104
  """
99
- dim = axis if dim is None else dim
100
- dim = (dim,) if isinstance(dim, int) else tuple(dim)
105
+ dim = _normalize_axis(axis, dim)
106
+ dim = (dim,) if isinstance(dim, int) else tuple(dim) if dim is not None else None
101
107
 
102
108
  func_names = funcs
103
109
  func_candidates = {
@@ -115,425 +121,280 @@ def describe_circular(
115
121
 
116
122
  calculated = [ff(angles, values, dim=dim, keepdims=keepdims) for ff in _funcs]
117
123
 
118
- return torch.stack(calculated, dim=-1), func_names
119
-
120
-
121
- def _ensure_more_than_2d(data: torch.Tensor) -> None:
122
- assert data.ndim >= 2, (
123
- f"Input tensor must be more than 2 dimensional with batch dimension as first axis, got {data.ndim}"
124
- )
125
-
126
-
127
- def _check_angle_units(angles: torch.Tensor) -> None:
128
- """Check if angles might be in degrees and warn user.
129
-
130
- Parameters
131
- ----------
132
- angles : torch.Tensor
133
- Input angles to check
134
- """
135
- max_val = torch.max(torch.abs(angles)).item()
136
- if max_val > 2 * torch.pi:
137
- logger.warning(
138
- f"Maximum angle value is {max_val:.2f} (>2π). "
139
- f"Consider using torch.deg2rad() or angle wrapping."
140
- )
124
+ if _is_torch_tensor(angles):
125
+ return torch.stack(calculated, dim=-1), func_names
126
+ else:
127
+ return np.stack(calculated, axis=-1), func_names
141
128
 
142
129
 
143
- # @batch_fn
144
- @torch_fn
145
130
  def circular_mean(
146
- angles: torch.Tensor,
147
- values: torch.Tensor,
131
+ angles,
132
+ values,
148
133
  axis: int = -1,
149
134
  dim: int = None,
150
135
  batch_size: int = None,
151
136
  keepdims: bool = False,
152
- ) -> torch.Tensor:
137
+ ):
153
138
  """Compute circular mean of angles weighted by histogram values.
154
139
 
155
140
  Parameters
156
141
  ----------
157
- angles : torch.Tensor
142
+ angles : array-like
158
143
  Input angles in radians with batch dimension as first axis
159
- values : torch.Tensor
144
+ values : array-like
160
145
  Histogram values for each angle (must match angles shape)
161
146
  axis : int, default=-1
162
147
  Axis along which to compute mean (deprecated, use dim)
163
148
  dim : int, optional
164
149
  Dimension along which to compute mean
165
150
  batch_size : int, optional
166
- Batch size for processing (handled by decorator)
151
+ Batch size for processing (currently unused)
167
152
  keepdims : bool, default=False
168
153
  Whether to keep reduced dimensions
169
154
 
170
155
  Returns
171
156
  -------
172
- torch.Tensor
157
+ ndarray or Tensor
173
158
  Circular mean in range [0, 2π]
174
159
  """
175
-
176
160
  _ensure_more_than_2d(angles)
177
161
  _check_angle_units(angles)
178
- assert angles.shape == values.shape, (
179
- f"angles shape {angles.shape} must match values shape {values.shape}"
180
- )
181
162
 
182
- dim = axis if dim is None else dim
183
- cos_angles = torch.cos(angles)
184
- sin_angles = torch.sin(angles)
163
+ dim = _normalize_axis(axis, dim)
164
+
165
+ if _is_torch_tensor(angles):
166
+ assert angles.shape == values.shape, (
167
+ f"angles shape {angles.shape} must match values shape {values.shape}"
168
+ )
169
+ cos_angles = torch.cos(angles)
170
+ sin_angles = torch.sin(angles)
171
+
172
+ cos_component = torch.sum(values * cos_angles, dim=dim, keepdim=True)
173
+ sin_component = torch.sum(values * sin_angles, dim=dim, keepdim=True)
174
+ value_sum = torch.sum(values, dim=dim, keepdim=True)
175
+ cos_component = cos_component / value_sum
176
+ sin_component = sin_component / value_sum
177
+
178
+ mean_angle = torch.atan2(sin_component, cos_component)
179
+ mean_angle = torch.where(mean_angle < 0, mean_angle + 2 * np.pi, mean_angle)
180
+ return mean_angle if keepdims else mean_angle.squeeze(dim)
181
+ else:
182
+ angles = np.asarray(angles)
183
+ values = np.asarray(values)
184
+ assert angles.shape == values.shape, (
185
+ f"angles shape {angles.shape} must match values shape {values.shape}"
186
+ )
185
187
 
186
- cos_component = torch.sum(values * cos_angles, dim=dim, keepdim=True)
187
- sin_component = torch.sum(values * sin_angles, dim=dim, keepdim=True)
188
- value_sum = torch.sum(values, dim=dim, keepdim=True)
189
- cos_component = cos_component / value_sum
190
- sin_component = sin_component / value_sum
188
+ cos_angles = np.cos(angles)
189
+ sin_angles = np.sin(angles)
191
190
 
192
- mean_angle = torch.atan2(sin_component, cos_component)
193
- mean_angle = torch.where(mean_angle < 0, mean_angle + 2 * np.pi, mean_angle)
191
+ cos_component = np.sum(values * cos_angles, axis=dim, keepdims=True)
192
+ sin_component = np.sum(values * sin_angles, axis=dim, keepdims=True)
193
+ value_sum = np.sum(values, axis=dim, keepdims=True)
194
+ cos_component = cos_component / value_sum
195
+ sin_component = sin_component / value_sum
194
196
 
195
- return mean_angle if keepdims else mean_angle.squeeze(dim)
197
+ mean_angle = np.arctan2(sin_component, cos_component)
198
+ mean_angle = np.where(mean_angle < 0, mean_angle + 2 * np.pi, mean_angle)
199
+ return mean_angle if keepdims else np.squeeze(mean_angle, axis=dim)
196
200
 
197
201
 
198
- # @batch_fn
199
- @torch_fn
200
202
  def circular_concentration(
201
- angles: torch.Tensor,
202
- values: torch.Tensor,
203
+ angles,
204
+ values,
203
205
  axis: int = -1,
204
206
  dim: int = None,
205
207
  batch_size: int = None,
206
208
  keepdims: bool = False,
207
- ) -> torch.Tensor:
209
+ ):
208
210
  """Compute circular concentration (mean resultant length).
209
211
 
210
212
  Parameters
211
213
  ----------
212
- angles : torch.Tensor with batch dimension as first axis
213
- Input angles in radians
214
- values : torch.Tensor
214
+ angles : array-like
215
+ Input angles in radians with batch dimension as first axis
216
+ values : array-like
215
217
  Histogram values for each angle (must match angles shape)
216
218
  axis : int, default=-1
217
219
  Axis along which to compute concentration (deprecated, use dim)
218
220
  dim : int, optional
219
221
  Dimension along which to compute concentration
220
222
  batch_size : int, optional
221
- Batch size for processing (handled by decorator)
223
+ Batch size for processing (currently unused)
222
224
  keepdims : bool, default=False
223
225
  Whether to keep reduced dimensions
224
226
 
225
227
  Returns
226
228
  -------
227
- torch.Tensor
229
+ ndarray or Tensor
228
230
  Concentration parameter in range [0, 1]
229
231
  """
230
232
  _ensure_more_than_2d(angles)
231
233
  _check_angle_units(angles)
232
- assert angles.shape == values.shape, (
233
- f"angles shape {angles.shape} must match values shape {values.shape}"
234
- )
235
234
 
236
- dim = axis if dim is None else dim
237
- cos_angles = torch.cos(angles)
238
- sin_angles = torch.sin(angles)
235
+ dim = _normalize_axis(axis, dim)
236
+
237
+ if _is_torch_tensor(angles):
238
+ assert angles.shape == values.shape
239
+ cos_angles = torch.cos(angles)
240
+ sin_angles = torch.sin(angles)
241
+
242
+ cos_component = torch.sum(values * cos_angles, dim=dim, keepdim=keepdims)
243
+ sin_component = torch.sum(values * sin_angles, dim=dim, keepdim=keepdims)
244
+ value_sum = torch.sum(values, dim=dim, keepdim=keepdims)
245
+ return torch.sqrt(cos_component**2 + sin_component**2) / value_sum
246
+ else:
247
+ angles = np.asarray(angles)
248
+ values = np.asarray(values)
249
+ assert angles.shape == values.shape
239
250
 
240
- cos_component = torch.sum(values * cos_angles, dim=dim, keepdim=keepdims)
241
- sin_component = torch.sum(values * sin_angles, dim=dim, keepdim=keepdims)
242
- value_sum = torch.sum(values, dim=dim, keepdim=keepdims)
243
- vector_length = torch.sqrt(cos_component**2 + sin_component**2) / value_sum
251
+ cos_angles = np.cos(angles)
252
+ sin_angles = np.sin(angles)
244
253
 
245
- return vector_length
254
+ cos_component = np.sum(values * cos_angles, axis=dim, keepdims=keepdims)
255
+ sin_component = np.sum(values * sin_angles, axis=dim, keepdims=keepdims)
256
+ value_sum = np.sum(values, axis=dim, keepdims=keepdims)
257
+ return np.sqrt(cos_component**2 + sin_component**2) / value_sum
246
258
 
247
259
 
248
- # @batch_fn
249
- @torch_fn
250
260
  def circular_skewness(
251
- angles: torch.Tensor,
252
- values: torch.Tensor,
261
+ angles,
262
+ values,
253
263
  axis: int = -1,
254
264
  dim: int = None,
255
265
  batch_size: int = None,
256
266
  keepdims: bool = False,
257
- ) -> torch.Tensor:
267
+ ):
258
268
  """Compute circular skewness.
259
269
 
260
270
  Parameters
261
271
  ----------
262
- angles : torch.Tensor with batch dimension as first axis
263
- Input angles in radians
264
- values : torch.Tensor
272
+ angles : array-like
273
+ Input angles in radians with batch dimension as first axis
274
+ values : array-like
265
275
  Histogram values for each angle (must match angles shape)
266
276
  axis : int, default=-1
267
277
  Axis along which to compute skewness (deprecated, use dim)
268
278
  dim : int, optional
269
279
  Dimension along which to compute skewness
270
280
  batch_size : int, optional
271
- Batch size for processing (handled by decorator)
281
+ Batch size for processing (currently unused)
272
282
  keepdims : bool, default=False
273
283
  Whether to keep reduced dimensions
274
284
 
275
285
  Returns
276
286
  -------
277
- torch.Tensor
287
+ ndarray or Tensor
278
288
  Circular skewness
279
289
  """
280
290
  _ensure_more_than_2d(angles)
281
291
  _check_angle_units(angles)
282
- assert angles.shape == values.shape, (
283
- f"angles shape {angles.shape} must match values shape {values.shape}"
284
- )
285
292
 
286
- dim = axis if dim is None else dim
287
- cos_angles = torch.cos(angles)
288
- sin_angles = torch.sin(angles)
289
- cos_2angles = torch.cos(2 * angles)
290
- sin_2angles = torch.sin(2 * angles)
293
+ dim = _normalize_axis(axis, dim)
294
+
295
+ if _is_torch_tensor(angles):
296
+ assert angles.shape == values.shape
297
+ cos_angles = torch.cos(angles)
298
+ sin_angles = torch.sin(angles)
299
+ cos_2angles = torch.cos(2 * angles)
300
+ sin_2angles = torch.sin(2 * angles)
291
301
 
292
- value_sum = torch.sum(values, dim=dim, keepdim=True)
293
- c1 = torch.sum(values * cos_angles, dim=dim, keepdim=True) / value_sum
294
- s1 = torch.sum(values * sin_angles, dim=dim, keepdim=True) / value_sum
295
- c2 = torch.sum(values * cos_2angles, dim=dim, keepdim=True) / value_sum
296
- s2 = torch.sum(values * sin_2angles, dim=dim, keepdim=True) / value_sum
302
+ value_sum = torch.sum(values, dim=dim, keepdim=True)
303
+ c1 = torch.sum(values * cos_angles, dim=dim, keepdim=True) / value_sum
304
+ s1 = torch.sum(values * sin_angles, dim=dim, keepdim=True) / value_sum
305
+ c2 = torch.sum(values * cos_2angles, dim=dim, keepdim=True) / value_sum
306
+ s2 = torch.sum(values * sin_2angles, dim=dim, keepdim=True) / value_sum
307
+
308
+ skewness = (c2 * s1 - s2 * c1) / (1 - (c1**2 + s1**2)) ** (3 / 2)
309
+ return skewness if keepdims else skewness.squeeze(dim)
310
+ else:
311
+ angles = np.asarray(angles)
312
+ values = np.asarray(values)
313
+ assert angles.shape == values.shape
297
314
 
298
- skewness = (c2 * s1 - s2 * c1) / (1 - (c1**2 + s1**2)) ** (3 / 2)
299
- return skewness if keepdims else skewness.squeeze(dim)
315
+ cos_angles = np.cos(angles)
316
+ sin_angles = np.sin(angles)
317
+ cos_2angles = np.cos(2 * angles)
318
+ sin_2angles = np.sin(2 * angles)
319
+
320
+ value_sum = np.sum(values, axis=dim, keepdims=True)
321
+ c1 = np.sum(values * cos_angles, axis=dim, keepdims=True) / value_sum
322
+ s1 = np.sum(values * sin_angles, axis=dim, keepdims=True) / value_sum
323
+ c2 = np.sum(values * cos_2angles, axis=dim, keepdims=True) / value_sum
324
+ s2 = np.sum(values * sin_2angles, axis=dim, keepdims=True) / value_sum
325
+
326
+ skewness = (c2 * s1 - s2 * c1) / (1 - (c1**2 + s1**2)) ** (3 / 2)
327
+ return skewness if keepdims else np.squeeze(skewness, axis=dim)
300
328
 
301
329
 
302
- # @batch_fn
303
- @torch_fn
304
330
  def circular_kurtosis(
305
- angles: torch.Tensor,
306
- values: torch.Tensor,
331
+ angles,
332
+ values,
307
333
  axis: int = -1,
308
334
  dim: int = None,
309
335
  batch_size: int = None,
310
336
  keepdims: bool = False,
311
- ) -> torch.Tensor:
337
+ ):
312
338
  """Compute circular kurtosis.
313
339
 
314
340
  Parameters
315
341
  ----------
316
- angles : torch.Tensor
342
+ angles : array-like
317
343
  Input angles in radians with batch dimension as first axis
318
- values : torch.Tensor
344
+ values : array-like
319
345
  Histogram values for each angle (must match angles shape)
320
346
  axis : int, default=-1
321
347
  Axis along which to compute kurtosis (deprecated, use dim)
322
348
  dim : int, optional
323
349
  Dimension along which to compute kurtosis
324
350
  batch_size : int, optional
325
- Batch size for processing (handled by decorator)
351
+ Batch size for processing (currently unused)
326
352
  keepdims : bool, default=False
327
353
  Whether to keep reduced dimensions
328
354
 
329
355
  Returns
330
356
  -------
331
- torch.Tensor
357
+ ndarray or Tensor
332
358
  Circular kurtosis
333
359
  """
334
360
  _ensure_more_than_2d(angles)
335
361
  _check_angle_units(angles)
336
- assert angles.shape == values.shape, (
337
- f"angles shape {angles.shape} must match values shape {values.shape}"
338
- )
339
-
340
- dim = axis if dim is None else dim
341
- cos_angles = torch.cos(angles)
342
- sin_angles = torch.sin(angles)
343
- cos_2angles = torch.cos(2 * angles)
344
- sin_2angles = torch.sin(2 * angles)
345
-
346
- value_sum = torch.sum(values, dim=dim, keepdim=True)
347
- c1 = torch.sum(values * cos_angles, dim=dim, keepdim=True) / value_sum
348
- s1 = torch.sum(values * sin_angles, dim=dim, keepdim=True) / value_sum
349
- c2 = torch.sum(values * cos_2angles, dim=dim, keepdim=True) / value_sum
350
- s2 = torch.sum(values * sin_2angles, dim=dim, keepdim=True) / value_sum
351
-
352
- kurtosis = (c2 * c1 + s2 * s1) / (1 - (c1**2 + s1**2)) ** 2
353
- return kurtosis if keepdims else kurtosis.squeeze(dim)
354
-
355
-
356
- def main(args) -> int:
357
- """Demonstrate circular statistics functions with synthetic data."""
358
-
359
- # Generate synthetic circular histogram data
360
- angles = torch.tensor([0.5, 1.2, 2.1, 3.8, 4.9, 5.7])
361
- values = torch.tensor([1.0, 2.0, 1.5, 1.0, 3.0, 1.2])
362
-
363
- angles = angles.reshape(1, -1)
364
- values = values.reshape(1, -1)
365
-
366
- # All at once
367
- described, methods = describe_circular(angles, values)
368
- described, methods = described[0], methods
369
- for i_dd, dd in enumerate(described):
370
- print(f"{methods[i_dd]}: {dd}")
371
-
372
- # Compute circular statistics
373
- c_mean = circular_mean(angles, values)
374
- c_concentration = circular_concentration(angles, values)
375
- c_skewness = circular_skewness(angles, values)
376
- c_kurtosis = circular_kurtosis(angles, values)
377
-
378
- # Store results
379
- ii = 0
380
- results = {
381
- "angles": angles[ii],
382
- "values": values[ii],
383
- "circular_mean": c_mean[ii],
384
- "circular_concentration": c_concentration[ii],
385
- "circular_skewness": c_skewness[ii],
386
- "circular_kurtosis": c_kurtosis[ii],
387
- }
388
-
389
- for k, v in results.items():
390
- if isinstance(v, (np.ndarray, torch.Tensor)):
391
- print(f"\n{k}, Type: {type(v)}, Shape: {v.shape}, Values: {v}")
392
- elif isinstance(v, list):
393
- print(f"\n{k}, Type: {type(v)}, Length: {len(v)}, Values: {v}")
394
- else:
395
- print(f"\n{k}, Type: {type(v)}, Values: {v}")
396
-
397
- # Create visualization
398
- fig, axes = plt.subplots(
399
- 2, 2, figsize=(12, 10), subplot_kw=dict(projection="polar")
400
- )
401
-
402
- # Plot 1: Histogram visualization
403
- axes[0, 0].bar(
404
- results["angles"],
405
- results["values"],
406
- width=0.3,
407
- alpha=0.7,
408
- color="blue",
409
- )
410
- axes[0, 0].axvline(
411
- c_mean.item(),
412
- color="red",
413
- linewidth=2,
414
- label=f"Mean: {c_mean.item():.2f}",
415
- )
416
- axes[0, 0].set_title("Circular Histogram")
417
- axes[0, 0].legend()
418
-
419
- # Plot 2: Concentration visualization
420
- theta = torch.linspace(0, 2 * np.pi, 100)
421
- radius = torch.ones_like(theta) * c_concentration.item()
422
- axes[0, 1].plot(
423
- theta,
424
- radius,
425
- "g-",
426
- linewidth=2,
427
- label=f"Concentration: {c_concentration.item():.3f}",
428
- )
429
- axes[0, 1].bar(
430
- results["angles"],
431
- results["values"],
432
- width=0.3,
433
- alpha=0.5,
434
- color="blue",
435
- )
436
- axes[0, 1].set_title("Concentration Circle")
437
- axes[0, 1].legend()
438
-
439
- # Plot 3: Mean direction
440
- axes[1, 0].bar(
441
- results["angles"],
442
- results["values"],
443
- width=0.3,
444
- alpha=0.7,
445
- color="blue",
446
- )
447
- axes[1, 0].arrow(
448
- 0,
449
- 0,
450
- np.cos(c_mean.item()) * c_concentration.item(),
451
- np.sin(c_mean.item()) * c_concentration.item(),
452
- head_width=0.1,
453
- head_length=0.1,
454
- fc="red",
455
- ec="red",
456
- )
457
- axes[1, 0].set_title("Mean Vector")
458
-
459
- # Plot 4: Statistics summary
460
- axes[1, 1].axis("off")
461
- stats_text = f"""Circular Statistics:
462
-
463
- Mean: {c_mean.item():.3f} rad ({np.degrees(c_mean.item()):.1f}°)
464
- Concentration: {c_concentration.item():.3f}
465
- Skewness: {c_skewness.item():.3f}
466
- Kurtosis: {c_kurtosis.item():.3f}"""
467
-
468
- axes[1, 1].text(
469
- 0.1,
470
- 0.5,
471
- stats_text,
472
- transform=axes[1, 1].transAxes,
473
- fontsize=12,
474
- verticalalignment="center",
475
- )
476
- axes[1, 1].set_title("Statistics Summary")
477
-
478
- plt.tight_layout()
479
- stx.io.save(fig, "./circular_stats_demo.jpg")
480
362
 
481
- # Save results
482
- stx.io.save(results, "./circular_statistics.pkl")
363
+ dim = _normalize_axis(axis, dim)
483
364
 
484
- # Log results
485
- logger.info(
486
- f"Circular mean: {c_mean.item():.3f} rad ({np.degrees(c_mean.item()):.1f}°)"
487
- )
488
- logger.info(f"Circular concentration: {c_concentration.item():.3f}")
489
- logger.info(f"Circular skewness: {c_skewness.item():.3f}")
490
- logger.info(f"Circular kurtosis: {c_kurtosis.item():.3f}")
365
+ if _is_torch_tensor(angles):
366
+ assert angles.shape == values.shape
367
+ cos_angles = torch.cos(angles)
368
+ sin_angles = torch.sin(angles)
369
+ cos_2angles = torch.cos(2 * angles)
370
+ sin_2angles = torch.sin(2 * angles)
491
371
 
492
- return 0
372
+ value_sum = torch.sum(values, dim=dim, keepdim=True)
373
+ c1 = torch.sum(values * cos_angles, dim=dim, keepdim=True) / value_sum
374
+ s1 = torch.sum(values * sin_angles, dim=dim, keepdim=True) / value_sum
375
+ c2 = torch.sum(values * cos_2angles, dim=dim, keepdim=True) / value_sum
376
+ s2 = torch.sum(values * sin_2angles, dim=dim, keepdim=True) / value_sum
493
377
 
378
+ kurtosis = (c2 * c1 + s2 * s1) / (1 - (c1**2 + s1**2)) ** 2
379
+ return kurtosis if keepdims else kurtosis.squeeze(dim)
380
+ else:
381
+ angles = np.asarray(angles)
382
+ values = np.asarray(values)
383
+ assert angles.shape == values.shape
494
384
 
495
- def parse_args() -> argparse.Namespace:
496
- """Parse command line arguments."""
497
- parser = argparse.ArgumentParser(
498
- description="Demonstrate circular statistics functions for histogram data"
499
- )
500
- args = parser.parse_args()
501
- return args
502
-
503
-
504
- def run_main() -> None:
505
- """Initialize scitex framework, run main function, and cleanup."""
506
- global CONFIG, CC, sys, plt, rng
507
-
508
- import sys
509
-
510
- import matplotlib.pyplot as plt
511
- import scitex as stx
512
-
513
- args = parse_args()
514
-
515
- CONFIG, sys.stdout, sys.stderr, plt, CC, rng_manager = stx.session.start(
516
- sys,
517
- plt,
518
- args=args,
519
- file=__FILE__,
520
- sdir_suffix=None,
521
- verbose=False,
522
- agg=True,
523
- )
524
-
525
- exit_status = main(args)
385
+ cos_angles = np.cos(angles)
386
+ sin_angles = np.sin(angles)
387
+ cos_2angles = np.cos(2 * angles)
388
+ sin_2angles = np.sin(2 * angles)
526
389
 
527
- stx.session.close(
528
- CONFIG,
529
- verbose=False,
530
- notify=False,
531
- message="",
532
- exit_status=exit_status,
533
- )
390
+ value_sum = np.sum(values, axis=dim, keepdims=True)
391
+ c1 = np.sum(values * cos_angles, axis=dim, keepdims=True) / value_sum
392
+ s1 = np.sum(values * sin_angles, axis=dim, keepdims=True) / value_sum
393
+ c2 = np.sum(values * cos_2angles, axis=dim, keepdims=True) / value_sum
394
+ s2 = np.sum(values * sin_2angles, axis=dim, keepdims=True) / value_sum
534
395
 
396
+ kurtosis = (c2 * c1 + s2 * s1) / (1 - (c1**2 + s1**2)) ** 2
397
+ return kurtosis if keepdims else np.squeeze(kurtosis, axis=dim)
535
398
 
536
- if __name__ == "__main__":
537
- run_main()
538
399
 
539
400
  # EOF