evalrx 0.1.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 (339) hide show
  1. evalrx/__init__.py +139 -0
  2. evalrx/agent_assets/__init__.py +2 -0
  3. evalrx/agent_assets/skills/README.md +28 -0
  4. evalrx/agent_assets/skills/eval-chart-style/SKILL.md +172 -0
  5. evalrx/agent_assets/skills/evalrx-report-ui/SKILL.md +116 -0
  6. evalrx/agent_assets/skills/nature-figure/LICENSE +201 -0
  7. evalrx/agent_assets/skills/nature-figure/README.md +412 -0
  8. evalrx/agent_assets/skills/nature-figure/SKILL.md +60 -0
  9. evalrx/agent_assets/skills/nature-figure/manifest.yaml +59 -0
  10. evalrx/agent_assets/skills/nature-figure/references/api.md +436 -0
  11. evalrx/agent_assets/skills/nature-figure/references/backend-selection.md +100 -0
  12. evalrx/agent_assets/skills/nature-figure/references/chart-types.md +281 -0
  13. evalrx/agent_assets/skills/nature-figure/references/common-patterns.md +350 -0
  14. evalrx/agent_assets/skills/nature-figure/references/demos.md +65 -0
  15. evalrx/agent_assets/skills/nature-figure/references/design-theory.md +439 -0
  16. evalrx/agent_assets/skills/nature-figure/references/figure-contract.md +93 -0
  17. evalrx/agent_assets/skills/nature-figure/references/figure-legend-conventions.md +71 -0
  18. evalrx/agent_assets/skills/nature-figure/references/nature-2026-observations.md +112 -0
  19. evalrx/agent_assets/skills/nature-figure/references/qa-contract.md +119 -0
  20. evalrx/agent_assets/skills/nature-figure/references/r-template-index.md +66 -0
  21. evalrx/agent_assets/skills/nature-figure/references/r-workflow.md +161 -0
  22. evalrx/agent_assets/skills/nature-figure/references/tutorials.md +251 -0
  23. evalrx/agent_assets/skills/nature-figure/static/core/contract.md +29 -0
  24. evalrx/agent_assets/skills/nature-figure/static/core/stance.md +37 -0
  25. evalrx/agent_assets/skills/nature-figure/static/fragments/backend/python.md +37 -0
  26. evalrx/agent_assets/skills/nature-figure/static/fragments/backend/r.md +44 -0
  27. evalrx/agent_assets/skills/outcome-driver-analysis/SKILL.md +213 -0
  28. evalrx/agent_assets/skills/outcome-driver-analysis/assets/analysis_report_template.md +53 -0
  29. evalrx/agent_assets/skills/outcome-driver-analysis/references/model_selection.md +72 -0
  30. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.R +130 -0
  31. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.py +150 -0
  32. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.R +181 -0
  33. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.py +186 -0
  34. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.R +149 -0
  35. evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.py +177 -0
  36. evalrx/agent_assets/skills.py +27 -0
  37. evalrx/agent_runtime/__init__.py +78 -0
  38. evalrx/agent_runtime/_docker_runner.py +89 -0
  39. evalrx/agent_runtime/cli_runtime.py +103 -0
  40. evalrx/agent_runtime/cli_transcript.py +138 -0
  41. evalrx/agent_runtime/cli_types.py +68 -0
  42. evalrx/agent_runtime/codegen/__init__.py +5 -0
  43. evalrx/agent_runtime/codegen/runner.py +94 -0
  44. evalrx/agent_runtime/experiment_harness.py +117 -0
  45. evalrx/agent_runtime/factory.py +102 -0
  46. evalrx/agent_runtime/json_shape.py +44 -0
  47. evalrx/agent_runtime/judges/__init__.py +28 -0
  48. evalrx/agent_runtime/judges/agy.py +179 -0
  49. evalrx/agent_runtime/judges/autodetect.py +135 -0
  50. evalrx/agent_runtime/judges/claude.py +159 -0
  51. evalrx/agent_runtime/judges/codex.py +120 -0
  52. evalrx/agent_runtime/providers/__init__.py +21 -0
  53. evalrx/agent_runtime/providers/antigravity.py +31 -0
  54. evalrx/agent_runtime/providers/base.py +145 -0
  55. evalrx/agent_runtime/providers/claude_code.py +49 -0
  56. evalrx/agent_runtime/providers/codex.py +37 -0
  57. evalrx/agent_runtime/providers/gemini_cli.py +26 -0
  58. evalrx/agent_runtime/providers/kimi_cli.py +27 -0
  59. evalrx/agent_runtime/providers/opencode.py +27 -0
  60. evalrx/agent_runtime/providers/registry.py +58 -0
  61. evalrx/agent_runtime/sandbox.py +517 -0
  62. evalrx/agent_runtime/skill_audit.py +143 -0
  63. evalrx/agent_runtime/skills/__init__.py +19 -0
  64. evalrx/agent_runtime/skills/installer.py +68 -0
  65. evalrx/agent_runtime/skills/prompt_policy.py +86 -0
  66. evalrx/agent_runtime/skills/resolver.py +19 -0
  67. evalrx/analysis/__init__.py +132 -0
  68. evalrx/analysis/adjudicate.py +154 -0
  69. evalrx/analysis/analysis_module.py +361 -0
  70. evalrx/analysis/api.py +171 -0
  71. evalrx/analysis/case_studio.py +651 -0
  72. evalrx/analysis/cli.py +114 -0
  73. evalrx/analysis/dashboard.py +350 -0
  74. evalrx/analysis/eval_case_matrix.py +118 -0
  75. evalrx/analysis/eval_viz_theme.py +833 -0
  76. evalrx/analysis/explore_run.py +333 -0
  77. evalrx/analysis/explorer.py +1276 -0
  78. evalrx/analysis/failure_modes.py +607 -0
  79. evalrx/analysis/fused_pipeline.py +489 -0
  80. evalrx/analysis/holdout.py +300 -0
  81. evalrx/analysis/hypothesis_agent.py +230 -0
  82. evalrx/analysis/narration.py +177 -0
  83. evalrx/analysis/operationalize.py +442 -0
  84. evalrx/analysis/plain_language.py +42 -0
  85. evalrx/analysis/planner.py +283 -0
  86. evalrx/analysis/probe_search.py +203 -0
  87. evalrx/analysis/profile.py +268 -0
  88. evalrx/analysis/prompts/__init__.py +0 -0
  89. evalrx/analysis/prompts/explorer.py +417 -0
  90. evalrx/analysis/prompts/failure_modes.py +33 -0
  91. evalrx/analysis/prompts/holdout.py +27 -0
  92. evalrx/analysis/prompts/hypothesis_agent.py +78 -0
  93. evalrx/analysis/prompts/run_codebase.py +47 -0
  94. evalrx/analysis/prompts/stats_agent.py +72 -0
  95. evalrx/analysis/prompts/stats_tool_generator.py +43 -0
  96. evalrx/analysis/result_marker.py +47 -0
  97. evalrx/analysis/run_codebase.py +242 -0
  98. evalrx/analysis/run_view.py +205 -0
  99. evalrx/analysis/stage_views.py +93 -0
  100. evalrx/analysis/stats_agent.py +944 -0
  101. evalrx/analysis/stats_tool_agent.py +261 -0
  102. evalrx/analysis/stats_tool_generator.py +415 -0
  103. evalrx/analysis/stats_tools.py +1153 -0
  104. evalrx/analysis/trajectory_records.py +193 -0
  105. evalrx/analysis/workbench.py +431 -0
  106. evalrx/analyzers/__init__.py +42 -0
  107. evalrx/analyzers/agent/__init__.py +25 -0
  108. evalrx/analyzers/agent/counterfactual.py +84 -0
  109. evalrx/analyzers/agent/first_error_judge.py +96 -0
  110. evalrx/analyzers/agent/ignored_obs.py +81 -0
  111. evalrx/analyzers/agent/loop_detect.py +79 -0
  112. evalrx/analyzers/agent/reliability.py +165 -0
  113. evalrx/analyzers/agent/tool_shap.py +225 -0
  114. evalrx/analyzers/agent/trajectory_rubric.py +168 -0
  115. evalrx/analyzers/attention/__init__.py +19 -0
  116. evalrx/analyzers/attention/relative_attn.py +610 -0
  117. evalrx/analyzers/attention/rollout.py +73 -0
  118. evalrx/analyzers/attention/sink.py +56 -0
  119. evalrx/analyzers/attention/summary.py +190 -0
  120. evalrx/analyzers/attribution/__init__.py +6 -0
  121. evalrx/analyzers/attribution/generic_attn.py +31 -0
  122. evalrx/analyzers/attribution/gradcam.py +30 -0
  123. evalrx/analyzers/base.py +12 -0
  124. evalrx/analyzers/geometry/__init__.py +6 -0
  125. evalrx/analyzers/geometry/cka.py +70 -0
  126. evalrx/analyzers/geometry/linear_probe.py +157 -0
  127. evalrx/analyzers/hallucination/__init__.py +9 -0
  128. evalrx/analyzers/hallucination/chair.py +78 -0
  129. evalrx/analyzers/hallucination/opera.py +29 -0
  130. evalrx/analyzers/hallucination/pope.py +119 -0
  131. evalrx/analyzers/hallucination/selfcheck.py +155 -0
  132. evalrx/analyzers/hallucination/vcd.py +29 -0
  133. evalrx/analyzers/lens/__init__.py +7 -0
  134. evalrx/analyzers/lens/layer_contrast.py +133 -0
  135. evalrx/analyzers/lens/logit_lens.py +138 -0
  136. evalrx/analyzers/lens/tuned_lens.py +30 -0
  137. evalrx/analyzers/patching/__init__.py +5 -0
  138. evalrx/analyzers/patching/causal_trace.py +30 -0
  139. evalrx/analyzers/perturbation/__init__.py +23 -0
  140. evalrx/analyzers/perturbation/_shapley.py +54 -0
  141. evalrx/analyzers/perturbation/context_shap.py +174 -0
  142. evalrx/analyzers/perturbation/cot_faithfulness.py +239 -0
  143. evalrx/analyzers/perturbation/format_sensitivity.py +237 -0
  144. evalrx/analyzers/perturbation/mm_shap.py +146 -0
  145. evalrx/analyzers/perturbation/modality_ablation.py +196 -0
  146. evalrx/analyzers/perturbation/perturbation_battery.py +274 -0
  147. evalrx/analyzers/perturbation/prompt_contrast.py +265 -0
  148. evalrx/analyzers/perturbation/rise.py +94 -0
  149. evalrx/analyzers/perturbation/vl_shap.py +102 -0
  150. evalrx/analyzers/reasoning/__init__.py +33 -0
  151. evalrx/analyzers/reasoning/_text.py +328 -0
  152. evalrx/analyzers/reasoning/answer_extraction_audit.py +327 -0
  153. evalrx/analyzers/reasoning/arith_audit.py +226 -0
  154. evalrx/analyzers/reasoning/contamination.py +214 -0
  155. evalrx/analyzers/reasoning/knowledge_split.py +253 -0
  156. evalrx/analyzers/reasoning/self_repair.py +246 -0
  157. evalrx/analyzers/reasoning/step_rollout_value.py +216 -0
  158. evalrx/analyzers/reasoning/termination_audit.py +258 -0
  159. evalrx/analyzers/uncertainty/__init__.py +18 -0
  160. evalrx/analyzers/uncertainty/calibration.py +174 -0
  161. evalrx/analyzers/uncertainty/coverage_gap.py +199 -0
  162. evalrx/analyzers/uncertainty/entropy.py +90 -0
  163. evalrx/analyzers/uncertainty/logprob_entropy.py +69 -0
  164. evalrx/analyzers/uncertainty/self_consistency.py +204 -0
  165. evalrx/analyzers/uncertainty/verbalized_conf.py +64 -0
  166. evalrx/cli.py +411 -0
  167. evalrx/config.py +77 -0
  168. evalrx/contract/__init__.py +179 -0
  169. evalrx/contract/common.py +452 -0
  170. evalrx/contract/emit.py +948 -0
  171. evalrx/contract/export.py +237 -0
  172. evalrx/contract/m1.py +325 -0
  173. evalrx/contract/m2.py +317 -0
  174. evalrx/contract/m3.py +165 -0
  175. evalrx/contract/m4.py +130 -0
  176. evalrx/contract/m5.py +292 -0
  177. evalrx/contract/methodology.py +76 -0
  178. evalrx/contract/pre_m1.py +58 -0
  179. evalrx/contract/typescript.py +140 -0
  180. evalrx/core/__init__.py +85 -0
  181. evalrx/core/analyzer.py +174 -0
  182. evalrx/core/capability.py +54 -0
  183. evalrx/core/case.py +443 -0
  184. evalrx/core/experiment.py +106 -0
  185. evalrx/core/model.py +198 -0
  186. evalrx/core/pipeline.py +42 -0
  187. evalrx/core/registry.py +142 -0
  188. evalrx/core/result.py +64 -0
  189. evalrx/core/spec.py +173 -0
  190. evalrx/core/tokentype.py +165 -0
  191. evalrx/core/tool.py +92 -0
  192. evalrx/datasets/__init__.py +41 -0
  193. evalrx/datasets/base.py +68 -0
  194. evalrx/datasets/gui_os.py +52 -0
  195. evalrx/datasets/llm_qa.py +57 -0
  196. evalrx/datasets/pure_qa.py +12 -0
  197. evalrx/datasets/vlm_qa.py +695 -0
  198. evalrx/datasets/web_search_qa.py +52 -0
  199. evalrx/eval_agent/__init__.py +341 -0
  200. evalrx/eval_agent/_tools.py +81 -0
  201. evalrx/eval_agent/ab_runner.py +50 -0
  202. evalrx/eval_agent/agentic/__init__.py +43 -0
  203. evalrx/eval_agent/agentic/actions.py +216 -0
  204. evalrx/eval_agent/agentic/board.py +107 -0
  205. evalrx/eval_agent/agentic/loop.py +190 -0
  206. evalrx/eval_agent/agentic/tools.py +538 -0
  207. evalrx/eval_agent/checkpoint.py +57 -0
  208. evalrx/eval_agent/cli_agent.py +59 -0
  209. evalrx/eval_agent/cli_skills.py +5 -0
  210. evalrx/eval_agent/evolution.py +396 -0
  211. evalrx/eval_agent/git_manager.py +215 -0
  212. evalrx/eval_agent/hypothesis.py +172 -0
  213. evalrx/eval_agent/label_quarantine.py +209 -0
  214. evalrx/eval_agent/legacy.py +530 -0
  215. evalrx/eval_agent/log_schema.py +497 -0
  216. evalrx/eval_agent/loop.py +2159 -0
  217. evalrx/eval_agent/loop_reports.py +116 -0
  218. evalrx/eval_agent/model_instrumentation.py +282 -0
  219. evalrx/eval_agent/narration.py +193 -0
  220. evalrx/eval_agent/nl_runner.py +460 -0
  221. evalrx/eval_agent/orchestrator.py +61 -0
  222. evalrx/eval_agent/preregister.py +93 -0
  223. evalrx/eval_agent/prompts/__init__.py +1 -0
  224. evalrx/eval_agent/prompts/agentic.py +46 -0
  225. evalrx/eval_agent/prompts/case_discovery.py +25 -0
  226. evalrx/eval_agent/prompts/diagnosis.py +125 -0
  227. evalrx/eval_agent/prompts/experiment_writer.py +265 -0
  228. evalrx/eval_agent/prompts/explore_step.py +37 -0
  229. evalrx/eval_agent/prompts/fix_agent.py +257 -0
  230. evalrx/eval_agent/prompts/hypothesis_tester.py +15 -0
  231. evalrx/eval_agent/prompts/nl_runner.py +38 -0
  232. evalrx/eval_agent/prompts/probe_agent.py +25 -0
  233. evalrx/eval_agent/prompts/probe_candidate_generator.py +14 -0
  234. evalrx/eval_agent/prompts/probe_generator.py +35 -0
  235. evalrx/eval_agent/prompts/whitebox_probe_generator.py +38 -0
  236. evalrx/eval_agent/report.py +58 -0
  237. evalrx/eval_agent/run_context.py +354 -0
  238. evalrx/eval_agent/run_log.schema.json +1215 -0
  239. evalrx/eval_agent/run_logger_v2.py +1764 -0
  240. evalrx/eval_agent/run_metadata.py +208 -0
  241. evalrx/eval_agent/stages/__init__.py +56 -0
  242. evalrx/eval_agent/stages/case_discovery.py +293 -0
  243. evalrx/eval_agent/stages/diagnosis.py +1017 -0
  244. evalrx/eval_agent/stages/experiment_writer.py +1634 -0
  245. evalrx/eval_agent/stages/fix_agent.py +3916 -0
  246. evalrx/eval_agent/stages/fix_internals.py +499 -0
  247. evalrx/eval_agent/stages/fix_pipeline.py +725 -0
  248. evalrx/eval_agent/stages/fix_tiers.py +187 -0
  249. evalrx/eval_agent/stages/fix_tools.py +1034 -0
  250. evalrx/eval_agent/stages/hypothesis_tester.py +1014 -0
  251. evalrx/eval_agent/stages/probe.py +439 -0
  252. evalrx/eval_agent/stages/probe_agent.py +1079 -0
  253. evalrx/eval_agent/stages/probe_candidate_generator.py +128 -0
  254. evalrx/eval_agent/stages/probe_generator.py +326 -0
  255. evalrx/eval_agent/stages/probe_search_agent.py +106 -0
  256. evalrx/eval_agent/stages/protocol.py +112 -0
  257. evalrx/eval_agent/stages/repair_catalog.py +273 -0
  258. evalrx/eval_agent/stages/surgery.py +524 -0
  259. evalrx/eval_agent/stages/whitebox_probe_generator.py +351 -0
  260. evalrx/eval_agent/store.py +231 -0
  261. evalrx/logging_utils.py +112 -0
  262. evalrx/models/__init__.py +161 -0
  263. evalrx/models/_discover.py +101 -0
  264. evalrx/models/agent.py +380 -0
  265. evalrx/models/backends/__init__.py +58 -0
  266. evalrx/models/backends/api.py +169 -0
  267. evalrx/models/backends/base.py +57 -0
  268. evalrx/models/backends/gemini_compat.py +579 -0
  269. evalrx/models/backends/hf_local.py +2074 -0
  270. evalrx/models/backends/openai_compat.py +301 -0
  271. evalrx/models/backends/vllm_offline.py +116 -0
  272. evalrx/models/base.py +24 -0
  273. evalrx/models/blackbox/__init__.py +4 -0
  274. evalrx/models/blackbox/agent.py +31 -0
  275. evalrx/models/blackbox/base.py +29 -0
  276. evalrx/models/blackbox/gemini.py +279 -0
  277. evalrx/models/blackbox/llm_api.py +17 -0
  278. evalrx/models/blackbox/vlm_api.py +17 -0
  279. evalrx/models/compose.py +66 -0
  280. evalrx/models/inference.py +88 -0
  281. evalrx/models/paper_methods/__init__.py +8 -0
  282. evalrx/models/paper_methods/aad.py +53 -0
  283. evalrx/models/paper_methods/ifcd.py +204 -0
  284. evalrx/models/paper_methods/pai.py +164 -0
  285. evalrx/models/paper_methods/tcd.py +202 -0
  286. evalrx/models/paper_methods/vcd.py +45 -0
  287. evalrx/models/paper_methods/vicrop.py +137 -0
  288. evalrx/models/toolcodec.py +143 -0
  289. evalrx/models/tools/__init__.py +20 -0
  290. evalrx/models/tools/perception.py +300 -0
  291. evalrx/models/tools/visual.py +174 -0
  292. evalrx/models/whitebox/__init__.py +26 -0
  293. evalrx/models/whitebox/agent.py +31 -0
  294. evalrx/models/whitebox/base.py +24 -0
  295. evalrx/models/whitebox/qwen.py +61 -0
  296. evalrx/models/whitebox/qwen2_5_omni.py +29 -0
  297. evalrx/models/whitebox/qwen2_audio.py +25 -0
  298. evalrx/models/whitebox/qwen_omni.py +53 -0
  299. evalrx/models/whitebox/qwen_vl.py +62 -0
  300. evalrx/observability/__init__.py +21 -0
  301. evalrx/observability/envelope.py +122 -0
  302. evalrx/observability/outbox.py +111 -0
  303. evalrx/observability/tracer.py +882 -0
  304. evalrx/reporting/__init__.py +28 -0
  305. evalrx/reporting/case_study.py +947 -0
  306. evalrx/reporting/compiler.py +587 -0
  307. evalrx/reporting/dynamic.py +1882 -0
  308. evalrx/reporting/html_report.py +2225 -0
  309. evalrx/reporting/langfuse_exporter.py +38 -0
  310. evalrx/reporting/langfuse_source.py +155 -0
  311. evalrx/reporting/model.py +151 -0
  312. evalrx/reporting/run_events.py +184 -0
  313. evalrx/reporting/server.py +557 -0
  314. evalrx/reporting/stages.py +58 -0
  315. evalrx/reporting/static_export.py +142 -0
  316. evalrx/reporting/web_dist/index.html +146 -0
  317. evalrx/specs.py +727 -0
  318. evalrx/stats/__init__.py +47 -0
  319. evalrx/stats/api.py +192 -0
  320. evalrx/stats/bootstrap.py +86 -0
  321. evalrx/stats/ebh.py +27 -0
  322. evalrx/stats/evalue.py +98 -0
  323. evalrx/stats/friedman.py +138 -0
  324. evalrx/stats/mcnemar.py +40 -0
  325. evalrx/stats/multiplicity.py +159 -0
  326. evalrx/stats/subset_sampling.py +55 -0
  327. evalrx/term_links.py +43 -0
  328. evalrx/viz/__init__.py +7 -0
  329. evalrx/viz/labels.py +77 -0
  330. evalrx/viz/prompts.py +39 -0
  331. evalrx/viz/renderer.py +590 -0
  332. evalrx/viz/schema.py +36 -0
  333. evalrx/viz/style.py +134 -0
  334. evalrx-0.1.2.dist-info/METADATA +532 -0
  335. evalrx-0.1.2.dist-info/RECORD +339 -0
  336. evalrx-0.1.2.dist-info/WHEEL +5 -0
  337. evalrx-0.1.2.dist-info/entry_points.txt +3 -0
  338. evalrx-0.1.2.dist-info/licenses/LICENSE +121 -0
  339. evalrx-0.1.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,436 @@
1
+ # API Reference — Nature Figure Making
2
+
3
+ Conventions, constants, and reusable code blocks. Implement in your script or adapt as needed.
4
+
5
+ ---
6
+
7
+ ## Constants
8
+
9
+ ### PALETTE
10
+
11
+ ```python
12
+ # NOTE (EvalRX adaptation): the key names and family semantics are the
13
+ # original skill's; the VALUES are synced to the CVD-validated EvalRX
14
+ # dataviz palette so agent-drawn figures, host plotly charts, and host
15
+ # spec-rendered PNGs share ONE palette. See the notice in README.md.
16
+ PALETTE = {
17
+ "blue_main": "#2a78d6",
18
+ "blue_secondary": "#6da7ec",
19
+ "green_1": "#7ec07e",
20
+ "green_2": "#4ca74b",
21
+ "green_3": "#008300",
22
+ "red_1": "#ee9999",
23
+ "red_2": "#e97675",
24
+ "red_strong": "#e34948",
25
+ "neutral_light": "#c3c2b7",
26
+ "neutral_mid": "#898781",
27
+ "neutral_dark": "#52514e",
28
+ "neutral_black": "#0b0b0b",
29
+ "gold": "#eda100",
30
+ "teal": "#1baf7a",
31
+ "violet": "#4a3aa7",
32
+ "magenta":"#e87ba4",
33
+ }
34
+
35
+ # Slots 1-5 are validated as a categorical set (worst adjacent CVD dE 13.3
36
+ # with direct labels as relief); the 6th slot is muted ink for a
37
+ # reference/background series only -- never a data series that must be
38
+ # told apart by hue.
39
+ DEFAULT_COLORS = [
40
+ PALETTE["blue_main"],
41
+ PALETTE["green_3"],
42
+ PALETTE["red_strong"],
43
+ PALETTE["teal"],
44
+ PALETTE["violet"],
45
+ PALETTE["neutral_mid"],
46
+ ]
47
+
48
+ PALETTE_NMI_PASTEL = {
49
+ "baseline_dark": "#484878",
50
+ "baseline_mid": "#7884B4",
51
+ "baseline_soft": "#B4C0E4",
52
+ "ours_tiny": "#E4E4F0",
53
+ "ours_base": "#E4CCD8",
54
+ "ours_large": "#F0C0CC",
55
+ "bg_lilac": "#E0E0F0",
56
+ "bg_aqua": "#E0F0F0",
57
+ "bg_peach": "#F0E0D0",
58
+ "neutral_light": "#D8D8D8",
59
+ "neutral_mid": "#A8A8A8",
60
+ "neutral_dark": "#606060",
61
+ "delta_up": "#2E9E44",
62
+ "delta_down": "#E53935",
63
+ }
64
+
65
+ DEFAULT_COLORS_NMI_PASTEL = [
66
+ PALETTE_NMI_PASTEL["baseline_dark"],
67
+ PALETTE_NMI_PASTEL["baseline_mid"],
68
+ PALETTE_NMI_PASTEL["baseline_soft"],
69
+ PALETTE_NMI_PASTEL["ours_tiny"],
70
+ PALETTE_NMI_PASTEL["ours_base"],
71
+ PALETTE_NMI_PASTEL["ours_large"],
72
+ ]
73
+
74
+ PALETTE_NATURE_IMAGING = {
75
+ "bg": "#000000",
76
+ "context": "#B8B8B8",
77
+ "cyan": "#22D7E6",
78
+ "magenta": "#FF2AD4",
79
+ "white": "#FFFFFF",
80
+ }
81
+
82
+ PALETTE_NATURE_MATERIAL = {
83
+ "aqua": "#77D7D1",
84
+ "teal": "#33B5A5",
85
+ "lilac": "#B9A7E8",
86
+ "violet": "#7C6CCF",
87
+ "callout_red": "#E53935",
88
+ "neutral": "#D9D9D9",
89
+ }
90
+
91
+ PALETTE_NATURE_CLINICAL = {
92
+ "baseline": "#272727",
93
+ "week6": "#E28E2C",
94
+ "week13": "#D24B40",
95
+ "week26": "#5B8FD6",
96
+ "year1": "#7BAA5B",
97
+ "year2": "#C45AD6",
98
+ "group_band": "#F2E6D9",
99
+ }
100
+
101
+ PALETTE_NATURE_GENOMICS = {
102
+ "neutral_light": "#D8D8D8",
103
+ "neutral_mid": "#8F8F8F",
104
+ "wave1": "#D9544D",
105
+ "wave2": "#5B7FCA",
106
+ "wave3": "#B89BD9",
107
+ "outline": "#4D4D4D",
108
+ }
109
+ ```
110
+
111
+ Use `DEFAULT_COLORS` when color itself carries explicit semantic meaning (`hero`, `baseline`, `positive variant`).
112
+ Use `DEFAULT_COLORS_NMI_PASTEL` when several compared methods belong to one or two related families and the page
113
+ should feel visually unified.
114
+
115
+ ---
116
+
117
+ ## MANDATORY font + SVG rules (always first, no exceptions)
118
+
119
+ These three lines are **non-negotiable** and must appear at the top of every script,
120
+ before any figure is created. They guarantee editable text in SVG output:
121
+
122
+ ```python
123
+ plt.rcParams['font.family'] = 'sans-serif'
124
+ plt.rcParams['font.sans-serif'] = ['Arial', 'DejaVu Sans', 'Liberation Sans']
125
+ plt.rcParams['svg.fonttype'] = 'none' # keeps text as <text> nodes, not paths
126
+ ```
127
+
128
+ **Why `svg.fonttype = 'none'`**: matplotlib's default (`'path'`) converts every
129
+ glyph to a bezier path, making text unselectable, unsearchable, and impossible to
130
+ re-align in Illustrator / Inkscape. With `'none'`, text stays as SVG `<text>` elements
131
+ and font substitution happens at render time.
132
+
133
+ **Output format**: always save as `.svg` (primary). PNG/PDF are optional secondary
134
+ exports. Never use `.png` alone when the figure contains text that may need adjustment.
135
+
136
+ ---
137
+
138
+ ## apply_publication_style()
139
+
140
+ ```python
141
+ def apply_publication_style(font_size=16, axes_linewidth=2.5, use_tex=False):
142
+ """Apply Nature-style rcParams. Call once before creating any figures."""
143
+ # ── MANDATORY: editable SVG text ──────────────────────────────────────────
144
+ plt.rcParams['font.family'] = 'sans-serif'
145
+ plt.rcParams['font.sans-serif'] = ['Arial', 'DejaVu Sans', 'Liberation Sans']
146
+ plt.rcParams['svg.fonttype'] = 'none'
147
+ # ── Layout & style ────────────────────────────────────────────────────────
148
+ plt.rcParams['font.size'] = font_size
149
+ plt.rcParams['axes.spines.right'] = False
150
+ plt.rcParams['axes.spines.top'] = False
151
+ plt.rcParams['axes.linewidth'] = axes_linewidth
152
+ plt.rcParams['legend.frameon'] = False
153
+ if use_tex:
154
+ plt.rcParams['text.usetex'] = True
155
+ ```
156
+
157
+ **Presets:**
158
+ - Large bar panels: `apply_publication_style(font_size=24, axes_linewidth=3)`
159
+ - Compact figures: `apply_publication_style(font_size=15, axes_linewidth=2)`
160
+ - Dense journal-width multi-panels: `apply_publication_style(font_size=8, axes_linewidth=1)`
161
+ - LaTeX labels: `apply_publication_style(use_tex=True)`
162
+
163
+ ---
164
+
165
+ ## is_dark(hex_color, threshold=128)
166
+
167
+ ```python
168
+ def is_dark(hex_color, threshold=128):
169
+ """Return True if hex color is dark (use white text on it)."""
170
+ c = hex_color.lstrip('#')
171
+ r, g, b = int(c[0:2], 16), int(c[2:4], 16), int(c[4:6], 16)
172
+ return (0.299*r + 0.587*g + 0.114*b) < threshold
173
+ ```
174
+
175
+ ---
176
+
177
+ ## add_panel_label(ax, label, ...)
178
+
179
+ ```python
180
+ def add_panel_label(ax, label, x=-0.06, y=1.02, fontsize=14,
181
+ color='black', fontweight='bold'):
182
+ """Place a Nature-style panel label near the top-left edge."""
183
+ ax.text(
184
+ x, y, label,
185
+ transform=ax.transAxes,
186
+ fontsize=fontsize,
187
+ fontweight=fontweight,
188
+ color=color,
189
+ ha='left',
190
+ va='bottom',
191
+ )
192
+ ```
193
+
194
+ For dark image plates, move the label inside the panel and switch to white:
195
+ `add_panel_label(ax, 'a', x=0.01, y=0.98, color='white')`
196
+
197
+ ---
198
+
199
+ ## style_dark_image_ax(ax, ...)
200
+
201
+ ```python
202
+ def style_dark_image_ax(ax, facecolor='black'):
203
+ """Prepare an axes for microscopy / rendering plates."""
204
+ ax.set_facecolor(facecolor)
205
+ ax.set_xticks([])
206
+ ax.set_yticks([])
207
+ for spine in ax.spines.values():
208
+ spine.set_visible(False)
209
+ return ax
210
+ ```
211
+
212
+ ---
213
+
214
+ ## make_grouped_bar(ax, categories, series, labels, ...)
215
+
216
+ ```python
217
+ def make_grouped_bar(ax, categories, series, labels,
218
+ ylabel='Value', colors=None,
219
+ annotate=False, bar_width=0.8,
220
+ error_kw=None):
221
+ """
222
+ Grouped bar chart.
223
+
224
+ Parameters
225
+ ----------
226
+ ax : matplotlib Axes
227
+ categories : list[str] — x-axis category names (length K)
228
+ series : list[array] — one array per group (each length K)
229
+ labels : list[str] — legend label per group
230
+ ylabel : str
231
+ colors : list[str] | None — defaults to DEFAULT_COLORS; override with
232
+ DEFAULT_COLORS_NMI_PASTEL for unified-family figures
233
+ annotate : bool — print value above each bar
234
+ bar_width : float — total width for all bars in one category
235
+ error_kw : dict — passed to ax.bar as error_kw
236
+
237
+ Returns
238
+ -------
239
+ list[BarContainer]
240
+ """
241
+ import numpy as np
242
+ if colors is None:
243
+ colors = DEFAULT_COLORS
244
+ if error_kw is None:
245
+ error_kw = {'elinewidth': 2, 'capthick': 2, 'capsize': 10}
246
+ n_groups = len(series)
247
+ n_cats = len(categories)
248
+ w = bar_width / n_groups
249
+ x = np.arange(n_cats)
250
+ containers = []
251
+ for i, (vals, label, color) in enumerate(zip(series, labels, colors)):
252
+ offset = (i - (n_groups - 1) / 2) * w
253
+ bars = ax.bar(x + offset, vals, width=w, label=label,
254
+ color=color, edgecolor='black', linewidth=1.5,
255
+ error_kw=error_kw)
256
+ containers.append(bars)
257
+ if annotate:
258
+ for bar, val in zip(bars, vals):
259
+ ax.text(bar.get_x() + bar.get_width() / 2,
260
+ bar.get_height() + 0.01,
261
+ f'{val:.2f}', ha='center', va='bottom', fontsize=10)
262
+ ax.set_xticks(x)
263
+ ax.set_xticklabels(categories)
264
+ ax.set_ylabel(ylabel)
265
+ ax.legend()
266
+ return containers
267
+ ```
268
+
269
+ ---
270
+
271
+ ## make_trend(ax, x, y_series, labels, ...)
272
+
273
+ ```python
274
+ def make_trend(ax, x, y_series, labels,
275
+ colors=None, ylabel=None, xlabel=None,
276
+ show_shadow=False, shadow_alpha=0.15,
277
+ lw=2.5, marker='o', markersize=8):
278
+ """
279
+ Multi-line trend plot.
280
+
281
+ Parameters
282
+ ----------
283
+ x : array-like — shared x values
284
+ y_series : list[array] — one 1D array per line
285
+ labels : list[str]
286
+ show_shadow : bool — fill_between ± std if y_series contains 2D arrays (rows=runs)
287
+ """
288
+ import numpy as np
289
+ if colors is None:
290
+ colors = DEFAULT_COLORS
291
+ for y, label, color in zip(y_series, labels, colors):
292
+ y = np.asarray(y)
293
+ if y.ndim == 2:
294
+ mean, std = y.mean(0), y.std(0)
295
+ else:
296
+ mean, std = y, None
297
+ ax.plot(x, mean, color=color, lw=lw, marker=marker,
298
+ markersize=markersize, label=label)
299
+ if show_shadow and std is not None:
300
+ ax.fill_between(x, mean - std, mean + std,
301
+ color=color, alpha=shadow_alpha)
302
+ if ylabel:
303
+ ax.set_ylabel(ylabel)
304
+ if xlabel:
305
+ ax.set_xlabel(xlabel)
306
+ ax.legend()
307
+ ```
308
+
309
+ ---
310
+
311
+ ## make_forest_plot(ax, labels, estimates, ci_low, ci_high, ...)
312
+
313
+ ```python
314
+ def make_forest_plot(ax, labels, estimates, ci_low, ci_high,
315
+ colors=None, ref=0.0, xlabel=None, xlim=None,
316
+ marker='o', markersize=5, lw=1.5):
317
+ """
318
+ Minimal forest plot helper for Nature-style clinical/statistical panels.
319
+ """
320
+ import numpy as np
321
+ y = np.arange(len(labels))[::-1]
322
+ if colors is None:
323
+ colors = ['#B64342'] * len(labels)
324
+ for yi, est, lo, hi, color in zip(y, estimates, ci_low, ci_high, colors):
325
+ ax.plot([lo, hi], [yi, yi], color=color, lw=lw)
326
+ ax.plot(est, yi, marker=marker, ms=markersize, color=color)
327
+ ax.axvline(ref, color='#767676', linestyle='--', linewidth=1.2, alpha=0.8)
328
+ ax.set_yticks(y)
329
+ ax.set_yticklabels(labels)
330
+ if xlabel:
331
+ ax.set_xlabel(xlabel)
332
+ if xlim is not None:
333
+ ax.set_xlim(xlim)
334
+ ax.spines['right'].set_visible(False)
335
+ ax.spines['top'].set_visible(False)
336
+ ```
337
+
338
+ Use pale `ax.axhspan(...)` bands behind contiguous label groups when you need the
339
+ clinical-triptych look from `Nature`.
340
+
341
+ ---
342
+
343
+ ## make_heatmap(ax, matrix, ...)
344
+
345
+ ```python
346
+ def make_heatmap(ax, matrix, x_labels=None, y_labels=None,
347
+ cmap='magma', cbar_label=None, annotate=False,
348
+ fmt='{:.2f}', fontsize=12):
349
+ """
350
+ 2D heatmap with optional colorbar and cell annotations.
351
+ """
352
+ import numpy as np
353
+ import matplotlib as mpl
354
+ im = ax.imshow(matrix, cmap=cmap, aspect='auto')
355
+ if cbar_label:
356
+ cbar = ax.figure.colorbar(im, ax=ax)
357
+ cbar.set_label(cbar_label)
358
+ if x_labels:
359
+ ax.set_xticks(range(len(x_labels)))
360
+ ax.set_xticklabels(x_labels, rotation=30, ha='right')
361
+ if y_labels:
362
+ ax.set_yticks(range(len(y_labels)))
363
+ ax.set_yticklabels(y_labels)
364
+ if annotate:
365
+ norm = mpl.colors.Normalize(vmin=matrix.min(), vmax=matrix.max())
366
+ cm_obj = plt.get_cmap(cmap)
367
+ for (i, j), val in np.ndenumerate(matrix):
368
+ r, g, b, _ = cm_obj(norm(val))
369
+ lum = 0.299*r + 0.587*g + 0.114*b
370
+ color = 'white' if lum < 0.5 else 'black'
371
+ ax.text(j, i, fmt.format(val), ha='center', va='center',
372
+ fontsize=fontsize, color=color)
373
+ ax.set_frame_on(False)
374
+ ```
375
+
376
+ ---
377
+
378
+ ## finalize_figure(fig, out_path, ...)
379
+
380
+ ```python
381
+ def finalize_figure(fig, out_path, formats=None, dpi=300,
382
+ pad=2, bbox_inches=None, close=True):
383
+ """
384
+ Apply tight_layout and save figure.
385
+
386
+ Parameters
387
+ ----------
388
+ out_path : str — path without extension, or with extension
389
+ formats : list — e.g. ['png', 'pdf']. If None, uses extension of out_path.
390
+ dpi : int — 300 standard, 600 for dense bar panels
391
+ pad : float — tight_layout pad (2 default, 1 for compact multi-panel)
392
+ """
393
+ import os
394
+ from pathlib import Path
395
+ fig.tight_layout(pad=pad)
396
+ base = Path(out_path)
397
+ os.makedirs(base.parent, exist_ok=True)
398
+ if formats is None:
399
+ formats = [base.suffix.lstrip('.') or 'png']
400
+ base = base.with_suffix('')
401
+ saved = []
402
+ for fmt in formats:
403
+ p = str(base) + f'.{fmt}'
404
+ kw = {}
405
+ if bbox_inches is not None:
406
+ kw['bbox_inches'] = bbox_inches
407
+ fig.savefig(p, dpi=dpi, **kw)
408
+ saved.append(p)
409
+ if close:
410
+ plt.close(fig)
411
+ return saved
412
+ ```
413
+
414
+ ---
415
+
416
+ ## Validation Rules
417
+
418
+ - `make_grouped_bar`: `len(categories)` must equal length of each array in `series`.
419
+ - `make_trend`: each array in `y_series` must have same length as `x`.
420
+ - `make_heatmap`: `matrix` must be 2D; `x_labels` length = `matrix.shape[1]`; `y_labels` length = `matrix.shape[0]`.
421
+ - `finalize_figure`: supported formats — `png`, `pdf`, `svg`, `eps`, `jpg`, `tif`.
422
+
423
+ ---
424
+
425
+ ## Conventions
426
+
427
+ - Save outputs under `./figures/` (or path given by user); `finalize_figure` creates parent dirs.
428
+ - In headless / batch runs, set non-interactive backend before importing pyplot:
429
+ ```python
430
+ import matplotlib
431
+ matplotlib.use('Agg')
432
+ import matplotlib.pyplot as plt
433
+ ```
434
+ - Always `plt.close(fig)` after saving to free memory.
435
+ - For multi-panel figures, prefer one baseline family plus one hero family; reserve green/red for delta cues.
436
+ - When color roles, resolution, or layout are underspecified and would change the figure, confirm with user before finalizing.
@@ -0,0 +1,100 @@
1
+ # Backend Selection
2
+
3
+ At the start of a figure task, ask the user to choose **Python or R** if they have
4
+ not already specified a backend. This is a blocking gate: stop after asking and wait
5
+ for the user's answer. Do not infer Python just because the task involves simulation,
6
+ NumPy-like data, or custom layout, and do not infer R just because the task is biological
7
+ or omics-adjacent.
8
+
9
+ Use the decision table only in either of these cases:
10
+
11
+ - the user explicitly asks you to recommend or choose the backend;
12
+ - the user provides an unambiguous language-specific workflow or file, such as an `.R`
13
+ script, RDS object, Python notebook, or existing Python plotting code.
14
+
15
+ ## Quick decision table
16
+
17
+ | Recommend R when | Recommend Python when |
18
+ |---|---|
19
+ | The user brings R scripts, RData/RDS, Seurat objects, DESeq2/limma outputs, survival models, or ggplot templates | The data pipeline is already Python, NumPy/Pandas arrays, PyTorch/TensorFlow outputs, image arrays, or simulation output |
20
+ | The target plot is `ggplot2`, `patchwork`, `ComplexHeatmap`, `ggtree`, `circlize`, `survminer`, `maftools`, or Seurat/UMAP-heavy | The target plot needs low-level custom layout, Matplotlib patches, image plates, subplot mosaics, or custom drawing primitives |
21
+ | The user provides an R template collection or an existing R plotting workflow | The user wants a self-contained script with matplotlib/seaborn/statsmodels and no R dependency |
22
+ | Heatmap annotations are biologically rich and multi-layered | Image panels and quantitative panels need tight pixel/axis control |
23
+
24
+ If either backend can do the job, honor the user's preference. Do not switch
25
+ backends for aesthetics alone.
26
+
27
+ ## Backend exclusivity rule
28
+
29
+ Backend choice is not just a syntax preference; it defines the graphics engine for
30
+ the entire deliverable. Once Python or R has been selected, use that backend for
31
+ all of the following:
32
+
33
+ - plotting scripts;
34
+ - mock/simulated data examples that include plotting;
35
+ - preview PNG/TIFF files;
36
+ - SVG/PDF/TIFF exports;
37
+ - visual QA renders and final layout checks.
38
+
39
+ Do not generate a substitute preview or export with the non-selected backend. For
40
+ example, if the user selected R and `Rscript` is missing, do not use Python/matplotlib
41
+ to approximate the figure. If the user selected Python and `matplotlib` or another
42
+ required Python plotting package is missing, do not use R/ggplot2/ComplexHeatmap to
43
+ approximate the figure. Stop, report the selected-backend blocker, and provide the
44
+ selected-backend script plus install/run instructions or request permission to install
45
+ the selected-backend dependencies.
46
+
47
+ The non-selected language is allowed only for non-visual utility work, such as
48
+ listing files, checking CSV dimensions, decompressing an archive, or converting a
49
+ data file before the selected backend draws the figure. It must not import plotting
50
+ libraries, open graphics devices, save image/vector files, or decide visual layout.
51
+
52
+ ## Default stacks
53
+
54
+ ### R
55
+
56
+ - Core plotting: `ggplot2`
57
+ - Multi-panel assembly: `patchwork`
58
+ - Heatmaps: `ComplexHeatmap`, `circlize`
59
+ - Direct labels: `ggrepel`
60
+ - Survival/clinical: `survival`, `survminer`, `forestplot`, `ggplot2`
61
+ - Single-cell/omics: `Seurat`, `SingleCellExperiment`, `ComplexHeatmap`, `ggtree`
62
+ - Export: `svglite`, `grDevices::cairo_pdf`, `ragg`
63
+
64
+ ### Python
65
+
66
+ - Core plotting: `matplotlib`
67
+ - Statistical plots: `seaborn`
68
+ - Layout: `subplot_mosaic`, `GridSpec`
69
+ - Tables/model output: `pandas`, `numpy`, `statsmodels`
70
+ - Images: `matplotlib.imshow`, `skimage`, `tifffile` when needed
71
+ - Export: `fig.savefig(... .svg/.pdf/.tiff)`, `svg.fonttype='none'`,
72
+ `pdf.fonttype=42`
73
+
74
+ ## Mixed workflow rule
75
+
76
+ Use the selected plotting backend for final assembly and all visual output. A mixed
77
+ workflow is reasonable only when the non-selected language performs non-visual data
78
+ preparation and the selected backend assembles the figure. In that case:
79
+
80
+ 1. Export clean source data as CSV/TSV with stable column names.
81
+ 2. Assemble the final figure in the selected backend.
82
+ 3. Keep the source-data file next to the plotting script.
83
+ 4. Do not stitch, preview, QA-render, or export final image/vector outputs from the
84
+ non-selected backend unless the user explicitly changes the selected backend.
85
+
86
+ ## Recommendation language
87
+
88
+ Use direct language:
89
+
90
+ ```text
91
+ For this figure I recommend R because the main burden is ComplexHeatmap-style
92
+ omics annotation and patchwork assembly. I will still keep the export contract
93
+ SVG/PDF/TIFF with editable text.
94
+ ```
95
+
96
+ ```text
97
+ For this figure I recommend Python because the key panel is a custom image plate
98
+ with quantitative overlays and a subplot_mosaic layout. Matplotlib gives tighter
99
+ control over the raster and vector layers.
100
+ ```