Skip to content

Commit

Permalink
this allows importCSV to utilize segment selection (#612) (#614)
Browse files Browse the repository at this point in the history
* this allows importCSV to utilize segment selection

* update to allow any case types and allow coordinates w/o brackets
ogewan authored Dec 19, 2022
1 parent c9d1054 commit d0f5ea7
Showing 4 changed files with 84 additions and 57 deletions.
125 changes: 71 additions & 54 deletions src/neuroglancer/annotation/annotation_layer_view.ts
Original file line number Diff line number Diff line change
@@ -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';
@@ -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', () => {
@@ -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);
}

@@ -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;

@@ -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]);
@@ -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++;
5 changes: 3 additions & 2 deletions src/neuroglancer/save_state/save_state.ts
Original file line number Diff line number Diff line change
@@ -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';
@@ -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;
@@ -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}`;
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
@@ -19,7 +20,6 @@ interface ItemConfig {
className?: string;
}

const br = () => document.createElement('br');
const simpleItem =
(value: string, config: ItemConfig = {
type: 'checkbox'
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');

0 comments on commit d0f5ea7

Please sign in to comment.