/ concept-collection / ephys_compression_tests
concept-collection / ephys_compression_tests
dev lossy
Jeremy Magland <jmagland@flatironinstitute.org> committed commit 5be77d17e3bd parent d5a0b99 Browse files
11 changed files+154−79
python/ephys_compression_tests/algorithms/ans/__init__.pymodified+30−29View file
@@ -256,35 +256,36 @@ for a in algorithm_dicts_base:
256256 })
257257
258258 # Add lossy ar2
259-def encode0_ar2_lossy(x: np.ndarray) -> bytes:
260- coeffs, residuals, initial_values = encode_ar_lossy(x, order=2, step=2 * 2 + 1)
261- encoded_residuals = ans_encode_0(residuals)
262- coeffs_bytes = coeffs.astype(np.float32).tobytes()
263- initial_values_bytes = initial_values.astype(np.int16).tobytes()
264- return coeffs_bytes + initial_values_bytes + encoded_residuals
265-def decode0_ar2_lossy(x: bytes, dtype: str, shape: tuple) -> np.ndarray:
266- dtype_np = np.dtype(dtype)
267- num_bytes_coeffs = 2 * np.dtype(np.float32).itemsize
268- coeffs_bytes = x[:num_bytes_coeffs]
269- coeffs = np.frombuffer(coeffs_bytes, dtype=np.float32)
270- num_initial_values = len(coeffs)
271- num_bytes_initial_values = num_initial_values * dtype_np.itemsize
272- initial_values_bytes = x[num_bytes_coeffs : num_bytes_coeffs + num_bytes_initial_values]
273- initial_values = np.frombuffer(initial_values_bytes, dtype=dtype_np)
274- encoded_residuals = x[num_bytes_coeffs + num_bytes_initial_values :]
275- residuals = ans_decode_0(encoded_residuals, dtype, (shape[0]-num_initial_values,))
276- reconstructed = decode_ar(coeffs, residuals, initial_values)
277- return reconstructed.reshape(shape)
278-algorithm_dicts.append({
279- "name": "ans-ar2-lossy-2",
280- "version": "1",
281- "encode": encode0_ar2_lossy,
282- "decode": decode0_ar2_lossy,
283- "description": "ANS with lossy auto-regressive prediction encoding of order 2",
284- "tags": ["ans", "lossy", "ar2"],
285- "source_file": SOURCE_FILE,
286- "long_description": LONG_DESCRIPTION
287-})
259+for tolerance in [1, 2, 3]:
260+ def encode0_ar2_lossy(x: np.ndarray, tolerance=tolerance) -> bytes:
261+ coeffs, residuals, initial_values = encode_ar_lossy(x, order=2, step=tolerance * 2 + 1)
262+ encoded_residuals = ans_encode_0(residuals)
263+ coeffs_bytes = coeffs.astype(np.float32).tobytes()
264+ initial_values_bytes = initial_values.astype(np.int16).tobytes()
265+ return coeffs_bytes + initial_values_bytes + encoded_residuals
266+ def decode0_ar2_lossy(x: bytes, dtype: str, shape: tuple) -> np.ndarray:
267+ dtype_np = np.dtype(dtype)
268+ num_bytes_coeffs = 2 * np.dtype(np.float32).itemsize
269+ coeffs_bytes = x[:num_bytes_coeffs]
270+ coeffs = np.frombuffer(coeffs_bytes, dtype=np.float32)
271+ num_initial_values = len(coeffs)
272+ num_bytes_initial_values = num_initial_values * dtype_np.itemsize
273+ initial_values_bytes = x[num_bytes_coeffs : num_bytes_coeffs + num_bytes_initial_values]
274+ initial_values = np.frombuffer(initial_values_bytes, dtype=dtype_np)
275+ encoded_residuals = x[num_bytes_coeffs + num_bytes_initial_values :]
276+ residuals = ans_decode_0(encoded_residuals, dtype, (shape[0]-num_initial_values,))
277+ reconstructed = decode_ar(coeffs, residuals, initial_values)
278+ return reconstructed.reshape(shape)
279+ algorithm_dicts.append({
280+ "name": f"ans-ar2-lossy-{tolerance}",
281+ "version": "1",
282+ "encode": encode0_ar2_lossy,
283+ "decode": decode0_ar2_lossy,
284+ "description": f"ANS with lossy auto-regressive prediction encoding of order 2 and tolerance {tolerance}",
285+ "tags": ["ans", "lossy", "ar2"],
286+ "source_file": SOURCE_FILE,
287+ "long_description": LONG_DESCRIPTION
288+ })
288289
289290 algorithms = [
290291 Algorithm(**a)
python/ephys_compression_tests/algorithms/wavpack/__init__.pymodified+12−21View file
@@ -80,27 +80,18 @@ for a in algorithm_dicts_base:
8080 "long_description": a["long_description"]
8181 })
8282
83-# Add lossy version
84-algorithm_dicts.append({
85- "name": "wavpack-lossy-3",
86- "version": "1",
87- "encode": lambda x: wavpack_encode(x, bps=3),
88- "decode": lambda x, dtype, shape: wavpack_decode(x, dtype, shape),
89- "description": "WavPack lossy with 3 bits per sample",
90- "tags": ["wavpack", "lossy"],
91- "source_file": SOURCE_FILE,
92- "long_description": LONG_DESCRIPTION,
93-})
94-algorithm_dicts.append({
95- "name": "wavpack-lossy-4",
96- "version": "1",
97- "encode": lambda x: wavpack_encode(x, bps=4),
98- "decode": lambda x, dtype, shape: wavpack_decode(x, dtype, shape),
99- "description": "WavPack lossy with 4 bits per sample",
100- "tags": ["wavpack", "lossy"],
101- "source_file": SOURCE_FILE,
102- "long_description": LONG_DESCRIPTION,
103-})
83+# Add lossy versions
84+for bps in [3, 4, 5, 6]:
85+ algorithm_dicts.append({
86+ "name": f"wavpack-lossy-{bps}",
87+ "version": "1",
88+ "encode": lambda x: wavpack_encode(x, bps=bps),
89+ "decode": lambda x, dtype, shape: wavpack_decode(x, dtype, shape),
90+ "description": f"WavPack lossy with {bps} bits per sample",
91+ "tags": ["wavpack", "lossy"],
92+ "source_file": SOURCE_FILE,
93+ "long_description": LONG_DESCRIPTION,
94+ })
10495
10596 algorithms = [
10697 Algorithm(**a)
web-ui/package.jsonmodified+1−1View file
@@ -4,7 +4,7 @@
44 "version": "0.0.0",
55 "type": "module",
66 "scripts": {
7- "dev": "../devel/generate_posts_index.sh && vite",
7+ "dev": "vite",
88 "build": "tsc -b && vite build",
99 "lint": "eslint .",
1010 "preview": "vite preview",
web-ui/src/components/algorithm/AlgorithmContent.tsxmodified+2−0View file
@@ -11,6 +11,8 @@ interface AlgorithmContentProps {
1111 reference_compression_ratio: number | null;
1212 encode_speed: number;
1313 decode_speed: number;
14+ rmse?: number;
15+ tags: string[];
1416 }>;
1517 }
1618
web-ui/src/components/benchmark/charts/BenchmarkCharts.tsxmodified+52−6View file
@@ -6,7 +6,7 @@ interface BenchmarkBarChartProps {
66 data: ChartData[];
77 dataKey: keyof Pick<
88 ChartData,
9- "compression_ratio" | "encode_speed" | "decode_speed"
9+ "compression_ratio" | "encode_speed" | "decode_speed" | "rmse"
1010 >;
1111 color: string;
1212 xAxisTitle: string;
@@ -41,7 +41,10 @@ function BenchmarkBarChart({
4141 type: "bar",
4242 orientation: "h",
4343 y: normalizedData.map((d) => d.algorithmOrDataset),
44- x: normalizedData.map((d) => d[dataKey]),
44+ x: normalizedData.map((d) => {
45+ const value = d[dataKey];
46+ return value !== undefined ? value : 0;
47+ }),
4548 marker: { color },
4649 name: title,
4750 hovertemplate:
@@ -87,7 +90,17 @@ function BenchmarkBarChart({
8790 height: Math.max(300, data.length * 23 + 40),
8891 margin: { t: 5, r: 30, l: 200, b: 30 },
8992 xaxis: { title: xAxisTitle },
90- yaxis: { automargin: true, ticksuffix: " " },
93+ yaxis: {
94+ automargin: true,
95+ ticksuffix: " ",
96+ tickmode: "array",
97+ tickvals: normalizedData.map((d) => d.algorithmOrDataset),
98+ ticktext: normalizedData.map((d) =>
99+ d.tags.includes("lossy")
100+ ? `<span style="color: red;">${d.algorithmOrDataset}*</span>`
101+ : d.algorithmOrDataset
102+ ),
103+ },
91104 dragmode: false,
92105 }}
93106 config={{ displayModeBar: false }}
@@ -102,6 +115,8 @@ interface ChartData {
102115 reference_compression_ratio: number | null; // the highest compression ratio for the dataset (if algorithmOrDataset is a dataset)
103116 encode_speed: number;
104117 decode_speed: number;
118+ rmse?: number;
119+ tags: string[];
105120 }
106121
107122 interface BenchmarkChartsProps {
@@ -119,17 +134,31 @@ export function BenchmarkCharts({
119134 showSortByCompressionRatio ? true : false,
120135 );
121136 const [normalize, setNormalize] = useState(false);
137+ const [showLossyAlgs, setShowLossyAlgs] = useState(true);
122138
123139 if (!chartData.length) return null;
124140
141+ // Filter data based on showLossyAlgs
142+ // If showLossyAlgs is true, show all algorithms (both lossy and lossless)
143+ // If showLossyAlgs is false, only show lossless algorithms
144+ const filteredData = showLossyAlgs
145+ ? chartData
146+ : chartData.filter((d) => !d.tags.includes("lossy"));
147+
125148 const sortedData = sortByRatio
126- ? [...chartData].sort((a, b) => a.compression_ratio - b.compression_ratio)
127- : chartData;
149+ ? [...filteredData].sort((a, b) => a.compression_ratio - b.compression_ratio)
150+ : filteredData;
151+
152+ // For RMSE chart, only show lossy algorithms with rmse values
153+ const lossyData = chartData.filter((d) => d.tags.includes("lossy") && d.rmse !== undefined);
154+ const sortedLossyData = sortByRatio
155+ ? [...lossyData].sort((a, b) => a.compression_ratio - b.compression_ratio)
156+ : lossyData;
128157
129158 return (
130159 <div>
131160 {showSortByCompressionRatio && (
132- <div style={{ marginBottom: "10px" }}>
161+ <div style={{ marginBottom: "10px", display: "flex", gap: "16px" }}>
133162 <label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
134163 <input
135164 type="checkbox"
@@ -138,6 +167,14 @@ export function BenchmarkCharts({
138167 />
139168 Sort by compression ratio
140169 </label>
170+ <label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
171+ <input
172+ type="checkbox"
173+ checked={showLossyAlgs}
174+ onChange={(e) => setShowLossyAlgs(e.target.checked)}
175+ />
176+ Show lossy algs
177+ </label>
141178 </div>
142179 )}
143180 {showNormalizeByReference && (
@@ -181,6 +218,15 @@ export function BenchmarkCharts({
181218 color="#ff7300"
182219 xAxisTitle="MB/s"
183220 />
221+ {sortedLossyData.length > 0 && (
222+ <BenchmarkBarChart
223+ title="RMSE (Lossy Algs)"
224+ data={sortedLossyData}
225+ dataKey="rmse"
226+ color="#d62728"
227+ xAxisTitle="RMSE"
228+ />
229+ )}
184230 </div>
185231 </div>
186232 );
web-ui/src/components/benchmark/charts/BenchmarkScatterPlots.tsxmodified+33−13View file
@@ -6,6 +6,7 @@ interface ChartData {
66 compression_ratio: number;
77 encode_speed: number;
88 decode_speed: number;
9+ tags: string[];
910 }
1011
1112 interface BenchmarkScatterPlotsProps {
@@ -16,11 +17,17 @@ export function BenchmarkScatterPlots({
1617 chartData,
1718 }: BenchmarkScatterPlotsProps) {
1819 const [showLabels, setShowLabels] = useState(false);
20+ const [showLossyAlgs, setShowLossyAlgs] = useState(true);
1921
2022 if (!chartData.length) return null;
2123
24+ // Filter data based on showLossyAlgs
25+ const filteredData = showLossyAlgs
26+ ? chartData
27+ : chartData.filter((d) => !d.tags.includes("lossy"));
28+
2229 const uniqueAlgorithms = Array.from(
23- new Set(chartData.map((d) => d.algorithmOrDataset)),
30+ new Set(filteredData.map((d) => d.algorithmOrDataset)),
2431 );
2532
2633 const colors = [
@@ -38,17 +45,20 @@ export function BenchmarkScatterPlots({
3845
3946 // Create traces for each algorithm
4047 const traces = uniqueAlgorithms.flatMap((algo, i) => {
41- const algoData = chartData.filter((d) => d.algorithmOrDataset === algo);
48+ const algoData = filteredData.filter((d) => d.algorithmOrDataset === algo);
49+ const isLossy = algoData.length > 0 && algoData[0].tags.includes("lossy");
50+ const displayName = isLossy ? `${algo}*` : algo;
4251 const baseTrace = {
43- name: algo,
52+ name: displayName,
4453 mode: showLabels ? ("markers+text" as const) : ("markers" as const),
4554 marker: {
46- color: colors[i % colors.length],
55+ color: isLossy ? "red" : colors[i % colors.length],
4756 symbol: markers[Math.floor(i / colors.length) % markers.length],
4857 size: 10,
4958 },
50- text: showLabels ? algoData.map(() => algo) : [],
59+ text: showLabels ? algoData.map(() => displayName) : [],
5160 textposition: "top center" as const,
61+ textfont: isLossy ? { color: "red" } : undefined,
5262 showlegend: true,
5363 legendgroup: algo,
5464 };
@@ -88,14 +98,24 @@ export function BenchmarkScatterPlots({
8898 <div style={{ margin: "20px 0" }}>
8999 <div style={{ marginBottom: "10px" }}>
90100 <h2 style={{ marginBottom: "10px" }}>Performance Relationships</h2>
91- <label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
92- <input
93- type="checkbox"
94- checked={showLabels}
95- onChange={(e) => setShowLabels(e.target.checked)}
96- />
97- Show point labels
98- </label>
101+ <div style={{ display: "flex", gap: "16px" }}>
102+ <label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
103+ <input
104+ type="checkbox"
105+ checked={showLabels}
106+ onChange={(e) => setShowLabels(e.target.checked)}
107+ />
108+ Show point labels
109+ </label>
110+ <label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
111+ <input
112+ type="checkbox"
113+ checked={showLossyAlgs}
114+ onChange={(e) => setShowLossyAlgs(e.target.checked)}
115+ />
116+ Show lossy algs
117+ </label>
118+ </div>
99119 </div>
100120 <Plot
101121 data={traces}
web-ui/src/components/dataset/DatasetContent.tsxmodified+2−0View file
@@ -13,6 +13,8 @@ interface DatasetContentProps {
1313 reference_compression_ratio: number | null;
1414 encode_speed: number;
1515 decode_speed: number;
16+ rmse?: number;
17+ tags: string[];
1618 }>;
1719 }
1820
web-ui/src/components/shared/BaseContent.tsxmodified+2−0View file
@@ -27,6 +27,8 @@ interface BaseContentProps {
2727 reference_compression_ratio: number | null;
2828 encode_speed: number;
2929 decode_speed: number;
30+ rmse?: number;
31+ tags: string[];
3032 }>;
3133 tagNavigationPrefix: string;
3234 filterKey: "dataset" | "algorithm";
web-ui/src/hooks/useBenchmarkChartData.tsmodified+17−9View file
@@ -1,8 +1,9 @@
11 import { useMemo } from "react";
2-import { BenchmarkResult } from "../types";
2+import { Algorithm, BenchmarkResult } from "../types";
33
44 export function useBenchmarkChartData(
55 results: BenchmarkResult[],
6+ algorithms: Algorithm[],
67 selectedDataset?: string | null,
78 selectedAlgorithm?: string | null,
89 ) {
@@ -10,13 +11,18 @@ export function useBenchmarkChartData(
1011 if (selectedDataset) {
1112 return results
1213 .filter((row) => row.dataset === selectedDataset)
13- .map((row) => ({
14- algorithmOrDataset: row.algorithm,
15- compression_ratio: row.compression_ratio,
16- reference_compression_ratio: null,
17- encode_speed: row.encode_mb_per_sec,
18- decode_speed: row.decode_mb_per_sec,
19- }));
14+ .map((row) => {
15+ const algorithm = algorithms.find((a) => a.name === row.algorithm);
16+ return {
17+ algorithmOrDataset: row.algorithm,
18+ compression_ratio: row.compression_ratio,
19+ reference_compression_ratio: null,
20+ encode_speed: row.encode_mb_per_sec,
21+ decode_speed: row.decode_mb_per_sec,
22+ rmse: row.rmse,
23+ tags: algorithm?.tags || [],
24+ };
25+ });
2026 } else if (selectedAlgorithm) {
2127 return results
2228 .filter((row) => row.algorithm === selectedAlgorithm)
@@ -30,8 +36,10 @@ export function useBenchmarkChartData(
3036 ),
3137 encode_speed: row.encode_mb_per_sec,
3238 decode_speed: row.decode_mb_per_sec,
39+ rmse: row.rmse,
40+ tags: [],
3341 }));
3442 }
3543 return [];
36- }, [results, selectedDataset, selectedAlgorithm]);
44+ }, [results, algorithms, selectedDataset, selectedAlgorithm]);
3745 }
web-ui/src/pages/BenchmarkView.tsxmodified+1−0View file
@@ -75,6 +75,7 @@ export default function BenchmarkView({ benchmarkData }: BenchmarkViewProps) {
7575 // Get chart data for specific dataset or algorithm view
7676 const chartData = useBenchmarkChartData(
7777 benchmarkData?.results || [],
78+ benchmarkData?.algorithms || [],
7879 dataset?.name || null,
7980 algorithm?.name || null,
8081 );
web-ui/src/types.tsmodified+2−0View file
@@ -14,6 +14,8 @@ export interface BenchmarkResult {
1414 array_shape: number[];
1515 array_dtype: string;
1616 timestamp: number;
17+ rmse?: number;
18+ max_error?: number;
1719 }
1820
1921 export interface Algorithm {