Skip to content

Commit

Permalink
polishing up relationship between linked segmentation layers and time…
Browse files Browse the repository at this point in the history
…stamps
  • Loading branch information
chrisj committed Jun 6, 2024
1 parent 93ea9d3 commit fdabc70
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 50 deletions.
1 change: 1 addition & 0 deletions src/datasource/cave/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function parseCaveAnnototations(
annotationsJson: any[],
parameters: AnnotationSourceParameters,
) {
console.log("#annos", annotationsJson.length);
const seenEnums = new Map<string, Set<string>>();
const annotations: (Point | Line)[] = annotationsJson.map((x) => {
const points = parameters.relationships.map((rel) => {
Expand Down
41 changes: 26 additions & 15 deletions src/datasource/cave/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,14 @@ export class CaveAnnotationSource extends MultiscaleAnnotationSourceBase {
}
}

interface VersionMetadata {
version: number;
valid: boolean;
time_stamp: string;
is_merged: boolean;
status: string;
}

// TODO, find a better generic caching mechanism
async function getVersions(
credentialsProvider: SpecialProtocolCredentialsProvider,
Expand All @@ -204,18 +212,21 @@ async function getVersions(
) {
const existing = getVersions.cache[`${url}_${datastack}`];
if (existing) return existing;
const versonsURL = `${url}/${API_STRING_V2}/datastack/${datastack}/versions`;
const versonsURL = `${url}/${API_STRING}/datastack/${datastack}/metadata`;
const versions = (await cancellableFetchSpecialOk(
credentialsProvider,
versonsURL,
{},
responseJson,
)) as number[]; //temp
getVersions.cache[`${url}_${datastack}`] = versions.sort((a, b) => a - b);
)) as VersionMetadata[]; //TODO: parse it with verify
getVersions.cache[`${url}_${datastack}`] = versions.sort(
(a, b) => a.version - b.version,
);
return versions;
}
getVersions.cache = {} as { [key: string]: number[] };
getVersions.cache = {} as { [key: string]: VersionMetadata[] };

// todo, can we get rid of this?
async function getVersionTimestamp(
credentialsProvider: SpecialProtocolCredentialsProvider,
url: string,
Expand Down Expand Up @@ -719,17 +730,17 @@ export class CaveDataSource extends DataSourceProvider {
materializationUrl,
datastack,
);
const timestampPromises = versions.map((version) => {
return getVersionTimestamp(
credentialsProvider,
materializationUrl,
datastack,
version,
);
});
const timestamps = await Promise.all(timestampPromises);
let completions = versions.map((x, idx) => {
return { value: `${x}`, description: timestamps[idx] };
// const timestampPromises = versions.map((version) => {
// return getVersionTimestamp(
// credentialsProvider,
// materializationUrl,
// datastack,
// version,
// );
// });
// const timestamps = await Promise.all(timestampPromises);
let completions = versions.map((x) => {
return { value: `${x.version}`, description: x.time_stamp };
});
completions.push({
value: "live",
Expand Down
115 changes: 89 additions & 26 deletions src/datasource/graphene/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,7 @@ const SINK_JSON_KEY = "sink";
const SOURCE_JSON_KEY = "source";

const TIMESTAMP_JSON_KEY = "timestamp";
const TIMESTAMP_OWNER_JSON_KEY = "timestampOwner";
const MULTICUT_JSON_KEY = "multicut";
const FOCUS_SEGMENT_JSON_KEY = "focusSegment";
const SINKS_JSON_KEY = "sinks";
Expand All @@ -873,7 +874,10 @@ const PRECISION_MODE_JSON_KEY = "precision";
class GrapheneState extends RefCounted implements Trackable {
changed = new NullarySignal();

public timestamp: TrackableValue<number> = new TrackableValue(0, (x) => x);
public timestamp = new TrackableValue<number>(0, (x) => x);

public timestampOwner = new WatchableSet<string>();

public multicutState = new MulticutState();
public mergeState = new MergeState();
public findPathState = new FindPathState();
Expand All @@ -882,12 +886,12 @@ class GrapheneState extends RefCounted implements Trackable {
super();
this.registerDisposer(
this.timestamp.changed.add(() => {
this.multicutState.reset();
this.changed.dispatch();
}),
);
this.registerDisposer(
this.multicutState.changed.add(() => {
console.log("multicutState.changed");
this.changed.dispatch();
}),
);
Expand All @@ -903,13 +907,25 @@ class GrapheneState extends RefCounted implements Trackable {
);
}

canSetTimestamp(owner?: string) {
if (this.multicutState.focusSegment.value) {
return false;
}
const otherOwners = [...this.timestampOwner].filter((x) => x !== owner);
if (otherOwners.length) {
return false;
}
return true;
}

replaceSegments(oldValues: Uint64Set, newValues: Uint64Set) {
this.multicutState.replaceSegments(oldValues, newValues);
this.mergeState.replaceSegments(oldValues, newValues);
this.findPathState.replaceSegments(oldValues, newValues);
}

reset() {
this.timestampOwner.clear();
this.timestamp.reset();
this.multicutState.reset();
this.mergeState.reset();
Expand All @@ -919,13 +935,21 @@ class GrapheneState extends RefCounted implements Trackable {
toJSON() {
return {
[TIMESTAMP_JSON_KEY]: this.timestamp.toJSON(),
[TIMESTAMP_OWNER_JSON_KEY]: [...this.timestampOwner], // convert from set to array
[MULTICUT_JSON_KEY]: this.multicutState.toJSON(),
[MERGE_JSON_KEY]: this.mergeState.toJSON(),
[FIND_PATH_JSON_KEY]: this.findPathState.toJSON(),
};
}

restoreState(x: any) {
verifyOptionalObjectProperty(x, TIMESTAMP_OWNER_JSON_KEY, (value) => {
const owners = verifyStringArray(value);
this.timestampOwner.clear();
for (const owner of owners) {
this.timestampOwner.add(owner);
}
});
verifyOptionalObjectProperty(x, TIMESTAMP_JSON_KEY, (value) => {
this.timestamp.restoreState(value);
});
Expand Down Expand Up @@ -1353,13 +1377,27 @@ export class GraphConnection extends SegmentationGraphSourceConnection {

const {
annotationLayerStates,
state: { multicutState, findPathState },
state: { multicutState, findPathState, timestamp },
} = this;

this.registerDisposer(
state.timestamp.changed.add(() => {
segmentsState.selectedSegments.clear();
segmentsState.temporaryVisibleSegments.clear();
state.timestamp.changed.add(async () => {
const nonLatestRoots = await this.graph.graphServer.filterLatestRoots(
[...segmentsState.selectedSegments],
timestamp.value,
true,
);
segmentsState.selectedSegments.delete(nonLatestRoots);
const currentTimestamp = timestamp.value === 0;
if (currentTimestamp) {
const {
focusSegment: { value: focusSegment },
} = state.multicutState;
if (focusSegment) {
// segmentsState.selectedSegments.add(focusSegment);
segmentsState.visibleSegments.add(focusSegment);
}
}
}),
);

Expand Down Expand Up @@ -1572,11 +1610,21 @@ export class GraphConnection extends SegmentationGraphSourceConnection {
private lastDeselectionMessageExists = false;

private visibleSegmentsChanged(segments: Uint64[] | null, added: boolean) {
console.log("visibleSegmentsChanged");
const { segmentsState } = this;
const { state } = this.graph;
const {
focusSegment: { value: focusSegment },
} = this.graph.state.multicutState;
if (focusSegment && !segmentsState.visibleSegments.has(focusSegment)) {
} = state.multicutState;
const {
timestamp: { value: timestamp },
} = state;
const currentTimestamp = timestamp === 0;
if (
currentTimestamp &&
focusSegment &&
!segmentsState.visibleSegments.has(focusSegment)
) {
if (segmentsState.selectedSegments.has(focusSegment)) {
StatusMessage.showTemporaryMessage(
`Can't hide active multicut segment.`,
Expand Down Expand Up @@ -2068,9 +2116,15 @@ class GrapheneGraphServerInterface {
return final;
}

async filterLatestRoots(segments: Uint64[]): Promise<Uint64[]> {
const url = `${this.url}/is_latest_roots`;

async filterLatestRoots(
segments: Uint64[],
timestamp = 0,
flipResult = false,
): Promise<Uint64[]> {
const timestampEpoch = timestamp / 1000;
const url = `${this.url}/is_latest_roots${
timestamp > 0 ? `?timestamp=${timestampEpoch}` : ""
}`;
const promise = cancellableFetchSpecialOk(
this.credentialsProvider,
url,
Expand All @@ -2080,15 +2134,13 @@ class GrapheneGraphServerInterface {
},
responseIdentity,
);

const response = await withErrorMessageHTTP(promise, {
errorPrefix: `Could not check latest: `,
});
const jsonResp = await response.json();

const res: Uint64[] = [];
for (const [i, isLatest] of jsonResp["is_latest"].entries()) {
if (isLatest) {
if (isLatest !== flipResult) {
res.push(segments[i]);
}
}
Expand Down Expand Up @@ -2647,29 +2699,40 @@ function timeLayerControl(): LayerControlFactory<SegmentationUserLayer> {
graph instanceof GrapheneGraphSource
? graph.timestampLimit
: new TrackableValue<number>(0, (x) => x);
const timestampOwner =
graph instanceof GrapheneGraphSource
? graph.state.timestampOwner
: new WatchableSet<string>();

const controlElement = document.createElement("div");
controlElement.classList.add("neuroglancer-time-control");
const intermediateTimestamp = new TrackableValue<number>(
timestamp.value,
(x) => x,
);
intermediateTimestamp.changed.add(() => {
intermediateTimestamp.changed.add(async () => {
if (intermediateTimestamp.value === timestamp.value) {
return;
}
if (graph instanceof GrapheneGraphSource) {
const hasSelectedSegments =
segmentationGroupState.selectedSegments.size +
segmentationGroupState.temporaryVisibleSegments.size >
0;
if (
!hasSelectedSegments ||
confirm("Changing graphene time will clear all selected segments.")
) {
timestamp.value = intermediateTimestamp.value;
} else {
intermediateTimestamp.value = timestamp.value;
if (timestampOwner.size) {
const nonLatestRoots = await graph.graphServer.filterLatestRoots(
[...segmentationGroupState.selectedSegments],
timestamp.value,
true,
);
if (
graph.state.canSetTimestamp() &&
(!nonLatestRoots.length ||
confirm(
`Changing graphene time will clear ${nonLatestRoots.length} segment(s).`,
))
) {
timestamp.value = intermediateTimestamp.value;
return;
}
}
intermediateTimestamp.value = timestamp.value;
}
});
const widget = context.registerDisposer(
Expand Down
Loading

0 comments on commit fdabc70

Please sign in to comment.