scitex 2.8.1__py3-none-any.whl → 2.10.2__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.2.dist-info}/METADATA +368 -183
  409. {scitex-2.8.1.dist-info → scitex-2.10.2.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.2.dist-info}/WHEEL +0 -0
  414. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/entry_points.txt +0 -0
  415. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/licenses/LICENSE +0 -0
scitex/cli/main.py CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  """
4
3
  SciTeX CLI Main Entry Point
5
4
  """
6
5
 
7
6
  import os
8
7
  import sys
8
+
9
9
  import click
10
- from . import cloud, config, scholar, security, web, writer
10
+
11
+ from . import cloud, config, convert, scholar, security, web, writer
11
12
 
12
13
 
13
14
  @click.group(context_settings={"help_option_names": ["-h", "--help"]})
@@ -39,6 +40,7 @@ def cli():
39
40
  # Add command groups
40
41
  cli.add_command(cloud.cloud)
41
42
  cli.add_command(config.config)
43
+ cli.add_command(convert.convert)
42
44
  cli.add_command(scholar.scholar)
43
45
  cli.add_command(security.security)
44
46
  cli.add_command(web.web)
@@ -130,7 +132,7 @@ def completion(shell, show):
130
132
 
131
133
  # Check if already installed (and not commented out)
132
134
  if os.path.exists(rc_file):
133
- with open(rc_file, "r") as f:
135
+ with open(rc_file) as f:
134
136
  for line in f:
135
137
  # Check if the line exists and is not commented
136
138
  stripped = line.strip()
@@ -149,7 +151,7 @@ def completion(shell, show):
149
151
  os.makedirs(os.path.dirname(rc_file), exist_ok=True)
150
152
 
151
153
  with open(rc_file, "a") as f:
152
- f.write(f"\n# SciTeX tab completion\n")
154
+ f.write("\n# SciTeX tab completion\n")
153
155
  f.write(f"{eval_line}\n")
154
156
 
155
157
  click.secho(f"Successfully installed tab completion to {rc_file}", fg="green")
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-05 14:30:00 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/datetime/__init__.py
4
+
5
+ """
6
+ Scitex datetime module.
7
+
8
+ Provides utilities for datetime operations including:
9
+ - linspace: Create linearly spaced datetime arrays
10
+ - normalize_timestamp: Standardize timestamps to consistent format
11
+ - to_datetime: Convert various formats to datetime objects
12
+ - validate_timestamp_format: Validate timestamp string format
13
+ - format_for_filename: Format timestamps for filenames
14
+ - format_for_display: Format timestamps for display
15
+ - get_time_delta_seconds: Calculate time differences
16
+ """
17
+
18
+ from ._linspace import linspace
19
+ from ._normalize_timestamp import (
20
+ ALTERNATIVE_FORMATS,
21
+ STANDARD_FORMAT,
22
+ format_for_display,
23
+ format_for_filename,
24
+ get_time_delta_seconds,
25
+ normalize_timestamp,
26
+ parse_patient_recording_start_format,
27
+ to_datetime,
28
+ validate_timestamp_format,
29
+ )
30
+
31
+ __all__ = [
32
+ # Core functions
33
+ "linspace",
34
+ "normalize_timestamp",
35
+ "to_datetime",
36
+ # Formatting functions
37
+ "format_for_filename",
38
+ "format_for_display",
39
+ "validate_timestamp_format",
40
+ # Utility functions
41
+ "get_time_delta_seconds",
42
+ "parse_patient_recording_start_format",
43
+ # Constants
44
+ "STANDARD_FORMAT",
45
+ "ALTERNATIVE_FORMATS",
46
+ ]
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-05 14:30:00 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/datetime/_linspace.py
4
+
5
+ """
6
+ Datetime linspace utility for creating evenly spaced datetime arrays.
7
+ """
8
+
9
+ import datetime
10
+ from datetime import timedelta
11
+ from typing import Optional
12
+
13
+ import numpy as np
14
+
15
+
16
+ def linspace(
17
+ start_dt: datetime.datetime,
18
+ end_dt: datetime.datetime,
19
+ n_samples: Optional[int] = None,
20
+ sampling_rate: Optional[float] = None,
21
+ ) -> np.ndarray:
22
+ """
23
+ Create a linearly spaced array between two datetime objects.
24
+
25
+ Parameters
26
+ ----------
27
+ start_dt : datetime.datetime
28
+ Starting datetime object
29
+ end_dt : datetime.datetime
30
+ Ending datetime object
31
+ n_samples : int, optional
32
+ Number of samples to create (mutually exclusive with sampling_rate)
33
+ sampling_rate : float, optional
34
+ Sampling rate in Hz (mutually exclusive with n_samples)
35
+
36
+ Returns
37
+ -------
38
+ np.ndarray
39
+ Array of datetime objects evenly spaced between start_dt and end_dt
40
+
41
+ Raises
42
+ ------
43
+ TypeError
44
+ If start_dt or end_dt is not a datetime object
45
+ ValueError
46
+ If start_dt >= end_dt, or if both/neither n_samples and sampling_rate provided
47
+
48
+ Examples
49
+ --------
50
+ >>> import datetime
51
+ >>> start = datetime.datetime(2023, 1, 1, 0, 0, 0)
52
+ >>> end = datetime.datetime(2023, 1, 1, 0, 0, 10)
53
+ >>> result = linspace(start, end, n_samples=11)
54
+ >>> len(result)
55
+ 11
56
+ """
57
+ # Type checking
58
+ if not isinstance(start_dt, datetime.datetime):
59
+ raise TypeError(f"start_dt must be a datetime object, got {type(start_dt)}")
60
+
61
+ if not isinstance(end_dt, datetime.datetime):
62
+ raise TypeError(f"end_dt must be a datetime object, got {type(end_dt)}")
63
+
64
+ if n_samples is not None and not isinstance(n_samples, (int, float)):
65
+ raise TypeError(f"n_samples must be a number, got {type(n_samples)}")
66
+
67
+ if sampling_rate is not None and not isinstance(sampling_rate, (int, float)):
68
+ raise TypeError(f"sampling_rate must be a number, got {type(sampling_rate)}")
69
+
70
+ if start_dt >= end_dt:
71
+ raise ValueError("start_dt must be earlier than end_dt")
72
+
73
+ duration_seconds = (end_dt - start_dt).total_seconds()
74
+
75
+ if n_samples is not None and sampling_rate is not None:
76
+ raise ValueError("Provide either n_samples or sampling_rate, not both")
77
+
78
+ if n_samples is None and sampling_rate is None:
79
+ raise ValueError("Either n_samples or sampling_rate must be provided")
80
+
81
+ if sampling_rate is not None:
82
+ if sampling_rate <= 0:
83
+ raise ValueError("sampling_rate must be positive")
84
+ n_samples = int(duration_seconds * sampling_rate) + 1
85
+ else:
86
+ if n_samples <= 0:
87
+ raise ValueError("n_samples must be positive")
88
+
89
+ # Create linear space in seconds
90
+ seconds_array = np.linspace(0, duration_seconds, n_samples)
91
+
92
+ # Convert to datetime objects
93
+ datetime_array = np.array(
94
+ [start_dt + timedelta(seconds=float(sec)) for sec in seconds_array]
95
+ )
96
+
97
+ return datetime_array
98
+
99
+
100
+ # EOF
@@ -0,0 +1,306 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-05 14:30:00 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/datetime/_normalize_timestamp.py
4
+
5
+ """
6
+ Timestamp Standardization Utilities
7
+
8
+ Functionality:
9
+ - Standardizes timestamps to consistent format defined in CONFIG.FORMATS.TIMESTAMP
10
+ - Handles various input formats (datetime objects, strings, timestamps)
11
+ - Provides UTC normalization
12
+ - Ensures consistent timestamp formatting across the codebase
13
+
14
+ Input formats supported:
15
+ - datetime objects (with or without timezone)
16
+ - Unix timestamps (int/float)
17
+ - Various string formats
18
+
19
+ Output:
20
+ - Standardized timestamp strings in format: "%Y-%m-%d %H:%M:%S.%f"
21
+ - UTC normalized timestamps
22
+ - Validation utilities
23
+
24
+ Prerequisites:
25
+ - CONFIG.FORMATS.TIMESTAMP for standard format
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import re
31
+ from datetime import datetime, timezone
32
+ from typing import Union
33
+
34
+ # Default standard format
35
+ DEFAULT_FORMAT = "%Y-%m-%d %H:%M:%S"
36
+
37
+ # Try to get standard format from config, fallback to default
38
+ try:
39
+ import scitex as stx
40
+
41
+ CONFIG = stx.io.load_configs()
42
+ STANDARD_FORMAT = (
43
+ getattr(getattr(CONFIG, "FORMATS", None), "TIMESTAMP", None) or DEFAULT_FORMAT
44
+ )
45
+ except Exception:
46
+ STANDARD_FORMAT = DEFAULT_FORMAT
47
+
48
+ # Common alternative formats to try when parsing
49
+ ALTERNATIVE_FORMATS = [
50
+ "%Y-%m-%dT%H:%M:%S.%f",
51
+ "%Y-%m-%dT%H:%M:%S", # ISO 8601 with T (no microseconds)
52
+ "%Y-%m-%d %H:%M:%S.%f",
53
+ "%Y-%m-%d %H:%M:%S",
54
+ "%Y/%m/%d %H:%M:%S.%f",
55
+ "%Y/%m/%d %H:%M:%S",
56
+ "%d-%m-%Y %H:%M:%S.%f",
57
+ "%d-%m-%Y %H:%M:%S",
58
+ "%d/%m/%Y %H:%M:%S.%f",
59
+ "%d/%m/%Y %H:%M:%S",
60
+ "%d/%m/%Y, %H:%M:%S", # Format used in REC_START
61
+ "%Y%m%d %H:%M:%S.%f",
62
+ "%Y%m%d %H:%M:%S",
63
+ "%Y-%m-%d_%H:%M:%S.%f",
64
+ "%Y-%m-%d_%H:%M:%S",
65
+ ]
66
+
67
+
68
+ def normalize_timestamp(
69
+ timestamp: Union[datetime, str, int, float],
70
+ return_as: str = "str",
71
+ normalize_utc: bool = True,
72
+ ) -> Union[str, datetime, float]:
73
+ """
74
+ Standardize any timestamp format to requested output type.
75
+
76
+ Parameters
77
+ ----------
78
+ timestamp : datetime, str, int, or float
79
+ Timestamp in any supported format
80
+ return_as : str
81
+ Output format: "str" (default), "datetime", or "timestamp"
82
+ normalize_utc : bool
83
+ If True, normalize to UTC timezone
84
+
85
+ Returns
86
+ -------
87
+ str, datetime, or float
88
+ Standardized timestamp in requested format:
89
+ - "str": String in CONFIG.FORMATS.TIMESTAMP format
90
+ - "datetime": datetime object
91
+ - "timestamp": Unix timestamp (float)
92
+
93
+ Examples
94
+ --------
95
+ >>> from datetime import datetime
96
+ >>> dt = datetime(2010, 6, 18, 10, 15, 0)
97
+ >>> normalize_timestamp(dt, return_as="str", normalize_utc=False)
98
+ '2010-06-18 10:15:00'
99
+ """
100
+ # Convert to datetime object
101
+ dt = to_datetime(timestamp)
102
+
103
+ # Normalize to UTC if requested
104
+ if normalize_utc:
105
+ if dt.tzinfo is None:
106
+ dt = dt.replace(tzinfo=timezone.utc)
107
+ else:
108
+ dt = dt.astimezone(timezone.utc)
109
+
110
+ # Return in requested format
111
+ if return_as == "str":
112
+ return dt.strftime(STANDARD_FORMAT)
113
+ elif return_as == "datetime":
114
+ return dt
115
+ elif return_as == "timestamp":
116
+ return dt.timestamp()
117
+ else:
118
+ raise ValueError(
119
+ f"return_as must be 'str', 'datetime', or 'timestamp', got: {return_as}"
120
+ )
121
+
122
+
123
+ def to_datetime(timestamp: Union[datetime, str, int, float]) -> datetime:
124
+ """
125
+ Convert various timestamp formats to datetime object.
126
+
127
+ Parameters
128
+ ----------
129
+ timestamp : datetime, str, int, or float
130
+ Timestamp in any supported format
131
+
132
+ Returns
133
+ -------
134
+ datetime
135
+ Datetime object
136
+
137
+ Raises
138
+ ------
139
+ ValueError
140
+ If string format cannot be parsed
141
+ TypeError
142
+ If timestamp type is not supported
143
+ """
144
+ # Already datetime
145
+ if isinstance(timestamp, datetime):
146
+ return timestamp
147
+
148
+ # Unix timestamp (int/float)
149
+ elif isinstance(timestamp, (int, float)):
150
+ return datetime.fromtimestamp(timestamp, tz=timezone.utc)
151
+
152
+ # String format
153
+ elif isinstance(timestamp, str):
154
+ # Handle nanosecond precision by truncating to microseconds
155
+ if "." in timestamp and len(timestamp.split(".")[-1]) > 6:
156
+ parts = timestamp.split(".")
157
+ # Keep only first 6 digits of fractional seconds
158
+ truncated_microseconds = parts[-1][:6]
159
+ # Handle cases where there might be additional text after microseconds
160
+ if not truncated_microseconds.isdigit():
161
+ # Extract just the digit portion
162
+ digits = re.match(r"(\d+)", parts[-1])
163
+ if digits:
164
+ truncated_microseconds = digits.group(1)[:6]
165
+ timestamp = ".".join(parts[:-1] + [truncated_microseconds])
166
+
167
+ # Try parsing with various formats
168
+ for fmt in ALTERNATIVE_FORMATS:
169
+ try:
170
+ return datetime.strptime(timestamp, fmt)
171
+ except ValueError:
172
+ continue
173
+
174
+ # If no format matched, raise error
175
+ raise ValueError(
176
+ f"Could not parse timestamp string: {timestamp}. "
177
+ f"Tried formats: {ALTERNATIVE_FORMATS}"
178
+ )
179
+
180
+ else:
181
+ raise TypeError(
182
+ f"timestamp must be datetime, str, int, or float, got: {type(timestamp)}"
183
+ )
184
+
185
+
186
+ def validate_timestamp_format(timestamp_str: str) -> bool:
187
+ """
188
+ Validate that a timestamp string matches the standard format.
189
+
190
+ Parameters
191
+ ----------
192
+ timestamp_str : str
193
+ Timestamp string to validate
194
+
195
+ Returns
196
+ -------
197
+ bool
198
+ True if string matches standard format
199
+ """
200
+ try:
201
+ datetime.strptime(timestamp_str, STANDARD_FORMAT)
202
+ return True
203
+ except (ValueError, TypeError):
204
+ return False
205
+
206
+
207
+ def format_for_filename(timestamp: Union[datetime, str]) -> str:
208
+ """
209
+ Format timestamp for use in filenames (no spaces or colons).
210
+
211
+ Parameters
212
+ ----------
213
+ timestamp : datetime or str
214
+ Timestamp to format
215
+
216
+ Returns
217
+ -------
218
+ str
219
+ Filename-safe timestamp string (YYYYMMDD_HHMMSS)
220
+
221
+ Examples
222
+ --------
223
+ >>> from datetime import datetime
224
+ >>> dt = datetime(2010, 6, 18, 10, 15, 0)
225
+ >>> format_for_filename(dt)
226
+ '20100618_101500'
227
+ """
228
+ dt = to_datetime(timestamp)
229
+ return dt.strftime("%Y%m%d_%H%M%S")
230
+
231
+
232
+ def format_for_display(timestamp: Union[datetime, str]) -> str:
233
+ """
234
+ Format timestamp for human-readable display.
235
+
236
+ Parameters
237
+ ----------
238
+ timestamp : datetime or str
239
+ Timestamp to format
240
+
241
+ Returns
242
+ -------
243
+ str
244
+ Human-readable timestamp string
245
+
246
+ Examples
247
+ --------
248
+ >>> from datetime import datetime
249
+ >>> dt = datetime(2010, 6, 18, 10, 15, 0)
250
+ >>> format_for_display(dt)
251
+ '2010-06-18 10:15:00'
252
+ """
253
+ dt = to_datetime(timestamp)
254
+ return dt.strftime("%Y-%m-%d %H:%M:%S")
255
+
256
+
257
+ def parse_patient_recording_start_format(
258
+ patient_recording_start_str: str,
259
+ ) -> datetime:
260
+ """
261
+ Parse recording start time from CONFIG.PATIENTS.REC_START format.
262
+
263
+ Parameters
264
+ ----------
265
+ patient_recording_start_str : str
266
+ Recording start time string in format "DD/MM/YYYY, HH:MM:SS"
267
+
268
+ Returns
269
+ -------
270
+ datetime
271
+ Parsed datetime object
272
+
273
+ Examples
274
+ --------
275
+ >>> parse_patient_recording_start_format("10/06/2010, 07:40:34")
276
+ datetime.datetime(2010, 6, 10, 7, 40, 34)
277
+ """
278
+ REC_START_FORMAT = "%d/%m/%Y, %H:%M:%S"
279
+ return datetime.strptime(patient_recording_start_str, REC_START_FORMAT)
280
+
281
+
282
+ def get_time_delta_seconds(
283
+ start: Union[datetime, str], end: Union[datetime, str]
284
+ ) -> float:
285
+ """
286
+ Calculate time difference in seconds between two timestamps.
287
+
288
+ Parameters
289
+ ----------
290
+ start : datetime or str
291
+ Start timestamp
292
+ end : datetime or str
293
+ End timestamp
294
+
295
+ Returns
296
+ -------
297
+ float
298
+ Time difference in seconds
299
+ """
300
+ start_dt = to_datetime(start)
301
+ end_dt = to_datetime(end)
302
+ delta = end_dt - start_dt
303
+ return delta.total_seconds()
304
+
305
+
306
+ # EOF
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  # Timestamp: "2025-07-12 12:00:00 (ywatanabe)"
4
3
  # File: ./src/scitex/db/_delete_duplicates.py
5
4
  # ----------------------------------------
@@ -15,7 +14,8 @@ The actual implementation has been moved to _sqlite3._delete_duplicates
15
14
  as it is SQLite3-specific.
16
15
  """
17
16
 
18
- from scitex.errors import warn_deprecated
17
+ from scitex.logging import warn_deprecated
18
+
19
19
  from ._sqlite3._delete_duplicates import delete_sqlite3_duplicates
20
20
 
21
21
 
@@ -28,8 +28,8 @@ def delete_duplicates(*args, **kwargs):
28
28
  Use scitex.db._sqlite3.delete_sqlite3_duplicates() instead.
29
29
  """
30
30
  warn_deprecated(
31
- old_function="scitex.db.delete_duplicates",
32
- new_function="scitex.db._sqlite3.delete_sqlite3_duplicates",
31
+ old_name="scitex.db.delete_duplicates",
32
+ new_name="scitex.db._sqlite3.delete_sqlite3_duplicates",
33
33
  version="3.0.0",
34
34
  )
35
35
  return delete_sqlite3_duplicates(*args, **kwargs)
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  # Timestamp: "2025-07-16 14:00:04 (ywatanabe)"
4
3
  # File: /ssh:sp:/home/ywatanabe/proj/scitex_repo/src/scitex/db/_sqlite3/_delete_duplicates.py
5
4
  # ----------------------------------------
@@ -177,7 +176,17 @@ def _delete_entry(
177
176
  cursor, duplicated_row, table_name, dry_run
178
177
  )
179
178
  if is_verified:
180
- delete_query = select_query.replace("SELECT", "DELETE")
179
+ # Construct proper DELETE query (delete only one matching row)
180
+ columns = list(duplicated_row.index)
181
+ where_conditions = " AND ".join([f"{col} = ?" for col in columns])
182
+ delete_query = f"""
183
+ DELETE FROM {table_name}
184
+ WHERE rowid IN (
185
+ SELECT rowid FROM {table_name}
186
+ WHERE {where_conditions}
187
+ LIMIT 1
188
+ )
189
+ """
181
190
  if dry_run:
182
191
  print(f"[DRY RUN] Would delete entry:\n{duplicated_row}")
183
192
  else: