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,516 +1,288 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # Timestamp: "2025-09-20 16:01:20 (ywatanabe)"
4
- # File: /ssh:sp:/home/ywatanabe/proj/scitex_repo/src/scitex/stats/desc/_nan.py
5
- # ----------------------------------------
2
+ # Timestamp: "2025-12-27 (refactored)"
3
+ # File: scitex/stats/descriptive/_nan.py
4
+ """
5
+ NaN-aware descriptive statistics.
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
8
13
 
14
+ import numpy as np
15
+
9
16
  __FILE__ = __file__
10
17
  __DIR__ = os.path.dirname(__FILE__)
11
- # ----------------------------------------
12
18
 
13
- """
14
- Functionalities:
15
- - Computes NaN-aware descriptive statistics on PyTorch tensors
16
- - Provides robust statistical measures that handle missing values
17
- - Calculates mean, std, variance, skewness, kurtosis with NaN handling
18
- - Computes quantiles and extreme values ignoring NaN
19
- - Demonstrates NaN-robust statistical computations
20
-
21
- Dependencies:
22
- - packages:
23
- - torch
24
- - numpy
25
- - scitex
26
-
27
- IO:
28
- - input-files:
29
- - PyTorch tensor or numpy array with potential NaN values
30
- - output-files:
31
- - NaN-robust statistical results
32
- """
19
+ # Optional torch support
20
+ try:
21
+ import torch
33
22
 
34
- """Imports"""
35
- import argparse
23
+ HAS_TORCH = True
24
+ except ImportError:
25
+ torch = None
26
+ HAS_TORCH = False
36
27
 
37
- import numpy as np
38
- import scitex as stx
39
- import torch
40
- from scitex import logging
41
28
 
42
- from scitex.decorators import batch_fn, torch_fn
29
+ def _is_torch_tensor(x):
30
+ """Check if x is a torch tensor."""
31
+ return HAS_TORCH and isinstance(x, torch.Tensor)
43
32
 
44
- logger = logging.getLogger(__name__)
45
33
 
46
- """Functions & Classes"""
34
+ def _normalize_axis(axis, dim):
35
+ """Normalize axis/dim parameter."""
36
+ return dim if dim is not None else axis
37
+
38
+
39
+ # =============================================================================
40
+ # NaN-aware Functions
41
+ # =============================================================================
47
42
 
48
43
 
49
- @torch_fn
50
- @batch_fn
51
44
  def nanmax(x, axis=-1, dim=None, batch_size=None, keepdims=False):
52
- min_value = torch.finfo(x.dtype).min
53
- dim = axis if dim is None else dim
54
- if isinstance(dim, (tuple, list)):
55
- for d in sorted(dim, reverse=True):
56
- x = x.nan_to_num(min_value).max(dim=d, keepdims=keepdims)[0]
45
+ """Compute maximum ignoring NaN values."""
46
+ dim = _normalize_axis(axis, dim)
47
+ if _is_torch_tensor(x):
48
+ min_value = torch.finfo(x.dtype).min
49
+ if isinstance(dim, (tuple, list)):
50
+ for d in sorted(dim, reverse=True):
51
+ x = x.nan_to_num(min_value).max(dim=d, keepdim=keepdims)[0]
52
+ return x
53
+ return x.nan_to_num(min_value).max(dim=dim, keepdim=keepdims)[0]
57
54
  else:
58
- x = x.nan_to_num(min_value).max(dim=dim, keepdims=keepdims)[0]
59
- return x
55
+ x = np.asarray(x)
56
+ return np.nanmax(x, axis=dim, keepdims=keepdims)
60
57
 
61
58
 
62
- @torch_fn
63
- @batch_fn
64
59
  def nanmin(x, axis=-1, dim=None, batch_size=None, keepdims=False):
65
- max_value = torch.finfo(x.dtype).max
66
- dim = axis if dim is None else dim
67
- if isinstance(dim, (tuple, list)):
68
- for d in sorted(dim, reverse=True):
69
- x = x.nan_to_num(max_value).min(dim=d, keepdims=keepdims)[0]
60
+ """Compute minimum ignoring NaN values."""
61
+ dim = _normalize_axis(axis, dim)
62
+ if _is_torch_tensor(x):
63
+ max_value = torch.finfo(x.dtype).max
64
+ if isinstance(dim, (tuple, list)):
65
+ for d in sorted(dim, reverse=True):
66
+ x = x.nan_to_num(max_value).min(dim=d, keepdim=keepdims)[0]
67
+ return x
68
+ return x.nan_to_num(max_value).min(dim=dim, keepdim=keepdims)[0]
70
69
  else:
71
- x = x.nan_to_num(max_value).min(dim=dim, keepdims=keepdims)[0]
72
- return x
70
+ x = np.asarray(x)
71
+ return np.nanmin(x, axis=dim, keepdims=keepdims)
73
72
 
74
73
 
75
- @torch_fn
76
- @batch_fn
77
74
  def nansum(x, axis=-1, dim=None, batch_size=None, keepdims=False):
78
- return torch.nansum(x, dim=dim, keepdims=keepdims)
75
+ """Compute sum ignoring NaN values."""
76
+ dim = _normalize_axis(axis, dim)
77
+ if _is_torch_tensor(x):
78
+ return torch.nansum(x, dim=dim, keepdim=keepdims)
79
+ return np.nansum(np.asarray(x), axis=dim, keepdims=keepdims)
79
80
 
80
81
 
81
- @torch_fn
82
- @batch_fn
83
82
  def nanmean(x, axis=-1, dim=None, batch_size=None, keepdims=False):
84
- return torch.nanmean(x, dim=dim, keepdims=keepdims)
83
+ """Compute mean ignoring NaN values."""
84
+ dim = _normalize_axis(axis, dim)
85
+ if _is_torch_tensor(x):
86
+ return torch.nanmean(x, dim=dim, keepdim=keepdims)
87
+ return np.nanmean(np.asarray(x), axis=dim, keepdims=keepdims)
85
88
 
86
89
 
87
- @torch_fn
88
- @batch_fn
89
90
  def nanvar(x, axis=-1, dim=None, batch_size=None, keepdims=False):
90
- tensor_mean = nanmean(x, dim=dim, keepdims=True)
91
- return (x - tensor_mean).square().nanmean(dim=dim, keepdims=keepdims)
91
+ """Compute variance ignoring NaN values."""
92
+ dim = _normalize_axis(axis, dim)
93
+ if _is_torch_tensor(x):
94
+ tensor_mean = torch.nanmean(x, dim=dim, keepdim=True)
95
+ return (x - tensor_mean).square().nanmean(dim=dim, keepdim=keepdims)
96
+ else:
97
+ x = np.asarray(x)
98
+ return np.nanvar(x, axis=dim, keepdims=keepdims)
92
99
 
93
100
 
94
- @torch_fn
95
- @batch_fn
96
101
  def nanstd(x, axis=-1, dim=None, batch_size=None, keepdims=False):
97
- return torch.sqrt(nanvar(x, dim=dim, keepdims=keepdims))
102
+ """Compute standard deviation ignoring NaN values."""
103
+ dim = _normalize_axis(axis, dim)
104
+ if _is_torch_tensor(x):
105
+ return torch.sqrt(nanvar(x, dim=dim, keepdims=keepdims))
106
+ return np.nanstd(np.asarray(x), axis=dim, keepdims=keepdims)
98
107
 
99
108
 
100
- # @torch_fn
101
- # def nanzscore(x, axis=-1, dim=None, batch_size=None, keepdims=True):
102
- # _mean = nanmean(x, dim=dim, keepdims=True)
103
- # _std = nanstd(x, dim=dim, keepdims=True)
104
- # zscores = (x - _mean) / _std
105
- # return zscores if keepdims else zscores.squeeze(dim)
106
- @torch_fn
107
- @batch_fn
108
109
  def nanzscore(x, axis=-1, dim=None, batch_size=None, keepdims=True):
109
- dim = axis if dim is None else dim
110
- if isinstance(dim, (tuple, list)):
111
- _mean = nanmean(x, dim=dim, keepdims=True)
110
+ """Compute z-scores ignoring NaN values."""
111
+ dim = _normalize_axis(axis, dim)
112
+ if _is_torch_tensor(x):
113
+ _mean = torch.nanmean(x, dim=dim, keepdim=True)
112
114
  _std = nanstd(x, dim=dim, keepdims=True)
115
+ zscores = (x - _mean) / _std
116
+ return zscores if keepdims else zscores.squeeze(dim)
113
117
  else:
114
- _mean = nanmean(x, dim=dim, keepdims=True)
115
- _std = nanstd(x, dim=dim, keepdims=True)
116
- zscores = (x - _mean) / _std
117
- return zscores if keepdims else zscores.squeeze(dim)
118
-
119
-
120
- # @torch_fn
121
- # def nankurtosis(x, axis=-1, dim=None, batch_size=None, keepdims=False):
122
- # zscores = nanzscore(x, axis=axis, keepdims=True)
123
- # n = (~torch.isnan(x)).sum(dim=dim, keepdim=True).to(x.dtype) # Changed this line
124
- # k = torch.nanmean(torch.pow(zscores, 4.0), dim=dim, keepdims=keepdims)
125
- # correction = (n * (n + 1)) / ((n - 1) * (n - 2) * (n - 3))
126
- # return correction * k - 3 * (n - 1)**2 / ((n - 2) * (n - 3))
127
-
128
-
129
- # @torch_fn
130
- # def nankurtosis(x, axis=-1, dim=None, batch_size=None, keepdims=False):
131
- # dim = axis if dim is None else dim
132
- # if isinstance(dim, (tuple, list)):
133
- # zscores = nanzscore(x, dim=dim, keepdims=True)
134
- # n = (~torch.isnan(x)).sum(dim=dim, keepdim=True).to(x.dtype)
135
- # k = torch.nanmean(torch.pow(zscores, 4.0), dim=dim, keepdims=keepdims)
136
- # correction = (n * (n + 1)) / ((n - 1) * (n - 2) * (n - 3))
137
- # result = correction * k - 3 * (n - 1)**2 / ((n - 2) * (n - 3))
138
- # return result.squeeze() if not keepdims else result
139
- # else:
140
- # # Original code for single dimension
141
- # zscores = nanzscore(x, dim=dim, keepdims=True)
142
- # n = (~torch.isnan(x)).sum(dim=dim, keepdim=True).to(x.dtype)
143
- # k = torch.nanmean(torch.pow(zscores, 4.0), dim=dim, keepdims=keepdims)
144
- # correction = (n * (n + 1)) / ((n - 1) * (n - 2) * (n - 3))
145
- # result = correction * k - 3 * (n - 1)**2 / ((n - 2) * (n - 3))
146
- # return result.squeeze() if not keepdims else result
147
-
148
- # @torch_fn
149
- # def nanskewness(x, axis=-1, dim=None, batch_size=None, keepdims=False):
150
- # zscores = nanzscore(x, axis=axis, keepdims=True)
151
- # n = (~torch.isnan(x)).sum(dim=dim, keepdim=True).to(x.dtype) # Changed this line
152
- # s = torch.nanmean(torch.pow(zscores, 3.0), dim=dim, keepdims=keepdims)
153
- # correction = n**2 / ((n - 1) * (n - 2))
154
- # return correction * s
155
-
156
-
157
- @torch_fn
158
- @batch_fn
118
+ x = np.asarray(x)
119
+ _mean = np.nanmean(x, axis=dim, keepdims=True)
120
+ _std = np.nanstd(x, axis=dim, keepdims=True)
121
+ zscores = (x - _mean) / _std
122
+ if not keepdims and dim is not None:
123
+ zscores = np.squeeze(zscores, axis=dim)
124
+ return zscores
125
+
126
+
159
127
  def nankurtosis(x, axis=-1, dim=None, batch_size=None, keepdims=False):
160
- zscores = nanzscore(x, axis=axis, keepdims=True)
161
- return torch.nanmean(torch.pow(zscores, 4.0), dim=dim, keepdims=keepdims) - 3.0
128
+ """Compute excess kurtosis ignoring NaN values."""
129
+ dim = _normalize_axis(axis, dim)
130
+ zscores = nanzscore(x, dim=dim, keepdims=True)
131
+ if _is_torch_tensor(x):
132
+ return torch.nanmean(torch.pow(zscores, 4.0), dim=dim, keepdim=keepdims) - 3.0
133
+ return np.nanmean(np.power(zscores, 4.0), axis=dim, keepdims=keepdims) - 3.0
162
134
 
163
135
 
164
- @torch_fn
165
- @batch_fn
166
136
  def nanskewness(x, axis=-1, dim=None, batch_size=None, keepdims=False):
167
- zscores = nanzscore(x, axis=axis, keepdims=True)
168
- return torch.nanmean(torch.pow(zscores, 3.0), dim=dim, keepdims=keepdims)
137
+ """Compute skewness ignoring NaN values."""
138
+ dim = _normalize_axis(axis, dim)
139
+ zscores = nanzscore(x, dim=dim, keepdims=True)
140
+ if _is_torch_tensor(x):
141
+ return torch.nanmean(torch.pow(zscores, 3.0), dim=dim, keepdim=keepdims)
142
+ return np.nanmean(np.power(zscores, 3.0), axis=dim, keepdims=keepdims)
169
143
 
170
144
 
171
- @torch_fn
172
- @batch_fn
173
145
  def nanprod(x, axis=-1, dim=None, batch_size=None, keepdims=False):
174
- dim = axis if dim is None else dim
175
- if isinstance(dim, (tuple, list)):
176
- for d in sorted(dim, reverse=True):
177
- x = x.nan_to_num(1).prod(dim=d, keepdims=keepdims)
146
+ """Compute product ignoring NaN values (treated as 1)."""
147
+ dim = _normalize_axis(axis, dim)
148
+ if _is_torch_tensor(x):
149
+ if isinstance(dim, (tuple, list)):
150
+ for d in sorted(dim, reverse=True):
151
+ x = x.nan_to_num(1).prod(dim=d, keepdim=keepdims)
152
+ return x
153
+ return x.nan_to_num(1).prod(dim=dim, keepdim=keepdims)
178
154
  else:
179
- x = x.nan_to_num(1).prod(dim=dim, keepdims=keepdims)
180
- return x
155
+ x = np.asarray(x)
156
+ return np.nanprod(x, axis=dim, keepdims=keepdims)
181
157
 
182
158
 
183
- @torch_fn
184
- @batch_fn
185
159
  def nancumprod(x, axis=-1, dim=None, batch_size=None, keepdims=False):
186
- dim = axis if dim is None else dim
160
+ """Compute cumulative product ignoring NaN values."""
161
+ dim = _normalize_axis(axis, dim)
187
162
  if isinstance(dim, (tuple, list)):
188
163
  raise ValueError("cumprod does not support multiple dimensions")
189
- return x.nan_to_num(1).cumprod(dim=dim)
164
+ if _is_torch_tensor(x):
165
+ return x.nan_to_num(1).cumprod(dim=dim)
166
+ return np.nancumprod(np.asarray(x), axis=dim)
190
167
 
191
168
 
192
- @torch_fn
193
- @batch_fn
194
169
  def nancumsum(x, axis=-1, dim=None, batch_size=None, keepdims=False):
195
- dim = axis if dim is None else dim
170
+ """Compute cumulative sum ignoring NaN values."""
171
+ dim = _normalize_axis(axis, dim)
196
172
  if isinstance(dim, (tuple, list)):
197
173
  raise ValueError("cumsum does not support multiple dimensions")
198
- return x.nan_to_num(0).cumsum(dim=dim)
174
+ if _is_torch_tensor(x):
175
+ return x.nan_to_num(0).cumsum(dim=dim)
176
+ return np.nancumsum(np.asarray(x), axis=dim)
199
177
 
200
178
 
201
- @torch_fn
202
- @batch_fn
203
179
  def nanargmin(x, axis=-1, dim=None, batch_size=None, keepdims=False):
204
- max_value = torch.finfo(x.dtype).max
205
- dim = axis if dim is None else dim
206
- if isinstance(dim, (tuple, list)):
207
- for d in sorted(dim, reverse=True):
208
- x = x.nan_to_num(max_value).argmin(dim=d, keepdims=keepdims)
180
+ """Compute argmin ignoring NaN values."""
181
+ dim = _normalize_axis(axis, dim)
182
+ if _is_torch_tensor(x):
183
+ max_value = torch.finfo(x.dtype).max
184
+ if isinstance(dim, (tuple, list)):
185
+ for d in sorted(dim, reverse=True):
186
+ x = x.nan_to_num(max_value).argmin(dim=d, keepdim=keepdims)
187
+ return x
188
+ return x.nan_to_num(max_value).argmin(dim=dim, keepdim=keepdims)
209
189
  else:
210
- x = x.nan_to_num(max_value).argmin(dim=dim, keepdims=keepdims)
211
- return x
190
+ x = np.asarray(x)
191
+ return (
192
+ np.nanargmin(x, axis=dim, keepdims=keepdims)
193
+ if keepdims
194
+ else np.nanargmin(x, axis=dim)
195
+ )
212
196
 
213
197
 
214
- @torch_fn
215
- @batch_fn
216
198
  def nanargmax(x, axis=-1, dim=None, batch_size=None, keepdims=False):
217
- min_value = torch.finfo(x.dtype).min
218
- dim = axis if dim is None else dim
219
- if isinstance(dim, (tuple, list)):
220
- for d in sorted(dim, reverse=True):
221
- x = x.nan_to_num(min_value).argmax(dim=d, keepdims=keepdims)
199
+ """Compute argmax ignoring NaN values."""
200
+ dim = _normalize_axis(axis, dim)
201
+ if _is_torch_tensor(x):
202
+ min_value = torch.finfo(x.dtype).min
203
+ if isinstance(dim, (tuple, list)):
204
+ for d in sorted(dim, reverse=True):
205
+ x = x.nan_to_num(min_value).argmax(dim=d, keepdim=keepdims)
206
+ return x
207
+ return x.nan_to_num(min_value).argmax(dim=dim, keepdim=keepdims)
222
208
  else:
223
- x = x.nan_to_num(min_value).argmax(dim=dim, keepdims=keepdims)
224
- return x
209
+ x = np.asarray(x)
210
+ return (
211
+ np.nanargmax(x, axis=dim, keepdims=keepdims)
212
+ if keepdims
213
+ else np.nanargmax(x, axis=dim)
214
+ )
225
215
 
226
216
 
227
- @torch_fn
228
- @batch_fn
229
217
  def nanquantile(x, q, axis=-1, dim=None, batch_size=None, keepdims=False):
230
- dim = axis if dim is None else dim
231
- if isinstance(dim, (tuple, list)):
232
- # For multiple dimensions, flatten them first
233
- # Save original shape for potential keepdims
234
- original_shape = x.shape
235
-
236
- # Calculate new shape: keep dimensions not in dim, flatten those in dim
237
- dim_list = list(dim) if isinstance(dim, tuple) else dim
238
- # Normalize negative dimensions
239
- dim_list = [d if d >= 0 else len(original_shape) + d for d in dim_list]
240
-
241
- # Determine which dimensions to keep
242
- keep_dims = [i for i in range(len(original_shape)) if i not in dim_list]
243
-
244
- # Permute tensor to move dims to reduce to the end
245
- perm_dims = keep_dims + dim_list
246
- x_perm = x.permute(perm_dims)
247
-
248
- # Reshape to flatten the dimensions to reduce
249
- new_shape = [original_shape[i] for i in keep_dims] + [-1]
250
- x_flat = x_perm.reshape(new_shape)
251
-
252
- # Apply nanquantile on the flattened dimension
253
- mask = ~torch.isnan(x_flat)
254
- x_filtered = torch.where(mask, x_flat, torch.tensor(float("inf")))
255
- result = torch.quantile(x_filtered, q / 100, dim=-1, keepdim=keepdims)
256
-
257
- # If keepdims, reshape back with singleton dimensions
258
- if keepdims:
259
- final_shape = list(original_shape)
260
- for d in dim_list:
261
- final_shape[d] = 1
262
- result = result.reshape(final_shape)
263
-
264
- return result
218
+ """Compute quantile ignoring NaN values.
219
+
220
+ Parameters
221
+ ----------
222
+ x : array-like
223
+ Input data
224
+ q : float
225
+ Quantile to compute (0-100)
226
+ """
227
+ dim = _normalize_axis(axis, dim)
228
+
229
+ if _is_torch_tensor(x):
230
+ if isinstance(dim, (tuple, list)):
231
+ original_shape = x.shape
232
+ dim_list = list(dim) if isinstance(dim, tuple) else dim
233
+ dim_list = [d if d >= 0 else len(original_shape) + d for d in dim_list]
234
+ keep_dims = [i for i in range(len(original_shape)) if i not in dim_list]
235
+ perm_dims = keep_dims + dim_list
236
+ x_perm = x.permute(perm_dims)
237
+ new_shape = [original_shape[i] for i in keep_dims] + [-1]
238
+ x_flat = x_perm.reshape(new_shape)
239
+ mask = ~torch.isnan(x_flat)
240
+ x_filtered = torch.where(mask, x_flat, torch.tensor(float("inf")))
241
+ result = torch.quantile(x_filtered, q / 100, dim=-1, keepdim=keepdims)
242
+ if keepdims:
243
+ final_shape = list(original_shape)
244
+ for d in dim_list:
245
+ final_shape[d] = 1
246
+ result = result.reshape(final_shape)
247
+ return result
248
+ else:
249
+ mask = ~torch.isnan(x)
250
+ x_filtered = torch.where(mask, x, torch.tensor(float("inf")))
251
+ return torch.quantile(x_filtered, q / 100, dim=dim, keepdim=keepdims)
265
252
  else:
266
- mask = ~torch.isnan(x)
267
- x_filtered = torch.where(mask, x, torch.tensor(float("inf")))
268
- x = torch.quantile(x_filtered, q / 100, dim=dim, keepdim=keepdims)
269
- return x
253
+ x = np.asarray(x)
254
+ return np.nanquantile(x, q / 100, axis=dim, keepdims=keepdims)
270
255
 
271
256
 
272
257
  def nanq25(x, axis=-1, dim=None, batch_size=None, keepdims=False):
273
- kwargs = {"axis": axis, "dim": dim, "keepdims": keepdims}
274
- if batch_size is not None:
275
- kwargs["batch_size"] = batch_size
276
- return nanquantile(x, 25, **kwargs)
258
+ """Compute 25th percentile ignoring NaN values."""
259
+ return nanquantile(x, 25, axis=axis, dim=dim, keepdims=keepdims)
277
260
 
278
261
 
279
262
  def nanq50(x, axis=-1, dim=None, batch_size=None, keepdims=False):
280
- kwargs = {"axis": axis, "dim": dim, "keepdims": keepdims}
281
- if batch_size is not None:
282
- kwargs["batch_size"] = batch_size
283
- return nanquantile(x, 50, **kwargs)
263
+ """Compute 50th percentile (median) ignoring NaN values."""
264
+ return nanquantile(x, 50, axis=axis, dim=dim, keepdims=keepdims)
284
265
 
285
266
 
286
267
  def nanq75(x, axis=-1, dim=None, batch_size=None, keepdims=False):
287
- kwargs = {"axis": axis, "dim": dim, "keepdims": keepdims}
288
- if batch_size is not None:
289
- kwargs["batch_size"] = batch_size
290
- return nanquantile(x, 75, **kwargs)
268
+ """Compute 75th percentile ignoring NaN values."""
269
+ return nanquantile(x, 75, axis=axis, dim=dim, keepdims=keepdims)
291
270
 
292
271
 
293
- @torch_fn
294
- @batch_fn
295
272
  def nancount(x, axis=-1, dim=None, batch_size=None, keepdims=False):
296
- """Count number of non-NaN values along specified dimensions.
297
-
298
- Parameters
299
- ----------
300
- x : torch.Tensor
301
- Input tensor
302
- axis : int, default=-1
303
- Deprecated. Use dim instead
304
- dim : int or tuple of ints, optional
305
- Dimension(s) along which to count
306
- keepdims : bool, default=True
307
- Whether to keep reduced dimensions
308
-
309
- Returns
310
- -------
311
- torch.Tensor
312
- Count of non-NaN values
313
- """
314
- dim = axis if dim is None else dim
315
- mask = ~torch.isnan(x)
316
-
317
- if isinstance(dim, (tuple, list)):
318
- for d in sorted(dim, reverse=True):
319
- mask = mask.sum(dim=d, keepdims=keepdims)
273
+ """Count non-NaN values along specified dimensions."""
274
+ dim = _normalize_axis(axis, dim)
275
+ if _is_torch_tensor(x):
276
+ mask = ~torch.isnan(x)
277
+ if isinstance(dim, (tuple, list)):
278
+ for d in sorted(dim, reverse=True):
279
+ mask = mask.sum(dim=d, keepdim=keepdims)
280
+ return mask
281
+ return mask.sum(dim=dim, keepdim=keepdims)
320
282
  else:
321
- mask = mask.sum(dim=dim, keepdims=keepdims)
322
-
323
- return mask
324
-
325
-
326
- # @torch_fn
327
- # @batch_fn
328
- # def nanmax(x, axis=-1, dim=None, batch_size=None, keepdims=False):
329
- # min_value = torch.finfo(x.dtype).min
330
- # dim = axis if dim is None else dim
331
- # if isinstance(dim, (tuple, list)):
332
- # for d in sorted(dim, reverse=True):
333
- # x = x.nan_to_num(min_value).max(dim=d, keepdims=keepdims)[0]
334
- # else:
335
- # x = x.nan_to_num(min_value).max(dim=dim, keepdims=keepdims)[0]
336
- # return x
337
-
338
-
339
- # @torch_fn
340
- # @batch_fn
341
- # def nanmin(x, axis=-1, dim=None, batch_size=None, keepdims=False):
342
- # max_value = torch.finfo(x.dtype).max
343
- # dim = axis if dim is None else dim
344
- # if isinstance(dim, (tuple, list)):
345
- # for d in sorted(dim, reverse=True):
346
- # x = x.nan_to_num(max_value).min(dim=d, keepdims=keepdims)[0]
347
- # else:
348
- # x = x.nan_to_num(max_value).min(dim=dim, keepdims=keepdims)[0]
349
- # return x
350
-
351
-
352
- # @torch_fn
353
- # @batch_fn
354
- # def nansum(x, axis=-1, dim=None, batch_size=None, keepdims=False):
355
- # return torch.nansum(x, dim=dim, keepdims=keepdims)
356
-
357
-
358
- # @torch_fn
359
- # @batch_fn
360
- # def nanmean(x, axis=-1, dim=None, batch_size=None, keepdims=False):
361
- # return torch.nanmean(x, dim=dim, keepdims=keepdims)
362
-
363
-
364
- # @torch_fn
365
- # @batch_fn
366
- # def nanvar(x, axis=-1, dim=None, batch_size=None, keepdims=False):
367
- # tensor_mean = nanmean(x, dim=dim, keepdims=True)
368
- # return (x - tensor_mean).square().nanmean(dim=dim, keepdims=keepdims)
369
-
370
-
371
- # @torch_fn
372
- # @batch_fn
373
- # def nanstd(x, axis=-1, dim=None, batch_size=None, keepdims=False):
374
- # return torch.sqrt(nanvar(x, dim=dim, keepdims=keepdims))
375
-
376
-
377
- # @torch_fn
378
- # @batch_fn
379
- # def nanzscore(x, axis=-1, dim=None, batch_size=None, keepdims=True):
380
- # dim = axis if dim is None else dim
381
- # if isinstance(dim, (tuple, list)):
382
- # _mean = nanmean(x, dim=dim, keepdims=True)
383
- # _std = nanstd(x, dim=dim, keepdims=True)
384
- # else:
385
- # _mean = nanmean(x, dim=dim, keepdims=True)
386
- # _std = nanstd(x, dim=dim, keepdims=True)
387
- # zscores = (x - _mean) / _std
388
- # return zscores if keepdims else zscores.squeeze(dim)
389
-
390
-
391
- # @torch_fn
392
- # @batch_fn
393
- # def nankurtosis(x, axis=-1, dim=None, batch_size=None, keepdims=False):
394
- # zscores = nanzscore(x, axis=axis, keepdims=True)
395
- # return (
396
- # torch.nanmean(torch.pow(zscores, 4.0), dim=dim, keepdims=keepdims)
397
- # - 3.0
398
- # )
399
-
400
-
401
- # @torch_fn
402
- # @batch_fn
403
- # def nanskewness(x, axis=-1, dim=None, batch_size=None, keepdims=False):
404
- # zscores = nanzscore(x, axis=axis, keepdims=True)
405
- # return torch.nanmean(torch.pow(zscores, 3.0), dim=dim, keepdims=keepdims)
406
-
407
-
408
- # @torch_fn
409
- # @batch_fn
410
- # def nancount(x, axis=-1, dim=None, batch_size=None, keepdims=False):
411
- # """Count number of non-NaN values along specified dimensions."""
412
- # dim = axis if dim is None else dim
413
- # mask = ~torch.isnan(x)
414
- # if isinstance(dim, (tuple, list)):
415
- # for d in sorted(dim, reverse=True):
416
- # mask = mask.sum(dim=d, keepdims=keepdims)
417
- # else:
418
- # mask = mask.sum(dim=dim, keepdims=keepdims)
419
- # return mask
420
-
421
-
422
- def main(args) -> int:
423
- """Demonstrate NaN-aware statistics functions with synthetic data."""
424
- x = np.random.rand(10, 5, 3)
425
-
426
- # Introduce some NaN values
427
- x_with_nan = x.copy()
428
- x_with_nan[0, 0, 0] = np.nan
429
- x_with_nan[2, 1, 1] = np.nan
430
- x = torch.tensor(x_with_nan)
431
-
432
- # Compute NaN-aware statistics
433
- x_nanmean = nanmean(x)
434
- x_nanstd = nanstd(x)
435
- x_nanvar = nanvar(x)
436
- x_nanskew = nanskewness(x)
437
- x_nankurt = nankurtosis(x)
438
- x_nanmax = nanmax(x)
439
- x_nanmin = nanmin(x)
440
- x_nancount = nancount(x)
441
-
442
- # Store results
443
- results = {
444
- "input": x,
445
- "nan_mean": x_nanmean,
446
- "nan_std": x_nanstd,
447
- "nan_var": x_nanvar,
448
- "nan_skew": x_nanskew,
449
- "nan_kurt": x_nankurt,
450
- "nan_max": x_nanmax,
451
- "nan_min": x_nanmin,
452
- "nan_count": x_nancount,
453
- }
454
-
455
- for k, v in results.items():
456
- if isinstance(v, (np.ndarray, torch.Tensor)):
457
- print(f"\n{k}, Type: {type(v)}, Shape: {v.shape}, Values: {v}")
458
- elif isinstance(v, list):
459
- print(f"\n{k}, Type: {type(v)}, Length: {len(v)}, Values: {v}")
460
- else:
461
- print(f"\n{k}, Type: {type(v)}, Values: {v}")
462
-
463
- # # Save results
464
- # stx.io.save(results, "./nan_statistics.pkl")
465
-
466
- # # Log results
467
- # logger.info(f"NaN-aware mean: {x_nanmean}")
468
- # logger.info(f"NaN-aware std: {x_nanstd}")
469
- # logger.info(f"Non-NaN count: {x_nancount}")
470
-
471
- return 0
472
-
473
-
474
- def parse_args() -> argparse.Namespace:
475
- """Parse command line arguments."""
476
- parser = argparse.ArgumentParser(
477
- description="Demonstrate NaN-aware statistics functions"
478
- )
479
- args = parser.parse_args()
480
- return args
481
-
482
-
483
- def run_main() -> None:
484
- """Initialize scitex framework, run main function, and cleanup."""
485
- global CONFIG, CC, sys, plt, rng
486
- import sys
487
-
488
- import matplotlib.pyplot as plt
489
- import scitex as stx
490
-
491
- args = parse_args()
492
- CONFIG, sys.stdout, sys.stderr, plt, CC, rng_manager = stx.session.start(
493
- sys,
494
- plt,
495
- args=args,
496
- file=__FILE__,
497
- sdir_suffix=None,
498
- verbose=False,
499
- agg=True,
500
- )
501
-
502
- exit_status = main(args)
503
-
504
- stx.session.close(
505
- CONFIG,
506
- verbose=False,
507
- notify=False,
508
- message="",
509
- exit_status=exit_status,
510
- )
511
-
283
+ x = np.asarray(x)
284
+ mask = ~np.isnan(x)
285
+ return np.sum(mask, axis=dim, keepdims=keepdims)
512
286
 
513
- if __name__ == "__main__":
514
- run_main()
515
287
 
516
288
  # EOF