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

this allows importCSV to utilize segment selection (#612) #614

Merged
merged 1 commit into from
Dec 19, 2022
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
125 changes: 71 additions & 54 deletions src/neuroglancer/annotation/annotation_layer_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import {StatusMessage} from 'neuroglancer/status';
import {TrackableBoolean, TrackableBooleanCheckbox} from 'neuroglancer/trackable_boolean';
import {getPositionSummary, SelectedAnnotationState, UserLayerWithAnnotations} from 'neuroglancer/ui/annotations';
import {HidingList} from 'neuroglancer/ui/hiding_list';
import {br} from 'neuroglancer/util/br';
import {Borrowed, Owned} from 'neuroglancer/util/disposable';
import {mat4, transformVectorByMat4, vec3} from 'neuroglancer/util/geom';
import {formatIntegerBounds, formatIntegerPoint} from 'neuroglancer/util/spatial_units';
import {Uint64} from 'neuroglancer/util/uint64';
import {ColorWidget} from 'neuroglancer/widget/color';
import {MinimizableGroupWidget} from 'neuroglancer/widget/minimizable_group';
import {RangeWidget} from 'neuroglancer/widget/range';
Expand Down Expand Up @@ -308,6 +310,8 @@ export class AnnotationLayerView extends Tab {
const importCSVButton = document.createElement('button');
const importCSVForm = document.createElement('form');
const importCSVFileSelect = document.createElement('input');
const segmentCSVOverrideCheckbox = document.createElement('input');
const segmentCSVOverrideLabel = document.createElement('label');
exportToCSVButton.id = 'exportToCSVButton';
exportToCSVButton.textContent = 'Export to CSV';
exportToCSVButton.addEventListener('click', () => {
Expand All @@ -323,12 +327,19 @@ export class AnnotationLayerView extends Tab {
});
importCSVForm.appendChild(importCSVFileSelect);
importCSVFileSelect.addEventListener('change', () => {
this.importCSV(importCSVFileSelect.files);
this.importCSV(importCSVFileSelect.files, segmentCSVOverrideCheckbox.checked);
importCSVForm.reset();
});
importCSVFileSelect.classList.add('neuroglancer-hidden-button');
segmentCSVOverrideLabel.textContent = 'Select segments on import (Experimental): ';
segmentCSVOverrideLabel.title =
'This requires that the segmentation source of the annotation layer when importing the CSV file is the same as the segmentation source when the file was exported. Imported IDs may be outdated.'
segmentCSVOverrideCheckbox.type = 'checkbox';

const csvContainer = document.createElement('span');
csvContainer.append(exportToCSVButton, importCSVButton, importCSVForm);
csvContainer.append(
exportToCSVButton, importCSVButton, br(), segmentCSVOverrideLabel,
segmentCSVOverrideCheckbox, importCSVForm);
this.groupAnnotations.appendFixedChild(csvContainer);
}

Expand Down Expand Up @@ -1040,34 +1051,38 @@ export class AnnotationLayerView extends Tab {
}

// TODO: pull request to papa repo
private betterPapa = (inputFile: File|Blob): Promise<any> => {
return new Promise((resolve) => {
Papa.parse(inputFile, {
complete: (results: any) => {
resolve(results);
}
private betterPapa = (inputFile: File|Blob):
Promise<any> => {
return new Promise((resolve) => {
Papa.parse(inputFile, {
complete: (results: any) => {
resolve(results);
}
});
});
});
}
}

private stringToVec3 = (input: string): vec3 => {
// format: (x, y, z)
let raw = input.split('');
raw.shift();
raw.pop();
let list = raw.join('');
let val = list.split(',').map(v => parseInt(v, 10));
return vec3.fromValues(val[0], val[1], val[2]);
}
private stringToVec3 = (input: string):
vec3 => {
// format: (x, y, z)
let list = input.replace(/[{()}[\]]/g, '');
let val = list.split(',').map(v => parseInt(v, 10));
return vec3.fromValues(val[0], val[1], val[2]);
}

private dimensionsToVec3 = (input: string): vec3 => {
// format: A × B × C
let raw = input.replace(/s/g, '');
let val = raw.split('×').map(v => parseInt(v, 10));
return vec3.fromValues(val[0], val[1], val[2]);
}
private dimensionsToVec3 = (input: string): vec3 => {
// format: A × B × C
let raw = input.replace(/s/g, '');
let val = raw.split('×').map(v => parseInt(v, 10));
return vec3.fromValues(val[0], val[1], val[2]);
}
private textToPoint = (point: string, transform: mat4, dimension?: boolean) => {
const parsedVec = dimension ? this.dimensionsToVec3(point) : this.stringToVec3(point);
const spatialPoint = this.voxelSize.spatialFromVoxel(tempVec3, parsedVec);
return vec3.transformMat4(vec3.create(), spatialPoint, transform);
}

private async importCSV(files: FileList|null) {
private async importCSV(files: FileList|null, segmentOverride: boolean = false) {
const rawAnnotations = <Annotation[]>[];
let successfulImport = 0;

Expand All @@ -1081,61 +1096,59 @@ export class AnnotationLayerView extends Tab {
if (!rawData.data.length) {
continue;
}
const annStrings = rawData.data;
const annStrings = <string[][]>rawData.data;
const csvIdToRealAnnotationIdMap: {[key: string]: string} = {};
const childStorage: {[key: string]: string[]} = {};
const textToPoint = (point: string, transform: mat4, dimension?: boolean) => {
const parsedVec = dimension ? this.dimensionsToVec3(point) : this.stringToVec3(point);
const spatialPoint = this.voxelSize.spatialFromVoxel(tempVec3, parsedVec);
return vec3.transformMat4(vec3.create(), spatialPoint, transform);
};
let row = -1;

for (const annProps of annStrings) {
row++;
const type = annProps[7];
const type = annProps[7].toLowerCase();
const parentId = annProps[6];
const annotationID: string|undefined = annProps[8];
const tags = annProps[3];
const segments = annProps[5];
let raw = <Annotation>{id: makeAnnotationId(), description: annProps[4]};

switch (type) {
case 'AABB':
case 'Line':
case 'aabb':
case 'line':
raw.type =
type === 'Line' ? AnnotationType.LINE : AnnotationType.AXIS_ALIGNED_BOUNDING_BOX;
(<Line>raw).pointA = textToPoint(annProps[0], this.annotationLayer.globalToObject);
(<Line>raw).pointB = textToPoint(annProps[1], this.annotationLayer.globalToObject);
type === 'line' ? AnnotationType.LINE : AnnotationType.AXIS_ALIGNED_BOUNDING_BOX;
(<Line>raw).pointA = this.textToPoint(annProps[0], this.annotationLayer.globalToObject);
(<Line>raw).pointB = this.textToPoint(annProps[1], this.annotationLayer.globalToObject);
break;
case 'Point':
case 'point':
raw.type = AnnotationType.POINT;
(<Point>raw).point = textToPoint(annProps[0], this.annotationLayer.globalToObject);
(<Point>raw).point = this.textToPoint(annProps[0], this.annotationLayer.globalToObject);
break;
case 'Ellipsoid':
case 'ellipsoid':
raw.type = AnnotationType.ELLIPSOID;
(<Ellipsoid>raw).center = textToPoint(annProps[0], this.annotationLayer.globalToObject);
(<Ellipsoid>raw).center =
this.textToPoint(annProps[0], this.annotationLayer.globalToObject);
(<Ellipsoid>raw).radii =
textToPoint(annProps[2], this.annotationLayer.globalToObject, true);
this.textToPoint(annProps[2], this.annotationLayer.globalToObject, true);
break;
case 'Line Strip':
case 'Line Strip*':
case 'Spoke':
case 'Spoke*':
case 'Collection':
if (type === 'Line Strip' || type === 'Line Strip*') {
case 'line Strip':
case 'line Strip*':
case 'spoke':
case 'spoke*':
case 'collection':
if (type === 'line Strip' || type === 'line Strip*') {
raw.type = AnnotationType.LINE_STRIP;
(<LineStrip>raw).connected = true;
(<LineStrip>raw).looped = type === 'Line Strip*';
} else if (type === 'Spoke' || type === 'Spoke*') {
(<LineStrip>raw).looped = type === 'line Strip*';
} else if (type === 'spoke' || type === 'spoke*') {
raw.type = AnnotationType.SPOKE;
(<Spoke>raw).connected = true;
(<Spoke>raw).wheeled = type === 'Spoke*';
(<Spoke>raw).wheeled = type === 'spoke*';
} else {
raw.type = AnnotationType.COLLECTION;
(<Collection>raw).connected = false;
}
(<Collection>raw).childrenVisible = new TrackableBoolean(false, true);
(<Collection>raw).source =
textToPoint(annProps[0], this.annotationLayer.globalToObject);
this.textToPoint(annProps[0], this.annotationLayer.globalToObject);
(<Collection>raw).entry = (index: number) =>
(<LocalAnnotationSource>this.annotationLayer.source)
.get((<Collection>raw).entries[index]);
Expand Down Expand Up @@ -1185,7 +1198,11 @@ export class AnnotationLayerView extends Tab {
});
}
// Segments not supported

// getSelectedAssocatedSegment(this.annotationLayer)
// naively add segment directly from excel
if (segments && segmentOverride) {
raw.segments = segments.split(',').map((s: string) => Uint64.parseString(s));
}
rawAnnotations.push(raw);
}
successfulImport++;
Expand Down
5 changes: 3 additions & 2 deletions src/neuroglancer/save_state/save_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {Dialog} from 'neuroglancer/dialog';
import {Overlay} from 'neuroglancer/overlay';
import {dismissUnshareWarning, getSaveToAddressBar, getUnshareWarning} from 'neuroglancer/preferences/user_preferences';
import {StatusMessage} from 'neuroglancer/status';
import {br} from 'neuroglancer/util/br';
import {RefCounted} from 'neuroglancer/util/disposable';
import {getRandomHexString} from 'neuroglancer/util/random';
import {Trackable} from 'neuroglancer/util/trackable';
Expand Down Expand Up @@ -200,7 +201,8 @@ export class SaveState extends RefCounted {

this.key = getRandomHexString();
params.set('local_id', this.key);
history.pushState({}, '', `${window.location.origin}${window.location.pathname}?${params.toString()}`);
history.pushState(
{}, '', `${window.location.origin}${window.location.pathname}?${params.toString()}`);
}
private reassign(master: any) {
const hist = <string[]>master.history;
Expand Down Expand Up @@ -296,7 +298,6 @@ type FieldConfig = {
class SaveDialog extends Overlay {
constructor(public viewer: Viewer, jsonString?: string, getUrlType?: UrlType) {
super();
const br = () => document.createElement('br');
const jsonURLDefault = `LINK SHORTNER INACCESSIBLE`;

const urlStart = `${window.location.origin}${window.location.pathname}`;
Expand Down
2 changes: 1 addition & 1 deletion src/neuroglancer/user_report/user_report.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Overlay} from 'neuroglancer/overlay';
import {br} from 'neuroglancer/util/br';
import {Viewer} from 'neuroglancer/viewer';

// TODO: css
Expand All @@ -19,7 +20,6 @@ interface ItemConfig {
className?: string;
}

const br = () => document.createElement('br');
const simpleItem =
(value: string, config: ItemConfig = {
type: 'checkbox'
Expand Down
9 changes: 9 additions & 0 deletions src/neuroglancer/util/br.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
This is why JavaScript is a terrible language.
This is my manifesto.
This is my gift to the world.
Hark, and hear my words.
I am the one who knocks.
Behold, this is the beginning of the end.
*/
export const br = () => document.createElement('br');