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

add button to reload data buckets for each layer #4383

Merged
merged 8 commits into from
Jan 13, 2020
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).

### Added
- Added `publicUri` configs for datastore and tracingstore for initial setup. [#4368](https://github.com/scalableminds/webknossos/pull/4368)
- Added a button to delete all cached data buckets of color layer and the reload them. [#4383](https://github.com/scalableminds/webknossos/pull/4383)
- Added a scale to the y-axis of histograms to indicate the logarithmic representation. Additionally, small histogram values are smoothed out. [#4349](https://github.com/scalableminds/webknossos/pull/4349)
- Added a new way of sharing annotations. You can share your annotations with selected teams. These annotations appear in the Shared Annotations Tab in the dashboard. [#4304](https://github.com/scalableminds/webknossos/pull/4304)

Expand Down
12 changes: 11 additions & 1 deletion frontend/javascripts/admin/admin_rest_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -886,10 +886,13 @@ export async function triggerDatasetCheck(datastoreHost: string): Promise<void>
export async function triggerDatasetClearCache(
datastoreHost: string,
datasetId: APIDatasetId,
layerName?: string,
): Promise<void> {
await doWithToken(token =>
Request.triggerRequest(
`/data/triggers/reload/${datasetId.owningOrganization}/${datasetId.name}?token=${token}`,
`/data/triggers/reload/${datasetId.owningOrganization}/${datasetId.name}?token=${token}${
layerName ? `&layerName=${layerName}` : ""
}`,
{
host: datastoreHost,
},
Expand All @@ -904,6 +907,13 @@ export async function triggerDatasetClearThumbnailCache(datasetId: APIDatasetId)
);
}

export async function clearCache(dataset: APIMaybeUnimportedDataset, layerName?: string) {
return Promise.all([
triggerDatasetClearCache(dataset.dataStore.url, dataset, layerName),
triggerDatasetClearThumbnailCache(dataset),
]);
}

export async function getDatasetSharingToken(
datasetId: APIDatasetId,
options?: RequestOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import { Link, type RouterHistory, withRouter } from "react-router-dom";
import * as React from "react";

import type { APIMaybeUnimportedDataset, TracingType } from "admin/api_flow_types";
import {
createExplorational,
triggerDatasetClearCache,
triggerDatasetClearThumbnailCache,
} from "admin/admin_rest_api";
import { createExplorational, clearCache } from "admin/admin_rest_api";
import Toast from "libs/toast";
import messages from "messages";
import { trackAction } from "oxalis/model/helpers/analytics";
Expand Down Expand Up @@ -55,10 +51,7 @@ class DatasetActionView extends React.PureComponent<Props, State> {
};

clearCache = async (dataset: APIMaybeUnimportedDataset) => {
await Promise.all([
triggerDatasetClearCache(dataset.dataStore.url, dataset),
triggerDatasetClearThumbnailCache(dataset),
]);
await clearCache(dataset);
Toast.success(messages["dataset.clear_cache_success"]);
};

Expand Down
19 changes: 17 additions & 2 deletions frontend/javascripts/oxalis/api/api_latest.js
Original file line number Diff line number Diff line change
Expand Up @@ -699,10 +699,25 @@ class DataApi {
}

/**
* Invalidates all downloaded buckets so that they are reloaded on the next movement.
* Invalidates all downloaded buckets of the given layer so that they are reloaded.
*/
reloadBuckets(layerName: string): void {
_.forEach(this.model.dataLayers, dataLayer => {
if (dataLayer.name === layerName) {
dataLayer.cube.collectAllBuckets();
dataLayer.layerRenderingManager.refresh();
}
});
}

/**
* Invalidates all downloaded buckets so that they are reloaded.
*/
reloadAllBuckets(): void {
_.forEach(this.model.dataLayers, dataLayer => dataLayer.cube.collectAllBuckets());
_.forEach(this.model.dataLayers, dataLayer => {
dataLayer.cube.collectAllBuckets();
dataLayer.layerRenderingManager.refresh();
});
}

/**
Expand Down
39 changes: 36 additions & 3 deletions frontend/javascripts/oxalis/view/settings/dataset_settings_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { connect } from "react-redux";
import * as React from "react";
import _ from "lodash";
import { V3 } from "libs/mjs";
import api from "oxalis/api/internal_api";

import type { APIDataset, APIHistogramData } from "admin/api_flow_types";
import { AsyncIconButton } from "components/async_clickables";
Expand All @@ -18,7 +19,7 @@ import {
DropdownSetting,
ColorSetting,
} from "oxalis/view/settings/setting_input_views";
import { findDataPositionForLayer, getHistogramForLayer } from "admin/admin_rest_api";
import { findDataPositionForLayer, getHistogramForLayer, clearCache } from "admin/admin_rest_api";
import { getGpuFactorsWithLabels } from "oxalis/model/bucket_data_handling/data_rendering_logic";
import { getMaxZoomValueForResolution } from "oxalis/model/accessors/flycam_accessor";
import {
Expand Down Expand Up @@ -130,15 +131,37 @@ class DatasetSettings extends React.PureComponent<DatasetSettingsProps, State> {
: () => Promise.resolve()
}
style={{
float: "right",
marginTop: 4,
position: "absolute",
top: 4,
right: -16,
cursor: !isDisabled ? "pointer" : "not-allowed",
}}
/>
</Tooltip>
);
};

getReloadDataButton = (layerName: string) => {
const tooltipText =
"Reload the data from the server. Use this when the data on the server changed.";
// If the tracing contains a volume tracing, the backend can only
// search in the fallback layer of the segmentation layer for data.
return (
<Tooltip title={tooltipText}>
<AsyncIconButton
type="reload"
onClick={() => this.reloadLayerData(layerName)}
style={{
position: "absolute",
top: 4,
right: 6,
cursor: "pointer",
}}
/>
</Tooltip>
);
};

setVisibilityForAllLayers = (isVisible: boolean) => {
const { layers } = this.props.datasetConfiguration;
Object.keys(layers).forEach(otherLayerName =>
Expand Down Expand Up @@ -228,6 +251,7 @@ class DatasetSettings extends React.PureComponent<DatasetSettingsProps, State> {
<span style={{ fontWeight: 700 }}>{layerName}</span>
<Tag style={{ cursor: "default", marginLeft: 8 }}>{elementClass} Layer</Tag>
{this.getFindDataButton(layerName, isDisabled, isColorLayer)}
{this.getReloadDataButton(layerName)}
</Col>
</Row>
);
Expand Down Expand Up @@ -329,6 +353,15 @@ class DatasetSettings extends React.PureComponent<DatasetSettingsProps, State> {
);
};

reloadLayerData = async (layerName: string): Promise<void> => {
await clearCache(this.props.dataset, layerName);
api.data.reloadBuckets(layerName);
window.needsRerender = true;
Toast.success(
`Successfully deleted cached data of layer ${layerName}. Now move a bit around to reload the data.`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't force the user to move around to see the new data. Setting window.needsRerender = true; above should do the trick here :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not work for me. Do you have any other suggestions or know why it might not be working?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, let's have a look together at this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this helps app.vent.trigger("rerender"); 🤷‍♂️ . Just saw it in our code base.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a look and fixed it now :) The layer rendering manager had to be refreshed.

);
};

onChangeRenderMissingDataBlack = (value: boolean): void => {
Toast.warning(
value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ trait LRUConcurrentCache[K, V] {
cache.synchronized {
val matching = cache.keySet.asScala.filter(predicate)
val size = matching.size
matching.map { key =>
remove(key)
}
matching.foreach(remove)
size
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,19 +191,21 @@ class DataSourceController @Inject()(
}
}

def reload(organizationName: String, dataSetName: String) = Action.async { implicit request =>
accessTokenService.validateAccess(UserAccessRequest.administrateDataSources) {
AllowRemoteOrigin {
val count = binaryDataServiceHolder.binaryDataService.clearCache(organizationName, dataSetName)
logger.info(s"Reloading datasource $organizationName / $dataSetName : closed " + count + " open file handles.")
val reloadedDataSource = dataSourceService.dataSourceFromFolder(
dataSourceService.dataBaseDir.resolve(organizationName).resolve(dataSetName),
organizationName)
for {
_ <- dataSourceRepository.updateDataSource(reloadedDataSource)
} yield Ok(Json.toJson(reloadedDataSource))
def reload(organizationName: String, dataSetName: String, layerName: Option[String] = None) = Action.async {
implicit request =>
accessTokenService.validateAccess(UserAccessRequest.administrateDataSources) {
AllowRemoteOrigin {
val count = binaryDataServiceHolder.binaryDataService.clearCache(organizationName, dataSetName, layerName)
logger.info(
s"Reloading ${layerName.map(l => s"layer '$l' of ").getOrElse("")}datasource $organizationName / $dataSetName: closed $count open file handles.")
val reloadedDataSource = dataSourceService.dataSourceFromFolder(
dataSourceService.dataBaseDir.resolve(organizationName).resolve(dataSetName),
organizationName)
for {
_ <- dataSourceRepository.updateDataSource(reloadedDataSource)
} yield Ok(Json.toJson(reloadedDataSource))
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,10 @@ class BinaryDataService(dataBaseDir: Path, loadTimeout: FiniteDuration, maxCache
result
}

def clearCache(organizationName: String, dataSetName: String) = {
def clearCache(organizationName: String, dataSetName: String, layerName: Option[String]) = {
def matchingPredicate(cubeKey: CachedCube) =
cubeKey.dataSourceName == dataSetName
cubeKey.dataSourceName == dataSetName && cubeKey.organization == organizationName && layerName.forall(
_ == cubeKey.dataLayerName)

cache.clear(matchingPredicate)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ GET /datasets/:organizationName/:dataSetName
GET /triggers/checkInbox @com.scalableminds.webknossos.datastore.controllers.DataSourceController.triggerInboxCheck()
GET /triggers/checkInboxBlocking @com.scalableminds.webknossos.datastore.controllers.DataSourceController.triggerInboxCheckBlocking()
GET /triggers/newOrganizationFolder @com.scalableminds.webknossos.datastore.controllers.DataSourceController.createOrganizationDirectory(organizationName: String)
GET /triggers/reload/:organizationName/:dataSetName @com.scalableminds.webknossos.datastore.controllers.DataSourceController.reload(organizationName: String, dataSetName: String)
GET /triggers/reload/:organizationName/:dataSetName @com.scalableminds.webknossos.datastore.controllers.DataSourceController.reload(organizationName: String, dataSetName: String, layerName: Option[String])