Skip to content

Commit

Permalink
perf(core): load tile sizes from tag if it is already loaded
Browse files Browse the repository at this point in the history
  • Loading branch information
blacha committed Oct 16, 2024
1 parent 35a0112 commit ed6c5e3
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 18 deletions.
4 changes: 3 additions & 1 deletion packages/cli/src/log.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { fsa, FsHttp } from '@chunkd/fs';
import { SourceCache, SourceChunk } from '@chunkd/middleware';
import { Tiff } from '@cogeotiff/core';
import { log } from '@linzjs/tracing';

import { FetchLog } from './fs.js';

// Cache the last 10MB of chunks for reuse
export const sourceCache = new SourceCache({ size: 10 * 1024 * 1024 });
export const sourceChunk = new SourceChunk({ size: 32 * 1024 });
export const sourceChunk = new SourceChunk({ size: 64 * 1024 });
Tiff.DefaultReadSize = sourceChunk.chunkSize;

export function setupLogger(cfg: { verbose?: boolean; extraVerbose?: boolean }): typeof log {
if (cfg.verbose) {
Expand Down
39 changes: 30 additions & 9 deletions packages/core/src/read/tiff.tag.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ export function createTag(tiff: Tiff, view: DataViewOffset, offset: number): Tag

const dataOffset = getUint(view, offset + 4 + tiff.ifdConfig.pointer, tiff.ifdConfig.pointer, tiff.isLittleEndian);

// If we already have the bytes in the view read them in
if (hasBytes(view, dataOffset, dataLength)) {
if (dataCount <= tiff.defaultTagInitCount) {
const value = readValue(tiff, tagId, view, dataOffset - view.sourceOffset, dataType, dataCount);
return { type: 'inline', id: tagId, name: TiffTag[tagId], count: dataCount, value, dataType, tagOffset: offset };
}
}

switch (tagId) {
case TiffTag.TileOffsets:
case TiffTag.TileByteCounts:
Expand All @@ -131,17 +139,13 @@ export function createTag(tiff: Tiff, view: DataViewOffset, offset: number): Tag
value: [],
tagOffset: offset,
};
// Some offsets are quite long and don't need to read them often, so only read the tags we are interested in when we need to
if (tagId === TiffTag.TileOffsets && hasBytes(view, dataOffset, dataLength)) setBytes(tag, view);
// Some offsets are massive and we don't need to read them often, so only read the tags we are interested in when we need to
if (tag.id === TiffTag.TileOffsets || tag.id === TiffTag.TileByteCounts) {
if (hasBytes(view, dataOffset, dataLength)) setBytes(tag, view);
}
return tag;
}

// If we already have the bytes in the view read them in
if (hasBytes(view, dataOffset, dataLength)) {
const value = readValue(tiff, tagId, view, dataOffset - view.sourceOffset, dataType, dataCount);
return { type: 'inline', id: tagId, name: TiffTag[tagId], count: dataCount, value, dataType, tagOffset: offset };
}

return { type: 'lazy', id: tagId, name: TiffTag[tagId], count: dataCount, dataOffset, dataType, tagOffset: offset };
}

Expand Down Expand Up @@ -181,7 +185,7 @@ export function setBytes(tag: TagOffset, view: DataViewOffset): void {
}

/** Partially fetch the values of a {@link TagOffset} and return the value for the offset */
export async function getValueAt(tiff: Tiff, tag: TagOffset, index: number): Promise<number> {
export async function getOffsetValueAt(tiff: Tiff, tag: TagOffset, index: number): Promise<number> {
if (index > tag.count || index < 0) throw new Error('TagOffset: out of bounds :' + index);
if (tag.value[index] != null) return tag.value[index];
const dataTypeSize = getTiffTagSize(tag.dataType);
Expand All @@ -202,3 +206,20 @@ export async function getValueAt(tiff: Tiff, tag: TagOffset, index: number): Pro
tag.value[index] = value;
return value;
}

/** Partially read the value of a {@link TagOffset} and return the value for the offset */
export function getOffsetValueAtSync(tiff: Tiff, tag: TagOffset, index: number): number | null {
if (index > tag.count || index < 0) throw new Error('TagOffset: out of bounds :' + index);
// Value already loaded
if (tag.value[index] != null) return tag.value[index];

// data has not been loaded so cannot get value
if (tag.view == null) return null;

const dataTypeSize = getTiffTagSize(tag.dataType);

const value = readValue(tiff, undefined, tag.view, index * dataTypeSize, tag.dataType, 1);
if (typeof value !== 'number') throw new Error('Value is not a number');
tag.value[index] = value;
return value;
}
35 changes: 27 additions & 8 deletions packages/core/src/tiff.image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
TiffTagGeoType,
TiffTagType,
} from './const/tiff.tag.id.js';
import { fetchAllOffsets, fetchLazy, getValueAt } from './read/tiff.tag.factory.js';
import { fetchAllOffsets, fetchLazy, getOffsetValueAt, getOffsetValueAtSync } from './read/tiff.tag.factory.js';
import { Tag, TagInline, TagOffset } from './read/tiff.tag.js';
import { Tiff } from './tiff.js';
import { getUint } from './util/bytes.js';
Expand Down Expand Up @@ -570,25 +570,38 @@ export class TiffImage {
* @returns Offset and byteCount for the tile
*/
async getTileSize(index: number): Promise<{ offset: number; imageSize: number }> {
const byteCounts = this.tags.get(TiffTag.TileByteCounts) as TagOffset | TagInline<number[]>;
if (byteCounts == null) throw new Error('No tile byte counts found, is the tiff tiled?');

const tileOffsets = this.tileOffset;

// If the tag has the data already loaded just read the values
const imageSizeSync = getOffsetSync(this.tiff, byteCounts, index);
const syncOffset = getOffsetSync(this.tiff, tileOffsets, index);
if (imageSizeSync != null && syncOffset != null) return { offset: syncOffset, imageSize: imageSizeSync };

// GDAL optimizes tiles by storing the size of the tile in
// the few bytes leading up to the tile
const leaderBytes = this.tiff.options?.tileLeaderByteSize;
if (leaderBytes) {
const offset = await getOffset(this.tiff, this.tileOffset, index);
const offset = imageSizeSync ?? (await getOffset(this.tiff, tileOffsets, index));
// Sparse tiff no data found
if (offset === 0) return { offset: 0, imageSize: 0 };

// If the byteSizes array already been loaded
if (imageSizeSync !== null) return { offset, imageSize: imageSizeSync };

// This fetch will generally load in the bytes needed for the image too
// provided the image size is less than the size of a chunk
const bytes = await this.tiff.source.fetch(offset - leaderBytes, leaderBytes);
return { offset, imageSize: getUint(new DataView(bytes), 0, leaderBytes, this.tiff.isLittleEndian) };
const imageSize = getUint(new DataView(bytes), 0, leaderBytes, this.tiff.isLittleEndian);
byteCounts.value[index] = imageSize;
return { offset, imageSize };
}

const byteCounts = this.tags.get(TiffTag.TileByteCounts) as TagOffset;
if (byteCounts == null) throw new Error('No tile byte counts found');
const [offset, imageSize] = await Promise.all([
getOffset(this.tiff, this.tileOffset, index),
getOffset(this.tiff, byteCounts, index),
syncOffset ?? getOffset(this.tiff, tileOffsets, index),
imageSizeSync ?? getOffset(this.tiff, byteCounts, index),
]);
return { offset, imageSize };
}
Expand All @@ -601,5 +614,11 @@ function getOffset(tiff: Tiff, x: TagOffset | TagInline<number[]>, index: number
// Sparse tiffs may not have the full tileWidth * tileHeight in their offset arrays
if (index >= x.count) return 0;
if (x.type === 'inline') return x.value[index];
return getValueAt(tiff, x, index);
return getOffsetValueAt(tiff, x, index);
}

function getOffsetSync(tiff: Tiff, x: TagOffset | TagInline<number[]>, index: number): number | null {
if (x.type === 'offset') return getOffsetValueAtSync(tiff, x, index);
if (index > x.count) return 0;
return x.value[index];
}
6 changes: 6 additions & 0 deletions packages/core/src/tiff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ export class Tiff {
static DefaultReadSize = 16 * 1024;
/** Read 16KB blocks at a time */
defaultReadSize = Tiff.DefaultReadSize;

/** Do not initialize tags that have greater than this many records see {@link createTag} */
static DefaultTagInitCount = 1024;
/** Do not initialize tags that are greater than this size see {@link createTag} */
defaultTagInitCount = Tiff.DefaultTagInitCount;

/** Where this cog is fetching its data from */
source: Source;
/** Big or small Tiff */
Expand Down

0 comments on commit ed6c5e3

Please sign in to comment.