Skip to content

Commit

Permalink
Codec-Compare version 0.2.3 (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
y-guyon authored Aug 29, 2024
1 parent ce7ac0e commit 3437550
Show file tree
Hide file tree
Showing 15 changed files with 146 additions and 37 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## v0.2.3

- No longer limit JPEG XL input quality range to [75:99] by default (introduced
at v0.2.1). Expect custom bpp or quality filters to be defined in the presets
instead.
- Add lazy row loading setting for more reactive table panels.
- Add animation frame count support.
- Fade unused images in the gallery tab.
- Hide matched pairs in the graph by default if there are more than two
experiments.

## v0.2.2

- Add DSSIM distortion metric recognition.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codec_compare",
"version": "0.2.2",
"version": "0.2.3",
"description": "Codec performance comparison tool",
"publisher": "Google LLC",
"author": "Yannis Guyon",
Expand Down
Binary file modified readme_preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/codec_compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export class CodecCompare extends LitElement {
</p>
<p id="credits">
Codec Compare beta version 0.2.2<br>
Codec Compare beta version 0.2.3<br>
<a href="https://github.com/webmproject/codec-compare">
Sources on GitHub
</a>
Expand Down
2 changes: 2 additions & 0 deletions src/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export enum FieldId {
DECODED_IMAGE_NAME,
WIDTH, // Number of pixel columns in SOURCE_IMAGE_NAME.
HEIGHT, // Number of pixel rows in SOURCE_IMAGE_NAME.
FRAME_COUNT, // Number of frames in SOURCE_IMAGE_NAME.
EFFORT, // Encoding setting.
QUALITY, // Encoding setting.
PSNR, // Difference between original and decoded images.
Expand Down Expand Up @@ -93,6 +94,7 @@ const NAME_TO_FIELD_ID = new Map<string, FieldId>([
['decoded image name', FieldId.DECODED_IMAGE_NAME],
['width', FieldId.WIDTH],
['height', FieldId.HEIGHT],
['frame count', FieldId.FRAME_COUNT],
['effort', FieldId.EFFORT],
['quality', FieldId.QUALITY],
['psnr', FieldId.PSNR],
Expand Down
8 changes: 7 additions & 1 deletion src/entry_loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ function jsonToBatch(
batch.fields.findIndex(field => field.id === FieldId.WIDTH);
const heightFieldIndex =
batch.fields.findIndex(field => field.id === FieldId.HEIGHT);
const frameCountFieldIndex =
batch.fields.findIndex(field => field.id === FieldId.FRAME_COUNT);
const encodedSizeFieldIndex =
batch.fields.findIndex(field => field.id === FieldId.ENCODED_SIZE);
if (widthFieldIndex !== -1 && heightFieldIndex !== -1 &&
Expand All @@ -222,9 +224,13 @@ function jsonToBatch(
batch.fields.push(field);
uniqueValuesSets.push(new Set<string>());
for (const row of batch.rows) {
const bpp = (row[encodedSizeFieldIndex] as number) * 8 /
let bpp = (row[encodedSizeFieldIndex] as number) * 8 /
((row[widthFieldIndex] as number) *
(row[heightFieldIndex] as number));
if (frameCountFieldIndex !== -1 &&
batch.fields[frameCountFieldIndex].isInteger) {
bpp /= (row[frameCountFieldIndex] as number);
}
row.push(bpp);
field.addValue(String(bpp), uniqueValuesSets[fieldIndex]);
}
Expand Down
4 changes: 4 additions & 0 deletions src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export enum EventType {

// The state changed and the UI needs to reflect them.
MATCHED_DATA_POINTS_CHANGED = 'MATCHED_DATA_POINTS_CHANGED',

// Some setting was toggled.
SETTINGS_CHANGED = 'SETTINGS_CHANGED',
}

/**
Expand All @@ -63,6 +66,7 @@ declare global {
REFERENCE_CHANGED: Event;
MATCHER_OR_METRIC_CHANGED: Event;
MATCHED_DATA_POINTS_CHANGED: Event;
SETTINGS_CHANGED: Event;
}
}

Expand Down
17 changes: 1 addition & 16 deletions src/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class FieldFilter {
}
}

/** Arbitrarily enables some filters (focusing on lossy image comparison). */
/** Arbitrarily enables some filters. */
export function enableDefaultFilters(batch: Batch, filters: FieldFilter[]) {
// Pick the highest (usually meaning slowest) effort for best compression
// results.
Expand All @@ -50,21 +50,6 @@ export function enableDefaultFilters(batch: Batch, filters: FieldFilter[]) {
effortFilter.rangeStart = effortFilter.rangeEnd;
}
}

// Restrict JPEG XL to a "reasonable" encoder quality setting range.
if (batch.codec.toLowerCase() === 'jxl' ||
batch.codec.toLowerCase() === 'jpegxl') {
const qualityIndex =
batch.fields.findIndex((field: Field) => field.id === FieldId.QUALITY);
if (qualityIndex !== -1) {
const qualityField = batch.fields[qualityIndex];
if (qualityField.isInteger && qualityField.uniqueValuesArray.length > 1) {
filters[qualityIndex].enabled = true;
filters[qualityIndex].rangeStart =
Math.max(filters[qualityIndex].rangeStart, 75);
}
}
}
}

function rowPassesFilter(
Expand Down
31 changes: 30 additions & 1 deletion src/filtered_images_ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {customElement, property} from 'lit/decorators.js';

import {BatchSelection} from './batch_selection';
import {Entry} from './entry';
import {dispatch, EventType} from './events';
import {State} from './state';

/** Component displaying filtered images in a table. */
Expand All @@ -44,6 +45,24 @@ export class FilteredImagesUi extends LitElement {

override render() {
const batch = this.batchSelection.batch;
let rows = batch.rows;
let truncatedRows = html``;
if (!this.state.showAllRows && rows.length > 100) {
const onDisplayHiddenRow = () => {
this.state.showAllRows = true;
dispatch(EventType.SETTINGS_CHANGED);
this.requestUpdate();
};
truncatedRows = html`
<tr>
<td @click=${onDisplayHiddenRow}
colspan=${batch.fields.length} class="hiddenRow">
${rows.length - 100} hidden rows. Click to expand.
</td>
</tr>`;
rows = rows.slice(0, 100);
}

return html`
<div class="horizontalFlex">
<div id="imageChip">
Expand Down Expand Up @@ -71,7 +90,8 @@ export class FilteredImagesUi extends LitElement {
(field) => html`<th title="${field.description}">${
field.displayName}</th>`)}
</tr>
${batch.rows.map((row, rowIndex) => this.renderRow(row, rowIndex))}
${rows.map((row, rowIndex) => this.renderRow(row, rowIndex))}
${truncatedRows}
</table>
</div>`;
}
Expand Down Expand Up @@ -151,6 +171,15 @@ export class FilteredImagesUi extends LitElement {
color: grey;
}
.hiddenRow {
color: grey;
font-style: italic;
text-align: center;
}
.hiddenRow:hover {
cursor: pointer;
}
a {
text-decoration: none;
}
Expand Down
7 changes: 6 additions & 1 deletion src/gallery_ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export class GalleryUi extends LitElement {
if (asset.sourcePath !== undefined) {
// Use a link to open the image in a new tab.
return html`
<a href="${asset.sourcePath}" target="_blank" title="${title}">
<a href="${asset.sourcePath}" target="_blank" title="${title}"
class="${asset.count == 0 ? 'unused' : ''}">
<img src="${asset.previewPath}" class="constrainedSize"
alt="${asset.sourceName}">
<span class="countBubble">${asset.count}</span>
Expand Down Expand Up @@ -194,6 +195,10 @@ export class GalleryUi extends LitElement {
font-size: 16px;
}
.unused {
opacity: 0.2;
}
.countBubble {
position: absolute;
top: 5px;
Expand Down
1 change: 1 addition & 0 deletions src/matcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export function createMatchers(batches: Batch[]): FieldMatcher[] {
if (field.id === FieldId.DECODED_IMAGE_NAME) continue;
// Same source image, so these will always match. Remove them from the UI.
if (field.id === FieldId.WIDTH || field.id === FieldId.HEIGHT) continue;
if (field.id === FieldId.FRAME_COUNT) continue;
// If bpp values are available, encoded sizes probably are too.
// Skip the former which brings nothing as a matcher over the latter.
if (field.id === FieldId.ENCODED_BITS_PER_PIXEL) continue;
Expand Down
31 changes: 29 additions & 2 deletions src/matches_table_ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ export class MatchesTableUi extends LitElement {
const cssClass = isNumber ? 'numberCell' : '';

if (isNumber && selectionField.id !== FieldId.WIDTH &&
selectionField.id !== FieldId.HEIGHT) {
selectionField.id !== FieldId.HEIGHT &&
selectionField.id !== FieldId.FRAME_COUNT) {
const ratio =
getRatio(selectionValue as number, referenceValue as number);
return html`<td class="${cssClass}">${getRelativePercent(ratio)}</td>`;
Expand Down Expand Up @@ -202,14 +203,32 @@ export class MatchesTableUi extends LitElement {

const numColumns =
matchers.length + metrics.length + selectionSharedFieldIndices.length;
let rows = this.batchSelection.matchedDataPoints.rows;
let truncatedRows = html``;
if (!this.state.showAllRows && rows.length > 100) {
const onDisplayHiddenRow = () => {
this.state.showAllRows = true;
dispatch(EventType.SETTINGS_CHANGED);
this.requestUpdate();
};
truncatedRows = html`
<tr>
<td @click=${onDisplayHiddenRow}
colspan=${numColumns} class="hiddenRow">
${rows.length - 100} hidden rows. Click to expand.
</td>
</tr>`;
rows = rows.slice(0, 100);
}

return html`
<table>
${
this.renderFirstHeaderRow(
reference, matchers, metrics, referenceSharedFieldIndices)}
${this.renderSecondHeaderRow(reference, matchers, metrics)}
${this.batchSelection.matchedDataPoints.rows.map(renderRow)}
${rows.map(renderRow)}
${truncatedRows}
<tr>
<th colspan=${numColumns}>Arithmetic means</th>
Expand Down Expand Up @@ -290,6 +309,14 @@ export class MatchesTableUi extends LitElement {
background: var(--mdc-theme-surface);
cursor: pointer;
}
.hiddenRow {
color: grey;
font-style: italic;
text-align: center;
}
.hiddenRow:hover {
cursor: pointer;
}
.missing {
background: var(--mdc-theme-surface);
Expand Down
1 change: 1 addition & 0 deletions src/metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export function createMetrics(batches: Batch[]): FieldMetric[] {
if (!isNumber) continue;
// Same source image, so same source image features. No need to compare.
if (field.id === FieldId.WIDTH || field.id === FieldId.HEIGHT) continue;
if (field.id === FieldId.FRAME_COUNT) continue;
// Encoder settings should not be compared.
if (field.id === FieldId.EFFORT || field.id === FieldId.QUALITY) continue;
// If bpp values are available, encoded sizes probably are too.
Expand Down
45 changes: 38 additions & 7 deletions src/settings_ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,39 +21,70 @@ import './filters_ui';
import {css, html, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';

import {dispatch, EventType} from './events';
import {dispatch, EventType, listen} from './events';
import {State} from './state';

/** Pop-up settings menu. */
@customElement('settings-ui')
export class SettingsUi extends LitElement {
@property({attribute: false}) state!: State;

override firstUpdated() {
listen(EventType.SETTINGS_CHANGED, () => {
this.requestUpdate();
});
}

override render() {
if (!this.state) return html``;
const slownessWarning = 'warning: may cause graphical interface slowness';

return html`
<div class="settingGroup">
<span>Hide data points</span>
<span title="Hide the matched pairs in the graph">
Hide data points
</span>
<mwc-switch ?selected=${this.state.showEachMatch}
@click=${() => {
this.state.showEachMatch = !this.state.showEachMatch;
dispatch(EventType.MATCHER_OR_METRIC_CHANGED);
this.requestUpdate();
dispatch(EventType.SETTINGS_CHANGED);
}}>
</mwc-switch>
<span>Show data points</span>
<span title="Show each matched pair as a small dot in the graph (${
slownessWarning})">
Show data points
</span>
</div>
<div class="settingGroup">
<span>Arithmetic mean</span>
<span title="Aggregate the metrics using the arithmetic mean of the values of the matched data points">
Arithmetic mean
</span>
<mwc-switch ?selected=${this.state.useGeometricMean}
@click=${() => {
this.state.useGeometricMean = !this.state.useGeometricMean;
dispatch(EventType.MATCHER_OR_METRIC_CHANGED);
this.requestUpdate();
dispatch(EventType.SETTINGS_CHANGED);
}}>
</mwc-switch>
<span title="Aggregate the metrics using the geometric mean of the ratios of the matched pairs">
Geometric mean
</span>
</div>
<div class="settingGroup">
<span title="Only display the first rows of the filtered or matched data point tables">
Show some rows
</span>
<mwc-switch ?selected=${this.state.showAllRows}
@click=${() => {
this.state.showAllRows = !this.state.showAllRows;
dispatch(EventType.SETTINGS_CHANGED);
}}>
</mwc-switch>
<span>Geometric mean</span>
<span title="Display all rows of the filtered or matched data point tables (${
slownessWarning})">
Show all rows
</span>
</div>`;
}

Expand Down
21 changes: 14 additions & 7 deletions src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ export class State {
*/
useGeometricMean = true;

/** If true, each row is shown in the tables. NOT stored in URL params. */
showAllRows = false;

/** Sets all other fields up based on the contents of the batches field. */
initialize() {
if (this.batches.length === 0) return;
Expand Down Expand Up @@ -113,13 +116,17 @@ export class State {
[this.plotMetricHorizontal, this.plotMetricVertical] =
selectPlotMetrics(this.batches[0], this.metrics);

// It would be better to count the matches rather than the data points but
// it is more important to have a default setting value right now, before
// reading the URL arguments, which happens before finding the matches.
const numDataPoints =
this.batches.reduce((n, batch) => n + batch.rows.length, 0);
if (numDataPoints > 50000) {
this.showEachMatch = false; // Avoids plot sluggishness by default.
if (this.batches.length > 2) {
this.showEachMatch = false; // Avoids visual confusion.
} else {
// It would be better to count the matches rather than the data points but
// it is more important to have a default setting value right now, before
// reading the URL arguments, which happens before finding the matches.
const numDataPoints =
this.batches.reduce((n, batch) => n + batch.rows.length, 0);
if (numDataPoints > 50000) {
this.showEachMatch = false; // Avoids plot sluggishness by default.
}
}

const computeMatchesAndStats = (batchSelection: BatchSelection) => {
Expand Down

0 comments on commit 3437550

Please sign in to comment.