scitex 2.14.0__py3-none-any.whl → 2.15.3__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 (264) hide show
  1. scitex/__init__.py +71 -17
  2. scitex/_env_loader.py +156 -0
  3. scitex/_mcp_resources/__init__.py +37 -0
  4. scitex/_mcp_resources/_cheatsheet.py +135 -0
  5. scitex/_mcp_resources/_figrecipe.py +138 -0
  6. scitex/_mcp_resources/_formats.py +102 -0
  7. scitex/_mcp_resources/_modules.py +337 -0
  8. scitex/_mcp_resources/_session.py +149 -0
  9. scitex/_mcp_tools/__init__.py +4 -0
  10. scitex/_mcp_tools/audio.py +66 -0
  11. scitex/_mcp_tools/diagram.py +11 -95
  12. scitex/_mcp_tools/introspect.py +210 -0
  13. scitex/_mcp_tools/plt.py +260 -305
  14. scitex/_mcp_tools/scholar.py +74 -0
  15. scitex/_mcp_tools/social.py +27 -0
  16. scitex/_mcp_tools/template.py +24 -0
  17. scitex/_mcp_tools/writer.py +17 -210
  18. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  19. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  20. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  21. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  22. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  23. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  24. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  25. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  26. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  27. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  28. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
  29. scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
  30. scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
  31. scitex/audio/README.md +40 -36
  32. scitex/audio/__init__.py +129 -61
  33. scitex/audio/_branding.py +185 -0
  34. scitex/audio/_mcp/__init__.py +32 -0
  35. scitex/audio/_mcp/handlers.py +59 -6
  36. scitex/audio/_mcp/speak_handlers.py +238 -0
  37. scitex/audio/_relay.py +225 -0
  38. scitex/audio/_tts.py +18 -10
  39. scitex/audio/engines/base.py +17 -10
  40. scitex/audio/engines/elevenlabs_engine.py +7 -2
  41. scitex/audio/mcp_server.py +228 -75
  42. scitex/canvas/README.md +1 -1
  43. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  44. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  45. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  46. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  47. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  48. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  49. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  50. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  51. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  52. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  53. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  54. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  55. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  56. scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
  57. scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
  58. scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
  59. scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
  60. scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
  61. scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
  62. scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
  63. scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
  64. scitex/canvas/editor/flask_editor/_core.py +25 -1684
  65. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  66. scitex/cli/__init__.py +38 -43
  67. scitex/cli/audio.py +160 -41
  68. scitex/cli/capture.py +133 -20
  69. scitex/cli/introspect.py +488 -0
  70. scitex/cli/main.py +200 -109
  71. scitex/cli/mcp.py +60 -34
  72. scitex/cli/plt.py +414 -0
  73. scitex/cli/repro.py +15 -8
  74. scitex/cli/resource.py +15 -8
  75. scitex/cli/scholar/__init__.py +154 -8
  76. scitex/cli/scholar/_crossref_scitex.py +296 -0
  77. scitex/cli/scholar/_fetch.py +25 -3
  78. scitex/cli/social.py +355 -0
  79. scitex/cli/stats.py +136 -11
  80. scitex/cli/template.py +129 -12
  81. scitex/cli/tex.py +15 -8
  82. scitex/cli/writer.py +49 -299
  83. scitex/cloud/__init__.py +41 -2
  84. scitex/config/README.md +1 -1
  85. scitex/config/__init__.py +16 -2
  86. scitex/config/_env_registry.py +256 -0
  87. scitex/context/__init__.py +22 -0
  88. scitex/dev/__init__.py +20 -1
  89. scitex/diagram/__init__.py +42 -19
  90. scitex/diagram/mcp_server.py +13 -125
  91. scitex/gen/__init__.py +50 -14
  92. scitex/gen/_list_packages.py +4 -4
  93. scitex/introspect/__init__.py +82 -0
  94. scitex/introspect/_call_graph.py +303 -0
  95. scitex/introspect/_class_hierarchy.py +163 -0
  96. scitex/introspect/_core.py +41 -0
  97. scitex/introspect/_docstring.py +131 -0
  98. scitex/introspect/_examples.py +113 -0
  99. scitex/introspect/_imports.py +271 -0
  100. scitex/{gen/_inspect_module.py → introspect/_list_api.py} +48 -56
  101. scitex/introspect/_mcp/__init__.py +41 -0
  102. scitex/introspect/_mcp/handlers.py +233 -0
  103. scitex/introspect/_members.py +155 -0
  104. scitex/introspect/_resolve.py +89 -0
  105. scitex/introspect/_signature.py +131 -0
  106. scitex/introspect/_source.py +80 -0
  107. scitex/introspect/_type_hints.py +172 -0
  108. scitex/io/_save.py +1 -2
  109. scitex/io/bundle/README.md +1 -1
  110. scitex/logging/_formatters.py +19 -9
  111. scitex/mcp_server.py +98 -5
  112. scitex/os/__init__.py +4 -0
  113. scitex/{gen → os}/_check_host.py +4 -5
  114. scitex/plt/__init__.py +245 -550
  115. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  116. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  117. scitex/plt/gallery/README.md +1 -1
  118. scitex/plt/utils/_hitmap/__init__.py +82 -0
  119. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  120. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  121. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  122. scitex/plt/utils/_hitmap/_constants.py +40 -0
  123. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  124. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  125. scitex/plt/utils/_hitmap/_query.py +113 -0
  126. scitex/plt/utils/_hitmap.py +46 -1616
  127. scitex/plt/utils/_metadata/__init__.py +80 -0
  128. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  129. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  130. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  131. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  132. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  133. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  134. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  135. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  136. scitex/plt/utils/_metadata/_csv.py +416 -0
  137. scitex/plt/utils/_metadata/_detect.py +225 -0
  138. scitex/plt/utils/_metadata/_legend.py +127 -0
  139. scitex/plt/utils/_metadata/_rounding.py +117 -0
  140. scitex/plt/utils/_metadata/_verification.py +202 -0
  141. scitex/schema/README.md +1 -1
  142. scitex/scholar/__init__.py +8 -0
  143. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  144. scitex/scholar/core/Scholar.py +63 -1700
  145. scitex/scholar/core/_mixins/__init__.py +36 -0
  146. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  147. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  148. scitex/scholar/core/_mixins/_loaders.py +103 -0
  149. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  150. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  151. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  152. scitex/scholar/core/_mixins/_savers.py +69 -0
  153. scitex/scholar/core/_mixins/_search.py +103 -0
  154. scitex/scholar/core/_mixins/_services.py +88 -0
  155. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  156. scitex/scholar/crossref_scitex.py +367 -0
  157. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  158. scitex/scholar/examples/00_run_all.sh +120 -0
  159. scitex/scholar/jobs/_executors.py +27 -3
  160. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  161. scitex/scholar/pdf_download/_cli.py +154 -0
  162. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  163. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  164. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  165. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  166. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  167. scitex/scholar/pipelines/_single_steps.py +71 -36
  168. scitex/scholar/storage/_LibraryManager.py +97 -1695
  169. scitex/scholar/storage/_mixins/__init__.py +30 -0
  170. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  171. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  172. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  173. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  174. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  175. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  176. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  177. scitex/security/README.md +3 -3
  178. scitex/session/README.md +1 -1
  179. scitex/session/__init__.py +26 -7
  180. scitex/session/_decorator.py +1 -1
  181. scitex/sh/README.md +1 -1
  182. scitex/sh/__init__.py +7 -4
  183. scitex/social/__init__.py +155 -0
  184. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  185. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  186. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  187. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  188. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  189. scitex/stats/_mcp/_handlers/_format.py +94 -0
  190. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  191. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  192. scitex/stats/_mcp/_handlers/_power.py +247 -0
  193. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  194. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  195. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  196. scitex/stats/_mcp/handlers.py +19 -1171
  197. scitex/stats/auto/_stat_style.py +175 -0
  198. scitex/stats/auto/_style_definitions.py +411 -0
  199. scitex/stats/auto/_styles.py +22 -620
  200. scitex/stats/descriptive/__init__.py +11 -8
  201. scitex/stats/descriptive/_ci.py +39 -0
  202. scitex/stats/power/_power.py +15 -4
  203. scitex/str/__init__.py +2 -1
  204. scitex/str/_title_case.py +63 -0
  205. scitex/template/README.md +1 -1
  206. scitex/template/__init__.py +25 -10
  207. scitex/template/_code_templates.py +147 -0
  208. scitex/template/_mcp/handlers.py +81 -0
  209. scitex/template/_mcp/tool_schemas.py +55 -0
  210. scitex/template/_templates/__init__.py +51 -0
  211. scitex/template/_templates/audio.py +233 -0
  212. scitex/template/_templates/canvas.py +312 -0
  213. scitex/template/_templates/capture.py +268 -0
  214. scitex/template/_templates/config.py +43 -0
  215. scitex/template/_templates/diagram.py +294 -0
  216. scitex/template/_templates/io.py +107 -0
  217. scitex/template/_templates/module.py +53 -0
  218. scitex/template/_templates/plt.py +202 -0
  219. scitex/template/_templates/scholar.py +267 -0
  220. scitex/template/_templates/session.py +130 -0
  221. scitex/template/_templates/session_minimal.py +43 -0
  222. scitex/template/_templates/session_plot.py +67 -0
  223. scitex/template/_templates/session_stats.py +77 -0
  224. scitex/template/_templates/stats.py +323 -0
  225. scitex/template/_templates/writer.py +296 -0
  226. scitex/template/clone_writer_directory.py +5 -5
  227. scitex/ui/_backends/_email.py +10 -2
  228. scitex/ui/_backends/_webhook.py +5 -1
  229. scitex/web/_search_pubmed.py +10 -6
  230. scitex/writer/README.md +1 -1
  231. scitex/writer/__init__.py +43 -34
  232. scitex/writer/_mcp/handlers.py +11 -744
  233. scitex/writer/_mcp/tool_schemas.py +5 -335
  234. scitex-2.15.3.dist-info/METADATA +667 -0
  235. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/RECORD +241 -120
  236. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  237. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  238. scitex/diagram/_compile.py +0 -312
  239. scitex/diagram/_diagram.py +0 -355
  240. scitex/diagram/_mcp/__init__.py +0 -4
  241. scitex/diagram/_mcp/handlers.py +0 -400
  242. scitex/diagram/_mcp/tool_schemas.py +0 -157
  243. scitex/diagram/_presets.py +0 -173
  244. scitex/diagram/_schema.py +0 -182
  245. scitex/diagram/_split.py +0 -278
  246. scitex/gen/_ci.py +0 -12
  247. scitex/gen/_title_case.py +0 -89
  248. scitex/plt/_mcp/__init__.py +0 -4
  249. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  250. scitex/plt/_mcp/_handlers_figure.py +0 -195
  251. scitex/plt/_mcp/_handlers_plot.py +0 -252
  252. scitex/plt/_mcp/_handlers_style.py +0 -219
  253. scitex/plt/_mcp/handlers.py +0 -74
  254. scitex/plt/_mcp/tool_schemas.py +0 -497
  255. scitex/plt/mcp_server.py +0 -231
  256. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  257. scitex/scholar/examples/dev.py +0 -38
  258. scitex-2.14.0.dist-info/METADATA +0 -1238
  259. /scitex/{gen → context}/_detect_environment.py +0 -0
  260. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  261. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  262. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/WHEEL +0 -0
  263. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/entry_points.txt +0 -0
  264. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,346 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/plt/utils/_hitmap/_color_application.py
4
+
5
+ """
6
+ Color application and restoration functions for hitmap generation.
7
+
8
+ This module provides functions to apply unique ID colors to matplotlib artists
9
+ and restore original colors after hitmap rendering.
10
+ """
11
+
12
+ from typing import Any, Dict, List, Tuple
13
+
14
+ from ._artist_extraction import get_all_artists, get_all_artists_with_groups
15
+ from ._color_conversion import id_to_rgb
16
+ from ._constants import HITMAP_AXES_COLOR, HITMAP_BACKGROUND_COLOR
17
+
18
+ __all__ = [
19
+ "apply_id_color",
20
+ "apply_hitmap_colors",
21
+ "restore_original_colors",
22
+ "prepare_hitmap_figure",
23
+ "restore_figure_props",
24
+ ]
25
+
26
+
27
+ def apply_id_color(artist, hex_color: str):
28
+ """Apply ID color to an artist, handling different artist types."""
29
+ if hasattr(artist, "set_color"):
30
+ artist.set_color(hex_color)
31
+ if hasattr(artist, "set_antialiased"):
32
+ artist.set_antialiased(False)
33
+
34
+ elif hasattr(artist, "set_facecolor"):
35
+ artist.set_facecolor(hex_color)
36
+ if hasattr(artist, "set_edgecolor"):
37
+ artist.set_edgecolor(hex_color)
38
+ if hasattr(artist, "set_alpha"):
39
+ artist.set_alpha(1.0)
40
+ if hasattr(artist, "set_antialiased"):
41
+ artist.set_antialiased(False)
42
+
43
+ # Handle BarContainer
44
+ if hasattr(artist, "patches"):
45
+ for patch in artist.patches:
46
+ patch.set_facecolor(hex_color)
47
+ patch.set_edgecolor(hex_color)
48
+ if hasattr(patch, "set_antialiased"):
49
+ patch.set_antialiased(False)
50
+
51
+
52
+ def apply_hitmap_colors(
53
+ fig,
54
+ include_text: bool = False,
55
+ ) -> Tuple[List[Dict[str, Any]], Dict[int, Dict[str, Any]], Dict[str, Dict[str, Any]]]:
56
+ """
57
+ Apply unique ID colors to data elements in a figure.
58
+
59
+ Also detects logical groups (histogram, bar_series, etc.) and assigns
60
+ group_id to each element for hierarchical selection.
61
+
62
+ Parameters
63
+ ----------
64
+ fig : matplotlib.figure.Figure
65
+ The figure to modify.
66
+ include_text : bool
67
+ Whether to include text elements.
68
+
69
+ Returns
70
+ -------
71
+ tuple
72
+ (original_props, color_map, groups) where:
73
+ - original_props: list of dicts with original artist properties
74
+ - color_map: dict mapping ID to element info
75
+ - groups: dict mapping group_id to logical group info
76
+ """
77
+ artists_with_groups, groups = get_all_artists_with_groups(fig, include_text)
78
+
79
+ original_props = []
80
+ color_map = {}
81
+
82
+ for i, (artist, ax_idx, artist_type, group_id) in enumerate(artists_with_groups):
83
+ element_id = i + 1
84
+ r, g, b = id_to_rgb(element_id)
85
+ hex_color = f"#{r:02x}{g:02x}{b:02x}"
86
+
87
+ # Store original properties
88
+ props = {"artist": artist, "type": artist_type}
89
+ try:
90
+ if hasattr(artist, "get_color"):
91
+ props["color"] = artist.get_color()
92
+ if hasattr(artist, "get_facecolor"):
93
+ props["facecolor"] = artist.get_facecolor()
94
+ if hasattr(artist, "get_edgecolor"):
95
+ props["edgecolor"] = artist.get_edgecolor()
96
+ if hasattr(artist, "get_alpha"):
97
+ props["alpha"] = artist.get_alpha()
98
+ if hasattr(artist, "get_antialiased"):
99
+ props["antialiased"] = artist.get_antialiased()
100
+ if hasattr(artist, "get_linewidth"):
101
+ props["linewidth"] = artist.get_linewidth()
102
+ except Exception:
103
+ pass
104
+ original_props.append(props)
105
+
106
+ # Build color map entry with group information
107
+ label = ""
108
+ if hasattr(artist, "get_label"):
109
+ label = artist.get_label()
110
+ if label.startswith("_"):
111
+ label = f"{artist_type}_{i}"
112
+
113
+ role = "physical" if group_id else "standalone"
114
+
115
+ color_map[element_id] = {
116
+ "id": element_id,
117
+ "type": artist_type,
118
+ "label": label,
119
+ "axes_index": ax_idx,
120
+ "rgb": [r, g, b],
121
+ "group_id": group_id,
122
+ "role": role,
123
+ }
124
+
125
+ # Apply ID color
126
+ try:
127
+ apply_id_color(artist, hex_color)
128
+ except Exception:
129
+ pass
130
+
131
+ # Add RGB color to groups for logical selection
132
+ group_id_start = len(artists_with_groups) + 1
133
+ groups_with_colors = {}
134
+ for i, (gid, ginfo) in enumerate(groups.items()):
135
+ logical_id = group_id_start + i
136
+ r, g, b = id_to_rgb(logical_id)
137
+
138
+ # Find member element IDs
139
+ member_ids = []
140
+ for elem_id, elem_info in color_map.items():
141
+ if elem_info.get("group_id") == gid:
142
+ member_ids.append(elem_id)
143
+
144
+ groups_with_colors[gid] = {
145
+ "id": logical_id,
146
+ "type": ginfo["type"],
147
+ "label": ginfo["label"],
148
+ "axes_index": ginfo["axes_index"],
149
+ "rgb": [r, g, b],
150
+ "role": "logical",
151
+ "member_ids": member_ids,
152
+ "member_count": ginfo["member_count"],
153
+ }
154
+
155
+ return original_props, color_map, groups_with_colors
156
+
157
+
158
+ def restore_original_colors(original_props: List[Dict[str, Any]]):
159
+ """
160
+ Restore original colors to artists after hitmap generation.
161
+
162
+ Parameters
163
+ ----------
164
+ original_props : list
165
+ List of dicts with original artist properties.
166
+ """
167
+ for props in original_props:
168
+ artist = props["artist"]
169
+ try:
170
+ if "color" in props and hasattr(artist, "set_color"):
171
+ artist.set_color(props["color"])
172
+ if "facecolor" in props and hasattr(artist, "set_facecolor"):
173
+ artist.set_facecolor(props["facecolor"])
174
+ if "edgecolor" in props and hasattr(artist, "set_edgecolor"):
175
+ artist.set_edgecolor(props["edgecolor"])
176
+ if "alpha" in props and hasattr(artist, "set_alpha"):
177
+ artist.set_alpha(props["alpha"])
178
+ if "antialiased" in props and hasattr(artist, "set_antialiased"):
179
+ artist.set_antialiased(props["antialiased"])
180
+ if "linewidth" in props and hasattr(artist, "set_linewidth"):
181
+ artist.set_linewidth(props["linewidth"])
182
+ except Exception:
183
+ pass
184
+
185
+
186
+ def prepare_hitmap_figure(
187
+ fig,
188
+ include_text: bool = False,
189
+ ) -> Tuple[Dict[int, Dict[str, Any]], List[Dict[str, Any]]]:
190
+ """
191
+ Prepare a figure for hitmap rendering by coloring elements with unique IDs.
192
+
193
+ Parameters
194
+ ----------
195
+ fig : matplotlib.figure.Figure
196
+ The figure to prepare for hitmap rendering.
197
+ include_text : bool
198
+ Whether to include text elements.
199
+
200
+ Returns
201
+ -------
202
+ tuple
203
+ (color_map, original_props) where:
204
+ - color_map: dict mapping ID to element info
205
+ - original_props: list of dicts with original properties
206
+ """
207
+ artists = get_all_artists(fig, include_text)
208
+
209
+ if not artists:
210
+ return {}, []
211
+
212
+ original_props = []
213
+ color_map = {}
214
+
215
+ for i, (artist, ax_idx, artist_type) in enumerate(artists):
216
+ element_id = i + 1
217
+ r, g, b = id_to_rgb(element_id)
218
+ hex_color = f"#{r:02x}{g:02x}{b:02x}"
219
+
220
+ # Store original properties
221
+ props = {"artist": artist, "type": artist_type}
222
+ try:
223
+ if hasattr(artist, "get_color"):
224
+ props["color"] = artist.get_color()
225
+ if hasattr(artist, "get_facecolor"):
226
+ props["facecolor"] = artist.get_facecolor()
227
+ if hasattr(artist, "get_edgecolor"):
228
+ props["edgecolor"] = artist.get_edgecolor()
229
+ if hasattr(artist, "get_alpha"):
230
+ props["alpha"] = artist.get_alpha()
231
+ if hasattr(artist, "get_antialiased"):
232
+ props["antialiased"] = artist.get_antialiased()
233
+ except Exception:
234
+ pass
235
+ original_props.append(props)
236
+
237
+ # Build color map entry
238
+ label = ""
239
+ if hasattr(artist, "get_label"):
240
+ label = artist.get_label()
241
+ if label.startswith("_"):
242
+ label = f"{artist_type}_{i}"
243
+
244
+ color_map[element_id] = {
245
+ "id": element_id,
246
+ "type": artist_type,
247
+ "label": label,
248
+ "axes_index": ax_idx,
249
+ "rgb": [r, g, b],
250
+ }
251
+
252
+ # Apply ID color
253
+ try:
254
+ apply_id_color(artist, hex_color)
255
+ except Exception:
256
+ pass
257
+
258
+ # Hide non-artist elements
259
+ axes_props = []
260
+ for ax in fig.axes:
261
+ ax_props = {
262
+ "ax": ax,
263
+ "grid_visible": (
264
+ ax.xaxis.get_gridlines()[0].get_visible()
265
+ if ax.xaxis.get_gridlines()
266
+ else False
267
+ ),
268
+ "facecolor": ax.get_facecolor(),
269
+ "spines_visible": {k: v.get_visible() for k, v in ax.spines.items()},
270
+ "xlabel": ax.get_xlabel(),
271
+ "ylabel": ax.get_ylabel(),
272
+ "title": ax.get_title(),
273
+ "legend_visible": (
274
+ ax.get_legend().get_visible() if ax.get_legend() else None
275
+ ),
276
+ "tick_params": {},
277
+ }
278
+ axes_props.append(ax_props)
279
+
280
+ ax.grid(False)
281
+ for spine in ax.spines.values():
282
+ spine.set_color(HITMAP_AXES_COLOR)
283
+ ax.set_facecolor(HITMAP_BACKGROUND_COLOR)
284
+ ax.tick_params(colors=HITMAP_AXES_COLOR, labelcolor=HITMAP_AXES_COLOR)
285
+ ax.xaxis.label.set_color(HITMAP_AXES_COLOR)
286
+ ax.yaxis.label.set_color(HITMAP_AXES_COLOR)
287
+ ax.title.set_color(HITMAP_AXES_COLOR)
288
+ if ax.get_legend():
289
+ ax.get_legend().set_visible(False)
290
+
291
+ original_props.append(
292
+ {
293
+ "type": "_figure_patch",
294
+ "facecolor": fig.patch.get_facecolor(),
295
+ "axes_props": axes_props,
296
+ }
297
+ )
298
+ fig.patch.set_facecolor(HITMAP_BACKGROUND_COLOR)
299
+
300
+ return color_map, original_props
301
+
302
+
303
+ def restore_figure_props(original_props: List[Dict[str, Any]]):
304
+ """
305
+ Restore figure properties after hitmap rendering.
306
+
307
+ Parameters
308
+ ----------
309
+ original_props : list
310
+ List of property dicts from prepare_hitmap_figure().
311
+ """
312
+ for props in original_props:
313
+ if props.get("type") == "_figure_patch":
314
+ if "axes_props" in props:
315
+ for ax_props in props["axes_props"]:
316
+ ax = ax_props["ax"]
317
+ ax.set_facecolor(ax_props["facecolor"])
318
+ for spine_name, visible in ax_props["spines_visible"].items():
319
+ ax.spines[spine_name].set_visible(visible)
320
+ ax.set_xlabel(ax_props["xlabel"])
321
+ ax.set_ylabel(ax_props["ylabel"])
322
+ ax.set_title(ax_props["title"])
323
+ if ax_props["legend_visible"] is not None and ax.get_legend():
324
+ ax.get_legend().set_visible(ax_props["legend_visible"])
325
+ continue
326
+
327
+ artist = props.get("artist")
328
+ if not artist:
329
+ continue
330
+
331
+ try:
332
+ if "color" in props and hasattr(artist, "set_color"):
333
+ artist.set_color(props["color"])
334
+ if "facecolor" in props and hasattr(artist, "set_facecolor"):
335
+ artist.set_facecolor(props["facecolor"])
336
+ if "edgecolor" in props and hasattr(artist, "set_edgecolor"):
337
+ artist.set_edgecolor(props["edgecolor"])
338
+ if "alpha" in props and hasattr(artist, "set_alpha"):
339
+ artist.set_alpha(props["alpha"])
340
+ if "antialiased" in props and hasattr(artist, "set_antialiased"):
341
+ artist.set_antialiased(props["antialiased"])
342
+ except Exception:
343
+ pass
344
+
345
+
346
+ # EOF
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/plt/utils/_hitmap/_color_conversion.py
4
+
5
+ """
6
+ Color conversion functions for hitmap ID encoding.
7
+
8
+ This module provides functions to convert between element IDs and RGB colors
9
+ for pixel-perfect element identification in hit maps.
10
+ """
11
+
12
+ import colorsys
13
+ import hashlib
14
+ from typing import Tuple
15
+
16
+ __all__ = [
17
+ "id_to_rgb",
18
+ "rgb_to_id",
19
+ "rgb_to_id_lookup",
20
+ ]
21
+
22
+ # Hand-picked palette for first 12 elements (most common case)
23
+ DISTINCT_COLORS = [
24
+ (255, 0, 0), # 1: Red
25
+ (0, 200, 0), # 2: Green
26
+ (0, 100, 255), # 3: Blue
27
+ (255, 200, 0), # 4: Yellow/Gold
28
+ (255, 0, 200), # 5: Magenta/Pink
29
+ (0, 220, 220), # 6: Cyan
30
+ (255, 100, 0), # 7: Orange
31
+ (150, 0, 255), # 8: Purple
32
+ (0, 255, 100), # 9: Spring Green
33
+ (255, 100, 150), # 10: Salmon/Rose
34
+ (100, 255, 0), # 11: Lime
35
+ (100, 150, 255), # 12: Sky Blue
36
+ ]
37
+
38
+
39
+ def id_to_rgb(element_id: int) -> Tuple[int, int, int]:
40
+ """
41
+ Convert element ID to unique, human-readable RGB color.
42
+
43
+ Uses a hash function to generate visually distinct colors that are:
44
+ 1. Deterministic (same ID always gives same color)
45
+ 2. Visually distinct (spread across the color space)
46
+ 3. Bright and saturated (easy to see)
47
+
48
+ The first 12 elements use a hand-picked palette for maximum distinctness.
49
+ Beyond that, uses hash-based HSV generation with high saturation.
50
+
51
+ Parameters
52
+ ----------
53
+ element_id : int
54
+ Element ID (1-based). ID 0 is reserved for background.
55
+
56
+ Returns
57
+ -------
58
+ tuple
59
+ (R, G, B) values (0-255)
60
+ """
61
+ if element_id <= 0:
62
+ return (0, 0, 0) # Background
63
+
64
+ if element_id <= len(DISTINCT_COLORS):
65
+ return DISTINCT_COLORS[element_id - 1]
66
+
67
+ # For IDs > 12, use hash-based color generation
68
+ hash_bytes = hashlib.md5(str(element_id).encode()).digest()
69
+
70
+ # Use hash bytes to generate HSV values
71
+ hue = int.from_bytes(hash_bytes[0:2], "big") / 65535.0
72
+ saturation = 0.7 + (int.from_bytes(hash_bytes[2:3], "big") / 255.0) * 0.3
73
+ value = 0.75 + (int.from_bytes(hash_bytes[3:4], "big") / 255.0) * 0.25
74
+
75
+ r, g, b = colorsys.hsv_to_rgb(hue, saturation, value)
76
+ return (int(r * 255), int(g * 255), int(b * 255))
77
+
78
+
79
+ def rgb_to_id(r: int, g: int, b: int) -> int:
80
+ """
81
+ Convert RGB color back to element ID using 24-bit encoding.
82
+
83
+ Parameters
84
+ ----------
85
+ r, g, b : int
86
+ RGB values (0-255)
87
+
88
+ Returns
89
+ -------
90
+ int
91
+ Element ID
92
+ """
93
+ return (r << 16) | (g << 8) | b
94
+
95
+
96
+ def rgb_to_id_lookup(r: int, g: int, b: int, color_map: dict) -> int:
97
+ """
98
+ Convert RGB color back to element ID using the color map.
99
+
100
+ Since we use human-readable colors, we need to look up in the map.
101
+
102
+ Parameters
103
+ ----------
104
+ r, g, b : int
105
+ RGB values (0-255)
106
+ color_map : dict
107
+ Color map from generate_hitmap_id_colors (maps ID -> info with 'rgb' key)
108
+
109
+ Returns
110
+ -------
111
+ int
112
+ Element ID, or 0 if not found
113
+ """
114
+ rgb = [r, g, b]
115
+ for element_id, info in color_map.items():
116
+ if info.get("rgb") == rgb:
117
+ return element_id
118
+ return 0
119
+
120
+
121
+ # EOF
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/plt/utils/_hitmap/_constants.py
4
+
5
+ """
6
+ Constants and type conversion utilities for hitmap generation.
7
+ """
8
+
9
+ from typing import Any
10
+
11
+ import numpy as np
12
+
13
+ # Reserved colors for hitmap (human-readable)
14
+ HITMAP_BACKGROUND_COLOR = "#1a1a1a" # Dark gray (not pure black, easier to see)
15
+ HITMAP_AXES_COLOR = "#404040" # Medium gray (non-selectable axes elements)
16
+
17
+
18
+ def to_native(obj: Any) -> Any:
19
+ """Convert numpy types to native Python types for JSON serialization."""
20
+ if isinstance(obj, np.integer):
21
+ return int(obj)
22
+ elif isinstance(obj, np.floating):
23
+ return float(obj)
24
+ elif isinstance(obj, np.ndarray):
25
+ return obj.tolist()
26
+ elif isinstance(obj, dict):
27
+ return {k: to_native(v) for k, v in obj.items()}
28
+ elif isinstance(obj, list):
29
+ return [to_native(v) for v in obj]
30
+ return obj
31
+
32
+
33
+ __all__ = [
34
+ "HITMAP_BACKGROUND_COLOR",
35
+ "HITMAP_AXES_COLOR",
36
+ "to_native",
37
+ ]
38
+
39
+
40
+ # EOF