scitex 2.14.0__py3-none-any.whl → 2.15.1__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.
- scitex/__init__.py +47 -0
- scitex/_env_loader.py +156 -0
- scitex/_mcp_resources/__init__.py +37 -0
- scitex/_mcp_resources/_cheatsheet.py +135 -0
- scitex/_mcp_resources/_figrecipe.py +138 -0
- scitex/_mcp_resources/_formats.py +102 -0
- scitex/_mcp_resources/_modules.py +337 -0
- scitex/_mcp_resources/_session.py +149 -0
- scitex/_mcp_tools/__init__.py +4 -0
- scitex/_mcp_tools/audio.py +66 -0
- scitex/_mcp_tools/diagram.py +11 -95
- scitex/_mcp_tools/introspect.py +191 -0
- scitex/_mcp_tools/plt.py +260 -305
- scitex/_mcp_tools/scholar.py +74 -0
- scitex/_mcp_tools/social.py +244 -0
- scitex/_mcp_tools/writer.py +21 -204
- scitex/ai/_gen_ai/_PARAMS.py +10 -7
- scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
- scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
- scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
- scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
- scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
- scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
- scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
- scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
- scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
- scitex/audio/README.md +40 -36
- scitex/audio/__init__.py +127 -59
- scitex/audio/_branding.py +185 -0
- scitex/audio/_mcp/__init__.py +32 -0
- scitex/audio/_mcp/handlers.py +59 -6
- scitex/audio/_mcp/speak_handlers.py +238 -0
- scitex/audio/_relay.py +225 -0
- scitex/audio/engines/elevenlabs_engine.py +6 -1
- scitex/audio/mcp_server.py +228 -75
- scitex/canvas/README.md +1 -1
- scitex/canvas/editor/_dearpygui/__init__.py +25 -0
- scitex/canvas/editor/_dearpygui/_editor.py +147 -0
- scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
- scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
- scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
- scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
- scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
- scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
- scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
- scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
- scitex/canvas/editor/_dearpygui/_selection.py +295 -0
- scitex/canvas/editor/_dearpygui/_state.py +93 -0
- scitex/canvas/editor/_dearpygui/_utils.py +61 -0
- scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
- scitex/cli/__init__.py +38 -43
- scitex/cli/audio.py +76 -27
- scitex/cli/capture.py +13 -20
- scitex/cli/introspect.py +443 -0
- scitex/cli/main.py +198 -109
- scitex/cli/mcp.py +60 -34
- scitex/cli/scholar/__init__.py +8 -0
- scitex/cli/scholar/_crossref_scitex.py +296 -0
- scitex/cli/scholar/_fetch.py +25 -3
- scitex/cli/social.py +314 -0
- scitex/cli/writer.py +117 -0
- scitex/config/README.md +1 -1
- scitex/config/__init__.py +16 -2
- scitex/config/_env_registry.py +191 -0
- scitex/diagram/__init__.py +42 -19
- scitex/diagram/mcp_server.py +13 -125
- scitex/introspect/__init__.py +75 -0
- scitex/introspect/_call_graph.py +303 -0
- scitex/introspect/_class_hierarchy.py +163 -0
- scitex/introspect/_core.py +42 -0
- scitex/introspect/_docstring.py +131 -0
- scitex/introspect/_examples.py +113 -0
- scitex/introspect/_imports.py +271 -0
- scitex/introspect/_mcp/__init__.py +37 -0
- scitex/introspect/_mcp/handlers.py +208 -0
- scitex/introspect/_members.py +151 -0
- scitex/introspect/_resolve.py +89 -0
- scitex/introspect/_signature.py +131 -0
- scitex/introspect/_source.py +80 -0
- scitex/introspect/_type_hints.py +172 -0
- scitex/io/bundle/README.md +1 -1
- scitex/mcp_server.py +98 -5
- scitex/plt/__init__.py +248 -550
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
- scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/plt/gallery/README.md +1 -1
- scitex/plt/utils/_hitmap/__init__.py +82 -0
- scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
- scitex/plt/utils/_hitmap/_color_application.py +346 -0
- scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
- scitex/plt/utils/_hitmap/_constants.py +40 -0
- scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
- scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
- scitex/plt/utils/_hitmap/_query.py +113 -0
- scitex/plt/utils/_hitmap.py +46 -1616
- scitex/plt/utils/_metadata/__init__.py +80 -0
- scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
- scitex/plt/utils/_metadata/_artists/_base.py +195 -0
- scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
- scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
- scitex/plt/utils/_metadata/_artists/_images.py +80 -0
- scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
- scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
- scitex/plt/utils/_metadata/_artists/_text.py +106 -0
- scitex/plt/utils/_metadata/_csv.py +416 -0
- scitex/plt/utils/_metadata/_detect.py +225 -0
- scitex/plt/utils/_metadata/_legend.py +127 -0
- scitex/plt/utils/_metadata/_rounding.py +117 -0
- scitex/plt/utils/_metadata/_verification.py +202 -0
- scitex/schema/README.md +1 -1
- scitex/scholar/__init__.py +8 -0
- scitex/scholar/_mcp/crossref_handlers.py +265 -0
- scitex/scholar/core/Scholar.py +63 -1700
- scitex/scholar/core/_mixins/__init__.py +36 -0
- scitex/scholar/core/_mixins/_enrichers.py +270 -0
- scitex/scholar/core/_mixins/_library_handlers.py +100 -0
- scitex/scholar/core/_mixins/_loaders.py +103 -0
- scitex/scholar/core/_mixins/_pdf_download.py +375 -0
- scitex/scholar/core/_mixins/_pipeline.py +312 -0
- scitex/scholar/core/_mixins/_project_handlers.py +125 -0
- scitex/scholar/core/_mixins/_savers.py +69 -0
- scitex/scholar/core/_mixins/_search.py +103 -0
- scitex/scholar/core/_mixins/_services.py +88 -0
- scitex/scholar/core/_mixins/_url_finding.py +105 -0
- scitex/scholar/crossref_scitex.py +367 -0
- scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/scholar/examples/00_run_all.sh +120 -0
- scitex/scholar/jobs/_executors.py +27 -3
- scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
- scitex/scholar/pdf_download/_cli.py +154 -0
- scitex/scholar/pdf_download/strategies/__init__.py +11 -8
- scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
- scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
- scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
- scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
- scitex/scholar/pipelines/_single_steps.py +71 -36
- scitex/scholar/storage/_LibraryManager.py +97 -1695
- scitex/scholar/storage/_mixins/__init__.py +30 -0
- scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
- scitex/scholar/storage/_mixins/_library_operations.py +218 -0
- scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
- scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
- scitex/scholar/storage/_mixins/_resolution.py +376 -0
- scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
- scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
- scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
- scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
- scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
- scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
- scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
- scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
- scitex/security/README.md +3 -3
- scitex/session/README.md +1 -1
- scitex/sh/README.md +1 -1
- scitex/social/__init__.py +153 -0
- scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/template/README.md +1 -1
- scitex/template/clone_writer_directory.py +5 -5
- scitex/writer/README.md +1 -1
- scitex/writer/_mcp/handlers.py +11 -744
- scitex/writer/_mcp/tool_schemas.py +5 -335
- scitex-2.15.1.dist-info/METADATA +648 -0
- {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/RECORD +166 -111
- scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
- scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
- scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
- scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
- scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
- scitex/diagram/_compile.py +0 -312
- scitex/diagram/_diagram.py +0 -355
- scitex/diagram/_mcp/__init__.py +0 -4
- scitex/diagram/_mcp/handlers.py +0 -400
- scitex/diagram/_mcp/tool_schemas.py +0 -157
- scitex/diagram/_presets.py +0 -173
- scitex/diagram/_schema.py +0 -182
- scitex/diagram/_split.py +0 -278
- scitex/plt/_mcp/__init__.py +0 -4
- scitex/plt/_mcp/_handlers_annotation.py +0 -102
- scitex/plt/_mcp/_handlers_figure.py +0 -195
- scitex/plt/_mcp/_handlers_plot.py +0 -252
- scitex/plt/_mcp/_handlers_style.py +0 -219
- scitex/plt/_mcp/handlers.py +0 -74
- scitex/plt/_mcp/tool_schemas.py +0 -497
- scitex/plt/mcp_server.py +0 -231
- scitex/scholar/data/.gitkeep +0 -0
- scitex/scholar/data/README.md +0 -44
- scitex/scholar/data/bib_files/bibliography.bib +0 -1952
- scitex/scholar/data/bib_files/neurovista.bib +0 -277
- scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
- scitex/scholar/data/bib_files/openaccess.bib +0 -89
- scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
- scitex/scholar/data/bib_files/pac.bib +0 -698
- scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
- scitex/scholar/data/bib_files/pac_processed.bib +0 -0
- scitex/scholar/data/bib_files/pac_titles.txt +0 -75
- scitex/scholar/data/bib_files/paywalled.bib +0 -98
- scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
- scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
- scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
- scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
- scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_seizure.bib +0 -46
- scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
- scitex/scholar/data/impact_factor.db +0 -0
- scitex/scholar/examples/SUGGESTIONS.md +0 -865
- scitex/scholar/examples/dev.py +0 -38
- scitex-2.14.0.dist-info/METADATA +0 -1238
- {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/WHEEL +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/entry_points.txt +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.1.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
|