-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdownsampling.ts
171 lines (151 loc) · 7.22 KB
/
downsampling.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/*
* Copyright (c) 2016 - now, David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
*/
import * as Data from './data-model'
import * as DataFormat from '../common/data-format'
/**
* Downsamples each slice of input data and checks if there is enough data to perform
* higher rate downsampling.
*/
export function downsampleLayer(ctx: Data.Context) {
for (let i = 0, _ii = ctx.sampling.length - 1; i < _ii; i++) {
const s = ctx.sampling[i];
downsampleSlice(ctx, s);
if (canDownsampleBuffer(s, false)) {
downsampleBuffer(ctx.kernel, s, ctx.sampling[i + 1], ctx.blockSize);
} else {
break;
}
}
}
/**
* When the "native" (rate = 1) sampling is finished, there might still
* be some data left to be processed for the higher rate samplings.
*/
export function finalize(ctx: Data.Context) {
for (let i = 0, _ii = ctx.sampling.length - 1; i < _ii; i++) {
const s = ctx.sampling[i];
// skip downsampling the 1st slice because that is guaranteed to be done in "downsampleLayer"
if (i > 0) downsampleSlice(ctx, s);
// this is different from downsample layer in that it does not need 2 extra slices but just 1 is enough.
if (canDownsampleBuffer(s, true)) {
downsampleBuffer(ctx.kernel, s, ctx.sampling[i + 1], ctx.blockSize);
} else {
break;
}
}
}
/**
* The functions downsampleH and downsampleHK both essentially do the
* same thing: downsample along H (1st axis in axis order) and K (2nd axis in axis order) axes respectively.
*
* The reason there are two copies of almost the same code is performance:
* Both functions use a different memory layout to improve cache coherency
* - downsampleU uses the H axis as the fastest moving one
* - downsampleUV uses the K axis as the fastest moving one
*/
function conv(w: number, c: number[], src: DataFormat.ValueArray, b: number, i0: number, i1: number, i2: number, i3: number, i4: number) {
return w * (c[0] * src[b + i0] + c[1] * src[b + i1] + c[2] * src[b + i2] + c[3] * src[b + i3] + c[4] * src[b + i4]);
}
/**
* Map from L-th slice in src to an array of dimensions (srcDims[1], (srcDims[0] / 2), 1),
* flipping the 1st and 2nd axis in the process to optimize cache coherency for downsampleUV call
* (i.e. use (K, H, L) axis order).
*/
function downsampleH(kernel: Data.Kernel, srcDims: number[], src: DataFormat.ValueArray, srcLOffset: number, buffer: Data.DownsamplingBuffer) {
const target = buffer.downsampleH;
const sizeH = srcDims[0], sizeK = srcDims[1], srcBaseOffset = srcLOffset * sizeH * sizeK;
const targetH = Math.floor((sizeH + 1) / 2);
const isEven = sizeH % 2 === 0;
const w = 1.0 / kernel.coefficientSum;
const c = kernel.coefficients;
for (let k = 0; k < sizeK; k++) {
let srcOffset = srcBaseOffset + k * sizeH;
let targetOffset = k;
target[targetOffset] = conv(w, c, src, srcOffset, 0, 0, 0, 1, 2);
for (let h = 1; h < targetH - 1; h++) {
srcOffset += 2;
targetOffset += sizeK;
target[targetOffset] = conv(w, c, src, srcOffset, -2, -1, 0, 1, 2);
}
srcOffset += 2;
targetOffset += sizeK;
if (isEven) target[targetOffset] = conv(w, c, src, srcOffset, -2, -1, 0, 1, 1);
else target[targetOffset] = conv(w, c, src, srcOffset, -2, -1, 0, 0, 0);
}
}
/**
* Downsample first axis in the slice present in buffer.downsampleH
* The result is written into the "cyclical" downsampleHk buffer
* in the (L, H, K) axis order.
*/
function downsampleHK(kernel: Data.Kernel, dimsX: number[], buffer: Data.DownsamplingBuffer) {
const { downsampleH: src, downsampleHK: target, slicesWritten } = buffer;
const kernelSize = kernel.size;
const sizeH = dimsX[0], sizeK = dimsX[1];
const targetH = Math.floor((sizeH + 1) / 2);
const isEven = sizeH % 2 === 0;
const targetSliceSize = kernelSize * sizeK;
const targetBaseOffset = slicesWritten % kernelSize;
const w = 1.0 / kernel.coefficientSum;
const c = kernel.coefficients;
for (let k = 0; k < sizeK; k++) {
let sourceOffset = k * sizeH;
let targetOffset = targetBaseOffset + k * kernelSize;
target[targetOffset] = conv(w, c, src, sourceOffset, 0, 0, 0, 1, 2);
for (let h = 1; h < targetH - 1; h++) {
sourceOffset += 2; targetOffset += targetSliceSize;
target[targetOffset] = conv(w, c, src, sourceOffset, -2, -1, 0, 1, 2);
}
sourceOffset += 2; targetOffset += targetSliceSize;
if (isEven) target[targetOffset] = conv(w, c, src, sourceOffset, -2, -1, 0, 1, 1);
else target[targetOffset] = conv(w, c, src, sourceOffset, -2, -1, 0, 0, 0);
}
buffer.slicesWritten++;
}
/** Calls downsampleH and downsampleHk for each input channel separately. */
function downsampleSlice(ctx: Data.Context, sampling: Data.Sampling) {
const dimsU = [sampling.sampleCount[1], Math.floor((sampling.sampleCount[0] + 1) / 2)]
for (let i = 0, _ii = sampling.blocks.values.length; i < _ii; i++) {
downsampleH(ctx.kernel, sampling.sampleCount, sampling.blocks.values[i], sampling.blocks.slicesWritten - 1, sampling.downsampling![i]);
downsampleHK(ctx.kernel, dimsU, sampling.downsampling![i]);
}
}
/** Determine if a buffer has enough data to be downsampled */
function canDownsampleBuffer(source: Data.Sampling, finishing: boolean): boolean {
const buffer = source.downsampling![0];
const delta = buffer.slicesWritten - buffer.startSliceIndex;
return (finishing && delta > 0) || (delta > 2 && (delta - 3) % 2 === 0);
}
/** Downsample data in the buffer */
function downsampleBuffer(kernel: Data.Kernel, source: Data.Sampling, target: Data.Sampling, blockSize: number) {
const downsampling = source.downsampling!;
const { slicesWritten, startSliceIndex } = downsampling[0];
const sizeH = target.sampleCount[0], sizeK = target.sampleCount[1], sizeHK = sizeH * sizeK;
const kernelSize = kernel.size;
const w = 1.0 / kernel.coefficientSum;
const c = kernel.coefficients;
// Indices to the 1st dimeninsion in the cyclic buffer.
const i0 = Math.max(0, startSliceIndex - 2) % kernelSize;
const i1 = Math.max(0, startSliceIndex - 1) % kernelSize;
const i2 = startSliceIndex % kernelSize;
const i3 = Math.min(slicesWritten, startSliceIndex + 1) % kernelSize;
const i4 = Math.min(slicesWritten, startSliceIndex + 2) % kernelSize;
const channelCount = downsampling.length;
const valuesBaseOffset = target.blocks.slicesWritten * sizeHK;
for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
const src = downsampling[channelIndex].downsampleHK;
const values = target.blocks.values[channelIndex];
for (let k = 0; k < sizeK; k++) {
const valuesOffset = valuesBaseOffset + k * sizeH;
for (let h = 0; h < sizeH; h++) {
const sO = kernelSize * h + kernelSize * k * sizeH;
const s = conv(w, c, src, sO, i0, i1, i2, i3, i4);
values[valuesOffset + h] = s;
}
}
// we have "consume" two layers of the buffer.
downsampling[channelIndex].startSliceIndex += 2;
}
target.blocks.slicesWritten++;
}