Implement Hasselblad flat-field and levels
This commit is contained in:
@@ -690,6 +690,254 @@ void LibRaw::phase_one_load_raw_c()
|
||||
maximum = 0xfffc - ph1.t_black;
|
||||
}
|
||||
|
||||
// RT: From dcraw.cc.
|
||||
void LibRaw::hasselblad_correct()
|
||||
{
|
||||
unsigned col, row;
|
||||
|
||||
/*
|
||||
|
||||
This function applies 3FR calibration data. At the time of writing it supports a
|
||||
subset, so here's a todo list:
|
||||
|
||||
TODO:
|
||||
- Support all gain tag formats
|
||||
- The 0x19 tag varies a bit between models. We don't have parsers for all models.
|
||||
- The reference model used was during initial reverse-engineering was a H4D-50,
|
||||
we probably support the Hasselblads with Kodak 31, 39, 40 and 50 megapixels
|
||||
well, but more work is needed for Kodak 16 and 22, Dalsa 60 and Sony 50.
|
||||
- Apply bad column(?) data (hbd.unknown1)
|
||||
- It was left out in this initial release since the effect is very small.
|
||||
- Apply black(?) data, tag 0x1a and 0x1b is not parsed, it has however marginal
|
||||
effect (at least for shorter exposures) so it's not too important.
|
||||
|
||||
While there are things to do, the current implementation supports the most
|
||||
important aspects, the faltfield and levels calibrations applied can have strong
|
||||
visible effects.
|
||||
|
||||
*/
|
||||
|
||||
const auto &hbd = imHassy;
|
||||
|
||||
if (hbd.levels) {
|
||||
int i;
|
||||
fseek(ifp, hbd.levels, SEEK_SET);
|
||||
/* skip the first set (not used as we don't apply on first/last row), we look at it though to see if
|
||||
the levels format is one that we support (there are other formats on some models which is not
|
||||
supported here) */
|
||||
short test[10];
|
||||
for (i = 0; i < 10; i++) test[i] = (short)get2();
|
||||
if (test[5] == 0 && test[6] == 0 && test[7] == 0 && test[8] == 0 && test[9] == 0) {
|
||||
int corr[4];
|
||||
ushort *row_above = (ushort *)malloc(sizeof(ushort) * raw_width); // we need to cache row above as we write new values as we go
|
||||
for (col = 0; col < raw_width; col++) row_above[col] = RAW(0,col);
|
||||
for (row = 1; row < raw_height-1; row++) {
|
||||
for (i = 0; i < 4; i++) corr[i] = (short)get2();
|
||||
fseek(ifp, 6 * 2, SEEK_CUR);
|
||||
for (col = 0; col < raw_width; col++) {
|
||||
unsigned v = RAW(row,col);
|
||||
if (v >= 65534) {
|
||||
v = 65535;
|
||||
} else {
|
||||
if (corr[((col & 1)<<1)+0] && row_above[col] > black) v += 2 * ((corr[((col & 1)<<1)+0] * (row_above[col]-(int)black)) / 32767) - 2;
|
||||
if (corr[((col & 1)<<1)+1] && RAW(row+1,col) > black) v += 2 * ((corr[((col & 1)<<1)+1] * (RAW(row+1,col)-(int)black)) / 32767) - 2;
|
||||
}
|
||||
row_above[col] = RAW(row,col);
|
||||
RAW(row,col) = CLIP(v);
|
||||
}
|
||||
}
|
||||
free(row_above);
|
||||
}
|
||||
}
|
||||
|
||||
if (hbd.flatfield) {
|
||||
int bw, bh, ffrows, ffcols, i, c;
|
||||
ushort ref[4], ref_max;
|
||||
fseek(ifp, hbd.flatfield, SEEK_SET);
|
||||
get2();
|
||||
bw = get2();
|
||||
bh = get2();
|
||||
ffcols = get2();
|
||||
ffrows = get2();
|
||||
fseek(ifp, hbd.flatfield + 16 * 2, SEEK_SET);
|
||||
unsigned toRead = sizeof(ushort) * 4 * ffcols * ffrows;
|
||||
if (toRead > ifp->size()) { // there must be something wrong, see Issue #4748
|
||||
return;
|
||||
}
|
||||
|
||||
ushort *ffmap = (ushort *)malloc(toRead);
|
||||
for (i = 0; i < 4 * ffcols * ffrows; i++) ffmap[i] = get2();
|
||||
|
||||
/* Get reference values from center of field. This seems to be what Phocus does too,
|
||||
but haven't cared to figure out exactly at which coordinate */
|
||||
i = 4 * (ffcols * ffrows / 2 + ffcols / 2);
|
||||
ref[0] = ffmap[i+0];
|
||||
ref[1] = ffmap[i+1];
|
||||
ref[3] = ffmap[i+2]; // G2 = index 3 in dcraw, 2 in 3FR
|
||||
ref[2] = ffmap[i+3];
|
||||
ref_max = 0;
|
||||
FORC4 if (ref[c] > ref_max) ref_max = ref[c];
|
||||
if (ref_max == 0) ref[0] = ref[1] = ref[2] = ref[3] = ref_max = 10000;
|
||||
|
||||
/* Convert measured flatfield values to actual multipliers. The measured flatfield
|
||||
can have vignetting which should be normalized, only color cast should be corrected. */
|
||||
for (i = 0; i < 4 * ffcols * ffrows; i += 4) {
|
||||
double base, min = 65535.0, max = 0;
|
||||
double cur[4];
|
||||
cur[0] = (double)ffmap[i+0] / ref[0];
|
||||
cur[1] = (double)ffmap[i+1] / ref[1];
|
||||
cur[3] = (double)ffmap[i+2] / ref[3]; // G2 index differs in dcraw and 3FR
|
||||
cur[2] = (double)ffmap[i+3] / ref[2];
|
||||
FORC4 {
|
||||
if (cur[c] < min) min = cur[c];
|
||||
if (cur[c] > max) max = cur[c];
|
||||
}
|
||||
if (max == 0) max = 1.0;
|
||||
base = (cur[0]+cur[1]+cur[2]+cur[3])/(max*4);
|
||||
FORC4 cur[c] = cur[c] == 0 ? 1.0 : (base * max) / cur[c];
|
||||
/* convert to integer multiplier and store back to ffmap, we limit
|
||||
range to 4 (16384*4) which should be fine for flatfield */
|
||||
FORC4 {
|
||||
cur[c] *= 16384.0;
|
||||
if (cur[c] > 65535.0) cur[c] = 65535.0;
|
||||
ffmap[i+c] = (ushort)cur[c];
|
||||
}
|
||||
}
|
||||
|
||||
// of the cameras we've tested we know the exact placement of the flatfield map
|
||||
int row_offset, col_offset;
|
||||
switch (raw_width) {
|
||||
case 8282: // 50 megapixel Kodak
|
||||
row_offset = 21;
|
||||
col_offset = 71;
|
||||
break;
|
||||
default:
|
||||
/* Default case for camera models we've not tested. We center the map, which may
|
||||
not be exactly where it should be but close enough for the smooth flatfield */
|
||||
row_offset = (raw_height - bh * ffrows) / 2;
|
||||
col_offset = (raw_width - bw * ffcols) / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
Concerning smoothing between blocks in the map Phocus makes it only vertically,
|
||||
probably because it's simpler and faster. Looking at actual flatfield data it seems
|
||||
like it's better to smooth in both directions. Possibly flatfield could be used for
|
||||
correcting tiling on Dalsa sensors (H4D-60) like partly done in Phase One IIQ format,
|
||||
and then sharp edges may be beneficial at least at the tiling seams, but at the time
|
||||
of writing I've had no H4D-60 3FR files to test with to verify that.
|
||||
|
||||
Meanwhile we do both vertical and horizontal smoothing/blurring.
|
||||
*/
|
||||
|
||||
/* pre-calculate constants for blurring. We probably should make a more efficient
|
||||
blur than this, but this does not need any buffer and makes nice-looking
|
||||
radial gradients */
|
||||
ushort *corners_weight = (ushort *)malloc(bw*bh*9*sizeof(*corners_weight));
|
||||
const int corners_mix[9][4][2] = { { {0,0}, {0,1}, {1,0}, {1,1} },
|
||||
{ {0,1}, {1,1}, {-1,-1}, {-1,-1} },
|
||||
{ {0,1}, {0,2}, {1,1}, {1,2} },
|
||||
{ {1,0}, {1,1}, {-1,-1}, {-1,-1} },
|
||||
{ {1,1}, {-1,-1}, {-1,-1}, {-1,-1} },
|
||||
{ {1,1}, {1,2}, {-1,-1}, {-1,-1} },
|
||||
{ {1,0}, {1,1}, {2,0}, {2,1} },
|
||||
{ {1,1}, {2,1}, {-1,-1}, {-1,-1} },
|
||||
{ {1,1}, {1,2}, {2,1}, {2,2} } };
|
||||
const ushort corners_shift[9] = { 2, 1, 2, 1, 0, 1, 2, 1, 2 };
|
||||
for (row = 0; row < bh; row++) {
|
||||
const ushort maxdist = bw < bh ? bw/2-1 : bh/2-1;
|
||||
const unsigned bwu = (unsigned)bw;
|
||||
const unsigned bhu = (unsigned)bh;
|
||||
const unsigned corners[9][2] = {{0,0}, {0,bwu/2}, {0,bwu-1},
|
||||
{bhu/2,0},{bhu/2,bwu/2},{bhu/2,bwu-1},
|
||||
{bhu-1,0},{bhu-1,bwu/2},{bhu-1,bwu-1}};
|
||||
for (col = 0; col < bw; col++) {
|
||||
for (i = 0; i < 9; i++) {
|
||||
ushort dist = (ushort)sqrt(abs((int)(corners[i][0] - row)) * abs((int)(corners[i][0] - row)) + abs((int)(corners[i][1] - col)) * abs((int)(corners[i][1] - col)));
|
||||
ushort weight = dist > maxdist ? 0 : maxdist - dist;
|
||||
corners_weight[9*(row*bw+col)+i] = weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply flatfield
|
||||
#if defined(LIBRAW_USE_OPENMP)
|
||||
#pragma omp parallel for
|
||||
#endif
|
||||
for (int row = 0; row < raw_height; row++) {
|
||||
int ffs, cur_ffr, i, c;
|
||||
if (row < row_offset) {
|
||||
cur_ffr = row_offset;
|
||||
ffs = 0;
|
||||
} else if (row >= row_offset + ffrows * bh) {
|
||||
cur_ffr = row_offset + (ffrows-1) * bh;
|
||||
ffs = 4 * ffcols * (ffrows-1);
|
||||
} else {
|
||||
cur_ffr = row_offset + bh * ((row - row_offset) / bh);
|
||||
ffs = 4 * ffcols * ((row - row_offset) / bh);
|
||||
}
|
||||
int next_ffc = 0, cur_ffc = col_offset;
|
||||
int ffc = ffs;
|
||||
ushort *cur[3][3]; // points to local ffmap entries with center at cur[1][1]
|
||||
for (int col = 0; col < raw_width; col++) {
|
||||
if (col == next_ffc) {
|
||||
int rowsub = ffs == 0 ? 0 : ffcols*4;
|
||||
int rowadd = ffs == 4 * ffcols * (ffrows-1) ? 0 : ffcols * 4;
|
||||
int colsub = ffc == ffs ? 0 : 4;
|
||||
int coladd = ffc == ffs + 4 * (ffcols-1) ? 0 : 4;
|
||||
if (col != 0) cur_ffc = next_ffc;
|
||||
else next_ffc += col_offset;
|
||||
next_ffc += bw;
|
||||
|
||||
cur[0][0] = &ffmap[ffc-rowsub-colsub];
|
||||
cur[0][1] = &ffmap[ffc-rowsub];
|
||||
cur[0][2] = &ffmap[ffc-rowsub+coladd];
|
||||
|
||||
cur[1][0] = &ffmap[ffc-colsub];
|
||||
cur[1][1] = &ffmap[ffc];
|
||||
cur[1][2] = &ffmap[ffc+coladd];
|
||||
|
||||
cur[2][0] = &ffmap[ffc+rowadd-colsub];
|
||||
cur[2][1] = &ffmap[ffc+rowadd];
|
||||
cur[2][2] = &ffmap[ffc+rowadd+coladd];
|
||||
|
||||
ffc += 4;
|
||||
if (ffc == ffs + 4 * ffcols) next_ffc += raw_width; // last col in map, avoid stepping further
|
||||
}
|
||||
unsigned v = RAW(row,col);
|
||||
if (v > black && v < 65535) {
|
||||
c = FC(row - top_margin, col - left_margin);
|
||||
unsigned x = col < cur_ffc ? 0 : col - cur_ffc;
|
||||
unsigned y = row < cur_ffr ? 0 : row - cur_ffr;
|
||||
if (x >= bw) x = bw-1;
|
||||
if (y >= bh) y = bh-1;
|
||||
unsigned wsum = 0;
|
||||
unsigned mul = 0;
|
||||
for (i = 0; i < 9; i++) {
|
||||
ushort cw = corners_weight[9*(y*bw+x)+i];
|
||||
if (cw) {
|
||||
unsigned m = 0;
|
||||
int j;
|
||||
for (j = 0; j < 1 << corners_shift[i]; j++) {
|
||||
int cr = corners_mix[i][j][0], cc = corners_mix[i][j][1];
|
||||
m += cur[cr][cc][c];
|
||||
}
|
||||
m >>= corners_shift[i];
|
||||
mul += m * cw;
|
||||
wsum += cw;
|
||||
}
|
||||
}
|
||||
mul /= wsum;
|
||||
v = black + ((v-black) * mul) / 16384;
|
||||
RAW(row,col) = v > 65535 ? 65535 : v;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(ffmap);
|
||||
free(corners_weight);
|
||||
}
|
||||
}
|
||||
|
||||
void LibRaw::hasselblad_load_raw()
|
||||
{
|
||||
struct jhead jh;
|
||||
|
Reference in New Issue
Block a user