Skip to content

Commit

Permalink
Fix clip histogram button if ideal mag does not exist (#6433)
Browse files Browse the repository at this point in the history
* fix clip histogram button if ideal mag does not exist

* Revert "fix clip histogram button if ideal mag does not exist"

This reverts commit f17883c.

* Revert "Revert "fix clip histogram button if ideal mag does not exist""

This reverts commit 8893963.

* avoid that too much data is loaded when calling getViewportData in unfavorable circumstances

* update changelog

* fix wrong calculation of maximum voxel limit

Co-authored-by: Florian M <[email protected]>
  • Loading branch information
philippotto and fm3 authored Sep 15, 2022
1 parent c944273 commit 69c49d0
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 40 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Fixed the duplicate function for annotations with an editable mapping (a.k.a. supervoxel proofreading) layer. [#6446](https://github.com/scalableminds/webknossos/pull/6446)
- Fixed isosurface loading for volume annotations with mappings. [#6458](https://github.com/scalableminds/webknossos/pull/6458)
- Fixed importing of remote datastore (e.g., zarr) when datastore is set up separately. [#6462](https://github.com/scalableminds/webknossos/pull/6462)
- Fixed a crash which could happen when using the "Automatically clip histogram" feature in certain scenarios. [#6433](https://github.com/scalableminds/webknossos/pull/6433)

### Removed

Expand Down
31 changes: 20 additions & 11 deletions frontend/javascripts/oxalis/api/api_latest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1388,31 +1388,40 @@ class DataApi {
maybeResolutionIndex: number | null | undefined,
) {
const state = Store.getState();
const [curX, curY, curZ] = dimensions.transDim(
const [curU, curV, curW] = dimensions.transDim(
dimensions.roundCoordinate(getPosition(state.flycam)),
viewport,
);
const [halfViewportExtentX, halfViewportExtentY] = getHalfViewportExtentsFromState(
const [halfViewportExtentU, halfViewportExtentV] = getHalfViewportExtentsFromState(
state,
viewport,
);
const layer = getLayerByName(state.dataset, layerName);
const resolutionInfo = getResolutionInfo(layer.resolutions);
if (maybeResolutionIndex == null) {
maybeResolutionIndex = getRequestLogZoomStep(state);
}
const zoomStep = resolutionInfo.getClosestExistingIndex(maybeResolutionIndex);

const min = dimensions.transDim(
V3.sub([curX, curY, curZ], [halfViewportExtentX, halfViewportExtentY, 0]),
V3.sub([curU, curV, curW], [halfViewportExtentU, halfViewportExtentV, 0]),
viewport,
);
const max = dimensions.transDim(
V3.add([curX, curY, curZ], [halfViewportExtentX, halfViewportExtentY, 1]),
V3.add([curU, curV, curW], [halfViewportExtentU, halfViewportExtentV, 1]),
viewport,
);

let zoomStep;
if (maybeResolutionIndex == null) {
zoomStep = getRequestLogZoomStep(state);
} else {
const layer = getLayerByName(state.dataset, layerName);
const resolutionInfo = getResolutionInfo(layer.resolutions);
zoomStep = resolutionInfo.getClosestExistingIndex(maybeResolutionIndex);
const resolution = resolutionInfo.getResolutionByIndexOrThrow(zoomStep);
const resolutionUVX = dimensions.transDim(resolution, viewport);
const widthInVoxel = Math.ceil(halfViewportExtentU / resolutionUVX[0]);
const heightInVoxel = Math.ceil(halfViewportExtentV / resolutionUVX[1]);
if (widthInVoxel * heightInVoxel > 1024 ** 2) {
throw new Error(
"Requested data for viewport cannot be loaded, since the amount of data is too large for the available resolution. Please zoom in further or ensure that coarser magnifications are available.",
);
}

const cuboid = await this.getDataForBoundingBox(
layerName,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,10 @@ export class ResolutionInfo {
});

const bestIndexWithDistance = _.head(_.sortBy(indicesWithDistances, (entry) => entry[1]));
if (bestIndexWithDistance == null) {
throw new Error("Couldn't find any resolution.");
}

// @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.
return bestIndexWithDistance[0];
}

Expand Down
70 changes: 42 additions & 28 deletions frontend/javascripts/oxalis/model/sagas/clip_histogram_saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { takeEvery } from "typed-redux-saga";
import type { ClipHistogramAction } from "oxalis/model/actions/settings_actions";
import { updateLayerSettingAction } from "oxalis/model/actions/settings_actions";
import Toast from "libs/toast";
import { OrthoViews } from "oxalis/constants";
import { OrthoViews, Vector3 } from "oxalis/constants";
import { getConstructorForElementClass } from "oxalis/model/bucket_data_handling/bucket";
import { getLayerByName } from "oxalis/model/accessors/dataset_accessor";
import api from "oxalis/api/internal_api";
Expand All @@ -18,33 +18,41 @@ function onThresholdChange(layerName: string, [firstVal, secVal]: [number, numbe
}
}

async function getClippingValues(layerName: string, thresholdRatio: number = 0.0001) {
const { elementClass } = getLayerByName(Store.getState().dataset, layerName);
async function getClippingValues(
layerName: string,
thresholdRatio: number = 0.0001,
): Promise<{ values: Vector3 } | { message: string }> {
const state = Store.getState();
const { dataset } = state;
const { elementClass } = getLayerByName(dataset, layerName);
const [TypedArrayClass] = getConstructorForElementClass(elementClass);

// Find a viable resolution to compute the histogram on
// Ideally, we want to avoid resolutions 1 and 2 to keep
// the amount of data that has to be loaded small and
// to de-noise the data
const state = Store.getState();
const maybeResolutionIndex = Math.max(2, getRequestLogZoomStep(state) + 1);

const [cuboidXY, cuboidXZ, cuboidYZ] = await Promise.all([
api.data.getViewportData(OrthoViews.PLANE_XY, layerName, maybeResolutionIndex),
api.data.getViewportData(OrthoViews.PLANE_XZ, layerName, maybeResolutionIndex),
api.data.getViewportData(OrthoViews.PLANE_YZ, layerName, maybeResolutionIndex),
]);
const dataForAllViewPorts = new TypedArrayClass(
cuboidXY.length + cuboidXZ.length + cuboidYZ.length,
);

// If getViewportData returned a BigUint array, dataForAllViewPorts will be an BigUint array, too.
// @ts-ignore
dataForAllViewPorts.set(cuboidXY);
// @ts-ignore
dataForAllViewPorts.set(cuboidXZ, cuboidXY.length);
// @ts-ignore
dataForAllViewPorts.set(cuboidYZ, cuboidXY.length + cuboidXZ.length);
const desiredResolutionIndex = Math.max(2, getRequestLogZoomStep(state) + 1);

let dataForAllViewPorts;
try {
const [cuboidXY, cuboidXZ, cuboidYZ] = await Promise.all([
api.data.getViewportData(OrthoViews.PLANE_XY, layerName, desiredResolutionIndex),
api.data.getViewportData(OrthoViews.PLANE_XZ, layerName, desiredResolutionIndex),
api.data.getViewportData(OrthoViews.PLANE_YZ, layerName, desiredResolutionIndex),
]);
dataForAllViewPorts = new TypedArrayClass(cuboidXY.length + cuboidXZ.length + cuboidYZ.length);
// If getViewportData returned a BigUint array, dataForAllViewPorts will be an BigUint array, too.
// @ts-ignore
dataForAllViewPorts.set(cuboidXY);
// @ts-ignore
dataForAllViewPorts.set(cuboidXZ, cuboidXY.length);
// @ts-ignore
dataForAllViewPorts.set(cuboidYZ, cuboidXY.length + cuboidXZ.length);
} catch (exception) {
console.error("Could not clip histogram due to", exception);
return { message: "Could not clip the histogram. Zoom in further and try again." };
}

const localHist = new Map();
for (let i = 0; i < dataForAllViewPorts.length; i++) {
if (dataForAllViewPorts[i] !== 0) {
Expand Down Expand Up @@ -86,18 +94,23 @@ async function getClippingValues(layerName: string, thresholdRatio: number = 0.0
}
}

if (lowerClip === -1 || upperClip === -1) {
return {
message:
"The histogram could not be clipped, because the data did not contain any brightness values greater than 0.",
};
}

// largest brightness value is first after the keys were reversed
const wiggleRoom = Math.floor(thresholdRatio * sortedHistKeys[0]);
return [lowerClip, upperClip, wiggleRoom];
return { values: [lowerClip, upperClip, wiggleRoom] };
}

async function clipHistogram(action: ClipHistogramAction) {
const [lowerClip, upperClip, wiggleRoom] = await getClippingValues(action.layerName);
const result = await getClippingValues(action.layerName);

if (lowerClip === -1 || upperClip === -1) {
Toast.warning(
"The histogram could not be clipped, because the data did not contain any brightness values greater than 0.",
);
if ("message" in result) {
Toast.warning(result.message);

// this is required to correctly reset the state of the AsyncButton initiating this action
if (action.callback != null) {
Expand All @@ -106,6 +119,7 @@ async function clipHistogram(action: ClipHistogramAction) {

return;
}
const [lowerClip, upperClip, wiggleRoom] = result.values;

if (!action.shouldAdjustClipRange) {
onThresholdChange(action.layerName, [lowerClip, upperClip]);
Expand Down

0 comments on commit 69c49d0

Please sign in to comment.