Skip to content

Commit 7c75963

Browse files
Analysis: warn on degenerate gamut volume
iccviz's GamutVolume now flags a `degenerate` result (IccVizModel.cpp) when the gamut boundary collapsed or was mostly non-finite, so that intent's volume is unreliable. Surface it in the Analysis tab's Profile Statistics rather than presenting a bogus number as trustworthy: - plot-wrapper.cpp: gamutVolume() JSON now carries `degenerate` (it was dropped). - AnalysisPanel.jsx: capture the flag per intent and, near the gamut-volume display, render a ⚠ marker on the affected cell (tooltip) plus a note below the table when any intent is degenerate. - AnalysisPanel.module.css: add .warnMark / .statWarning (uses --warning). - i18n.jsx: new stats_gamut_degenerate key across all 12 locales; xlsx resynced (188 keys). Dormant until the next WASM rebuild: the committed iccplot.wasm predates both the wrapper's new field and IccVizModel.cpp's degenerate logic, so r.degenerate reads undefined (marker/note simply don't render) until the deploy-time rebuild picks both up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTfq9wJh9ZWT1fTN62LkDN
1 parent df2f057 commit 7c75963

13 files changed

Lines changed: 44 additions & 5 deletions

File tree

frontend/src/components/AnalysisPanel.jsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,13 @@ function ProfileStatsSection({ bytes, t }) {
6767
;(async () => {
6868
const rows = []
6969
for (const it of STATS_INTENTS) {
70-
let vol = null, rt = null
71-
try { vol = (await gamutVolume(bytes, it.tag, it.intent)).volume } catch { /* AToB tag absent */ }
70+
let vol = null, degenerate = false, rt = null
71+
try {
72+
const g = await gamutVolume(bytes, it.tag, it.intent)
73+
vol = g.volume; degenerate = !!g.degenerate // iccviz flags a collapsed/unreliable gamut boundary
74+
} catch { /* AToB tag absent */ }
7275
try { rt = await roundTripDE(bytes, it.intent) } catch { /* AToB/BToA tags absent */ }
73-
if (vol != null || rt != null) rows.push({ key: it.key, fallback: it.fallback, vol, rt })
76+
if (vol != null || rt != null) rows.push({ key: it.key, fallback: it.fallback, vol, degenerate, rt })
7477
}
7578
if (cancelled) return
7679
statsCache.set(bytes, rows)
@@ -95,6 +98,13 @@ function ProfileStatsSection({ bytes, t }) {
9598
rtPad[key] = strs.map((s) => ' '.repeat(w - s.length) + s)
9699
}
97100

101+
// iccviz sets `degenerate` when a gamut boundary collapsed / was mostly
102+
// non-finite, so that intent's volume is unreliable — flag it (⚠ on the cell +
103+
// a note below) rather than presenting a bogus number as trustworthy.
104+
const degenerateMsg = t('stats_gamut_degenerate') ||
105+
'The gamut boundary collapsed or was mostly undefined, so this gamut volume is unreliable.'
106+
const anyDegenerate = state.rows.some((r) => r.degenerate)
107+
98108
return (
99109
<Collapsible title={t('analysis_stats_heading') || 'Profile Statistics'} defaultOpen>
100110
<p className={styles.sectionIntro}>
@@ -126,7 +136,10 @@ function ProfileStatsSection({ bytes, t }) {
126136
{state.rows.map((r, i) => (
127137
<tr key={r.key}>
128138
<td>{t(r.key) || r.fallback}</td>
129-
<td className={styles.statNum}>{fmtVol(r.vol)}</td>
139+
<td className={styles.statNum}>
140+
{fmtVol(r.vol)}
141+
{r.degenerate && <span className={styles.warnMark} title={degenerateMsg} aria-label={degenerateMsg}></span>}
142+
</td>
130143
{RT.map(({ key }) => (
131144
<td key={key} className={styles.statNumC}>{rtPad[key][i]}</td>
132145
))}
@@ -135,6 +148,7 @@ function ProfileStatsSection({ bytes, t }) {
135148
</tbody>
136149
</table>
137150
)}
151+
{anyDegenerate && <div className={styles.statWarning}>{degenerateMsg}</div>}
138152
</Collapsible>
139153
)
140154
}

frontend/src/components/AnalysisPanel.module.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,19 @@
103103
.table th.statNum, .table td.statNum { text-align: right; }
104104
.table th.statNumC, .table td.statNumC { text-align: center; }
105105

106+
/* Degenerate-gamut flag: a ⚠ appended to the unreliable volume cell, plus a note
107+
below the table. Uses --warning so it reads as caution, not error. */
108+
.warnMark { color: var(--warning); cursor: help; }
109+
.statWarning {
110+
margin-top: 10px;
111+
padding: 8px 12px;
112+
border-radius: var(--radius);
113+
background: var(--surface-tint);
114+
color: var(--warning);
115+
font-size: 0.8rem;
116+
line-height: 1.4;
117+
}
118+
106119
/* ── status / loading / diagnostics ───────────────────────────────────────── */
107120
.status,
108121
.loading {

frontend/src/i18n.jsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const I18N = {
4141
analysis_stats_intro: 'Whole-profile metrics per rendering intent: the gamut volume enclosed by the device→PCS transform (ΔE*ab³), and the B2A round-trip accuracy — ΔE*ab of a Lab → device → Lab round trip through the profile.',
4242
analysis_stats_na: 'No device↔PCS CLUTs in this profile — profile statistics do not apply (e.g. a matrix/TRC display profile).',
4343
stats_intent: 'Rendering intent', stats_gamut_volume: 'Gamut volume (ΔE³)', stats_roundtrip: 'Round-trip ΔE',
44+
stats_gamut_degenerate: 'The gamut boundary collapsed or was mostly undefined, so this gamut volume is unreliable.',
4445
stats_mean: 'mean', stats_p90: 'P90', stats_max: 'max',
4546
analysis_loading: 'Analysing…', analysis_title: 'Analysis',
4647
analysis_intro: 'Profile-wide quality analyses derived from the device-to-PCS transform.',
@@ -152,6 +153,7 @@ export const I18N = {
152153
analysis_stats_na: 'Aucune CLUT périphérique↔PCS dans ce profil — les statistiques du profil ne s’appliquent pas (par exemple un profil d’affichage matrice/TRC).',
153154
stats_intent: 'Intention de rendu',
154155
stats_gamut_volume: 'Volume de gamut (ΔE³)',
156+
stats_gamut_degenerate: 'La frontière du gamut s’est effondrée ou était en grande partie indéfinie ; ce volume de gamut n’est pas fiable.',
155157
stats_roundtrip: 'ΔE aller-retour',
156158
stats_mean: 'moyenne',
157159
stats_p90: 'P90',
@@ -295,6 +297,7 @@ export const I18N = {
295297
analysis_stats_na: 'Keine Gerät↔PCS-CLUTs in diesem Profil — die Profilstatistik ist nicht anwendbar (z. B. ein Matrix/TRC-Displayprofil).',
296298
stats_intent: 'Render-Intent',
297299
stats_gamut_volume: 'Gamut-Volumen (ΔE³)',
300+
stats_gamut_degenerate: 'Die Gamut-Grenze ist kollabiert oder war größtenteils undefiniert; dieses Gamut-Volumen ist unzuverlässig.',
298301
stats_roundtrip: 'Roundtrip-ΔE',
299302
stats_mean: 'Mittel',
300303
stats_p90: 'P90',
@@ -438,6 +441,7 @@ export const I18N = {
438441
analysis_stats_na: 'Nessuna CLUT dispositivo↔PCS in questo profilo — le statistiche del profilo non si applicano (ad es. un profilo monitor matrice/TRC).',
439442
stats_intent: 'Intento di rendering',
440443
stats_gamut_volume: 'Volume del gamut (ΔE³)',
444+
stats_gamut_degenerate: 'Il confine del gamut è collassato o era in gran parte indefinito; questo volume di gamut non è affidabile.',
441445
stats_roundtrip: 'ΔE round-trip',
442446
stats_mean: 'media',
443447
stats_p90: 'P90',
@@ -581,6 +585,7 @@ export const I18N = {
581585
analysis_stats_na: 'Este perfil no tiene CLUT dispositivo↔PCS — las estadísticas del perfil no se aplican (p. ej. un perfil de pantalla matriz/TRC).',
582586
stats_intent: 'Propósito de representación',
583587
stats_gamut_volume: 'Volumen de gama (ΔE³)',
588+
stats_gamut_degenerate: 'El contorno de la gama se colapsó o quedó mayormente indefinido, por lo que este volumen de gama no es fiable.',
584589
stats_roundtrip: 'ΔE de ciclo',
585590
stats_mean: 'media',
586591
stats_p90: 'P90',
@@ -724,6 +729,7 @@ export const I18N = {
724729
analysis_stats_na: 'Este perfil não tem CLUTs dispositivo↔PCS — as estatísticas do perfil não se aplicam (por exemplo, um perfil de ecrã matriz/TRC).',
725730
stats_intent: 'Intenção de renderização',
726731
stats_gamut_volume: 'Volume de gamute (ΔE³)',
732+
stats_gamut_degenerate: 'A fronteira do gamute colapsou ou ficou maioritariamente indefinida, pelo que este volume de gamute não é fiável.',
727733
stats_roundtrip: 'ΔE de percurso',
728734
stats_mean: 'média',
729735
stats_p90: 'P90',
@@ -867,6 +873,7 @@ export const I18N = {
867873
analysis_stats_na: 'Este perfil não tem CLUTs dispositivo↔PCS — as estatísticas do perfil não se aplicam (por exemplo, um perfil de tela matriz/TRC).',
868874
stats_intent: 'Intenção de renderização',
869875
stats_gamut_volume: 'Volume de gamut (ΔE³)',
876+
stats_gamut_degenerate: 'A fronteira do gamut colapsou ou ficou majoritariamente indefinida, então este volume de gamut não é confiável.',
870877
stats_roundtrip: 'ΔE de percurso',
871878
stats_mean: 'média',
872879
stats_p90: 'P90',
@@ -1010,6 +1017,7 @@ export const I18N = {
10101017
analysis_stats_na: 'Inga enhet↔PCS-CLUT:ar i den här profilen — profilstatistiken gäller inte (t.ex. en matris/TRC-skärmprofil).',
10111018
stats_intent: 'Renderingsavsikt',
10121019
stats_gamut_volume: 'Gamutvolym (ΔE³)',
1020+
stats_gamut_degenerate: 'Gamytens gräns kollapsade eller var mestadels odefinierad, så denna gamutvolym är otillförlitlig.',
10131021
stats_roundtrip: 'Rundtur-ΔE',
10141022
stats_mean: 'medel',
10151023
stats_p90: 'P90',
@@ -1153,6 +1161,7 @@ export const I18N = {
11531161
analysis_stats_na: '此特性文件没有设备↔PCS CLUT——特性文件统计不适用(例如矩阵/TRC 显示器特性文件)。',
11541162
stats_intent: '渲染意图',
11551163
stats_gamut_volume: '色域体积 (ΔE³)',
1164+
stats_gamut_degenerate: '色域边界塌陷或大部分未定义,因此此色域体积不可靠。',
11561165
stats_roundtrip: '往返 ΔE',
11571166
stats_mean: '均值',
11581167
stats_p90: 'P90',
@@ -1296,6 +1305,7 @@ export const I18N = {
12961305
analysis_stats_na: '此特性檔沒有裝置↔PCS CLUT——特性檔統計不適用(例如矩陣/TRC 顯示器特性檔)。',
12971306
stats_intent: '轉換意圖',
12981307
stats_gamut_volume: '色域體積 (ΔE³)',
1308+
stats_gamut_degenerate: '色域邊界塌陷或大部分未定義,因此此色域體積不可靠。',
12991309
stats_roundtrip: '往返 ΔE',
13001310
stats_mean: '平均',
13011311
stats_p90: 'P90',
@@ -1439,6 +1449,7 @@ export const I18N = {
14391449
analysis_stats_na: 'このプロファイルにはデバイス↔PCS CLUT がありません——プロファイル統計は適用されません(例:マトリクス/TRC ディスプレイプロファイル)。',
14401450
stats_intent: 'レンダリングインテント',
14411451
stats_gamut_volume: '色域体積 (ΔE³)',
1452+
stats_gamut_degenerate: 'この色域の境界が崩壊したか、ほとんど未定義のため、この色域体積は信頼できません。',
14421453
stats_roundtrip: '往復 ΔE',
14431454
stats_mean: '平均',
14441455
stats_p90: 'P90',
@@ -1582,6 +1593,7 @@ export const I18N = {
15821593
analysis_stats_na: '이 프로파일에는 장치↔PCS CLUT가 없습니다 — 프로파일 통계가 적용되지 않습니다(예: 행렬/TRC 디스플레이 프로파일).',
15831594
stats_intent: '렌더링 의도',
15841595
stats_gamut_volume: '색역 부피 (ΔE³)',
1596+
stats_gamut_degenerate: '색역 경계가 붕괴되었거나 대부분 정의되지 않아 이 색역 부피를 신뢰할 수 없습니다.',
15851597
stats_roundtrip: '왕복 ΔE',
15861598
stats_mean: '평균',
15871599
stats_p90: 'P90',

translations/Eng-De.xlsx

279 Bytes
Binary file not shown.

translations/Eng-Es.xlsx

282 Bytes
Binary file not shown.

translations/Eng-Fr.xlsx

290 Bytes
Binary file not shown.

translations/Eng-It.xlsx

278 Bytes
Binary file not shown.

translations/Eng-Ja.xlsx

290 Bytes
Binary file not shown.

translations/Eng-Ko.xlsx

284 Bytes
Binary file not shown.

translations/Eng-Pt.xlsx

432 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)