Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up and improve how mismatching resolutions are detected #4936

Merged
merged 4 commits into from
Nov 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 36 additions & 21 deletions frontend/javascripts/oxalis/model/accessors/dataset_accessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,18 +179,39 @@ function _getResolutionInfo(resolutions: Array<Vector3>): ResolutionInfo {
// (which are not that many).
export const getResolutionInfo = _.memoize(_getResolutionInfo);

export function getMostExtensiveResolutions(dataset: APIDataset): Array<Vector3> {
return _.chain(dataset.dataSource.dataLayers)
.map(dataLayer => dataLayer.resolutions)
.sortBy(resolutions => resolutions.length)
.last()
export function getResolutionUnion(
dataset: APIDataset,
shouldThrow: boolean = false,
): Array<Vector3> {
const resolutionUnionDict = {};

for (const layer of dataset.dataSource.dataLayers) {
for (const resolution of layer.resolutions) {
const key = _.max(resolution);

if (resolutionUnionDict[key] == null) {
resolutionUnionDict[key] = resolution;
} else if (_.isEqual(resolutionUnionDict[key], resolution)) {
// the same resolution was already picked up
} else if (shouldThrow) {
throw new Error(
`The resolutions of the different layers don't match. ${resolutionUnionDict[key].join(
"-",
)} != ${resolution.join("-")}.`,
);
} else {
// The resolutions don't match, but shouldThrow is false
}
}
}

return _.chain(resolutionUnionDict)
.values()
.sortBy(_.max)
.valueOf();
}

export function convertToDenseResolution(
resolutions: Array<Vector3>,
fallbackDenseResolutions?: Array<Vector3>,
): Array<Vector3> {
export function convertToDenseResolution(resolutions: Array<Vector3>): Array<Vector3> {
// Each resolution entry can be characterized by it's greatest resolution dimension.
// E.g., the resolution array [[1, 1, 1], [2, 2, 1], [4, 4, 2]] defines that
// a log zoomstep of 2 corresponds to the resolution [2, 2, 1] (and not [4, 4, 2]).
Expand All @@ -204,31 +225,25 @@ export function convertToDenseResolution(
}
const paddedResolutionCount = 1 + Math.log2(_.max(resolutions.map(v => _.max(v))));
const resolutionsLookUp = _.keyBy(resolutions, _.max);
const fallbackResolutionsLookUp = _.keyBy(fallbackDenseResolutions || [], _.max);

return _.range(0, paddedResolutionCount).map(exp => {
const resPower = 2 ** exp;
// If the resolution does not exist, use either the given fallback resolution or an isotropic fallback
const fallback = fallbackResolutionsLookUp[resPower] || [resPower, resPower, resPower];
const fallback = [resPower, resPower, resPower];
return resolutionsLookUp[resPower] || fallback;
});
}

function _getResolutions(dataset: APIDataset): Vector3[] {
// Different layers can have different resolutions. At the moment,
// unequal resolutions will result in undefined behavior.
// However, if resolutions are subset of each other, everything should be fine.
// For that case, returning the longest resolutions array should suffice
// mismatching resolutions will result in undefined behavior (rather than
// causing a hard error). During the model initialization, an error message
// will be shown, though.

// In the long term, getResolutions should not be used anymore.
// Instead, all the code should use the ResolutionInfo class which represents
// exactly which resolutions exist.
const mostExtensiveResolutions = getMostExtensiveResolutions(dataset);
if (!mostExtensiveResolutions) {
return [];
}

return convertToDenseResolution(mostExtensiveResolutions);
// exactly which resolutions exist per layer.
return convertToDenseResolution(getResolutionUnion(dataset));
}

// _getResolutions itself is not very performance intensive, but other functions which rely
Expand Down
17 changes: 6 additions & 11 deletions frontend/javascripts/oxalis/model_initialization.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ import {
getBoundaries,
getColorLayers,
getDatasetCenter,
getMostExtensiveResolutions,
getResolutionUnion,
getSegmentationLayer,
isElementClassSupported,
convertToDenseResolution,
} from "oxalis/model/accessors/dataset_accessor";
import { getSomeServerTracing } from "oxalis/model/accessors/tracing_accessor";
import {
Expand Down Expand Up @@ -337,15 +336,11 @@ function initializeDataset(
}

export function ensureMatchingLayerResolutions(dataset: APIDataset): void {
const mostExtensiveResolutions = convertToDenseResolution(getMostExtensiveResolutions(dataset));

for (const layer of dataset.dataSource.dataLayers) {
const denseResolutions = convertToDenseResolution(layer.resolutions, mostExtensiveResolutions);
for (const resolution of denseResolutions) {
if (mostExtensiveResolutions.find(element => _.isEqual(resolution, element)) == null) {
Toast.error(messages["dataset.resolution_mismatch"], { sticky: true });
}
}
try {
getResolutionUnion(dataset, true);
} catch (exception) {
console.warn(exception);
Toast.error(messages["dataset.resolution_mismatch"], { sticky: true });
}
}

Expand Down
73 changes: 59 additions & 14 deletions frontend/javascripts/test/model/model_resolutions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import test from "ava";
import { ensureMatchingLayerResolutions } from "oxalis/model_initialization";
import {
convertToDenseResolution,
getMostExtensiveResolutions,
getResolutionUnion,
} from "oxalis/model/accessors/dataset_accessor";

test("Simple convertToDenseResolution", t => {
Expand All @@ -26,17 +26,62 @@ test("Complex convertToDenseResolution", t => {
},
};
ensureMatchingLayerResolutions(dataset);
const expectedResolutions = [
[1, 1, 1],
[2, 2, 1],
[4, 4, 1],
[8, 8, 1],
[16, 16, 2],
[32, 32, 4],
];
const mostExtensiveResolutions = convertToDenseResolution(getMostExtensiveResolutions(dataset));
const densify = layer => convertToDenseResolution(layer.resolutions, mostExtensiveResolutions);

t.deepEqual(densify(dataset.dataSource.dataLayers[0]), expectedResolutions);
t.deepEqual(densify(dataset.dataSource.dataLayers[1]), expectedResolutions);
const expectedResolutions = {
"0": [[1, 1, 1], [2, 2, 1], [4, 4, 1], [8, 8, 1], [16, 16, 2], [32, 32, 4]],
"1": [[1, 1, 1], [2, 2, 2], [4, 4, 4], [8, 8, 8], [16, 16, 16], [32, 32, 4]],
};
const densify = layer => convertToDenseResolution(layer.resolutions);

t.deepEqual(densify(dataset.dataSource.dataLayers[0]), expectedResolutions[0]);
t.deepEqual(densify(dataset.dataSource.dataLayers[1]), expectedResolutions[1]);
});

test("Test empty getResolutionUnion", t => {
const dataset = {
dataSource: {
dataLayers: [],
},
};
ensureMatchingLayerResolutions(dataset);
const expectedResolutions = [];
const union = getResolutionUnion(dataset);

t.deepEqual(union, expectedResolutions);
});

test("Test getResolutionUnion", t => {
const dataset = {
dataSource: {
dataLayers: [
{
resolutions: [[4, 4, 1], [8, 8, 1], [16, 16, 2], [32, 32, 4]],
},
{
resolutions: [[2, 2, 1], [8, 8, 1], [32, 32, 4]],
},
],
},
};
ensureMatchingLayerResolutions(dataset);
const expectedResolutions = [[2, 2, 1], [4, 4, 1], [8, 8, 1], [16, 16, 2], [32, 32, 4]];
const union = getResolutionUnion(dataset);

t.deepEqual(union, expectedResolutions);
});

test("getResolutionUnion should fail since 8-8-1 != 8-8-2", t => {
const dataset = {
dataSource: {
dataLayers: [
{
resolutions: [[4, 4, 1], [8, 8, 1], [16, 16, 2], [32, 32, 4]],
},
{
resolutions: [[2, 2, 1], [8, 8, 2], [32, 32, 4]],
},
],
},
};

t.throws(() => ensureMatchingLayerResolutions(dataset));
});