Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

For 3.11.0 #661

Merged
merged 57 commits into from
Nov 17, 2023
Merged
Changes from 1 commit
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
04fdfab
Implement annotation editor on view screen
NAL-DungDT Jun 18, 2023
ce48249
Replace with sanitized file input
CGDogan Jun 21, 2023
d1125b0
Validate on message content processing not on header
CGDogan Jun 21, 2023
48e9166
Likewise for batch loader
CGDogan Jun 22, 2023
809f2b3
Add annotation assistant UI
NAL-DungDT Jun 26, 2023
4999d3b
Merge branch 'feature/add-editable-polygon' into gsoc/ml_assistant
NAL-DungDT Jun 26, 2023
07a027d
Merge branch 'feature/annotation-assistants' into gsoc/ml_assistant
NAL-DungDT Jun 26, 2023
d06742f
don't want submodules here
birm Jun 27, 2023
069cb9e
wait for pathdb code in redir.js
birm Jun 29, 2023
2c928ef
add ending paren
birm Jun 29, 2023
72affc7
Add machine learning assistant draw mode
NAL-DungDT Jul 8, 2023
ca997d0
Update machine learning draw assistant
NAL-DungDT Jul 12, 2023
c9585a4
Remove console log
NAL-DungDT Jul 12, 2023
e1cd0a7
optimize machine learning assistant and add multi annotation function
NAL-DungDT Jul 23, 2023
e1f760e
Fix add add modal modal and modal info load method
NAL-DungDT Jul 24, 2023
df69428
Fix asynchornous in endNewFeature function
NAL-DungDT Jul 24, 2023
991d169
fix undo functions with annotation assistant
NAL-DungDT Jul 24, 2023
5bf9206
Fix disable assistant mode when assistant menu close
NAL-DungDT Jul 24, 2023
ddeaa54
Fix multiple points annotation points
NAL-DungDT Jul 24, 2023
0624c55
DCM type: batchloader.js
CGDogan Jul 26, 2023
dd400df
DCM type: batchloader.html
CGDogan Jul 26, 2023
c9d201c
DCM type: table.js
CGDogan Jul 26, 2023
84c2f92
don't wait for thumbnail to return before allowing post
birm Aug 1, 2023
afb7d87
Merge pull request #647 from CGDogan/patch-3
birm Aug 2, 2023
ef2f409
Change UI of annotation assistant UI and openning method
NAL-DungDT Aug 4, 2023
7125099
Intergrate annotation assistant into brush preset label
NAL-DungDT Aug 4, 2023
cdeac8c
optimize find contour algorithm
NAL-DungDT Aug 9, 2023
78a679d
Merge pull request #640 from CGDogan/patch-1
birm Aug 10, 2023
b46a095
fix non array and inaccurate grids return from ml assistant
NAL-DungDT Aug 13, 2023
94dbb20
change edit mouse up handler of annotation edit tool
NAL-DungDT Aug 14, 2023
44a7665
Remove and add annotation point for annotation edit function
NAL-DungDT Aug 14, 2023
4d27c00
Disable undo when open mlassistant on presetlabel
NAL-DungDT Aug 17, 2023
91a37bf
clean console.log
NAL-DungDT Aug 17, 2023
bf91174
Fix preset label saving multiple annotation bugs
NAL-DungDT Aug 17, 2023
b5f0edf
Refer to iipsrv rather than OpenSlide
CGDogan Aug 19, 2023
3ac47b1
Merge pull request #651 from CGDogan/CGDogan-patch-1
birm Aug 21, 2023
fcbedc7
BioFormats files
CGDogan Aug 27, 2023
7b89e37
BioFormats extensions batchloader.js
CGDogan Aug 27, 2023
1799c88
bioformats extensions batchloader.html
CGDogan Aug 27, 2023
ab43e1f
Merge pull request #653 from CGDogan/patch-4
birm Aug 27, 2023
b711883
images in subdirs
CGDogan Sep 6, 2023
87fb0a5
Merge pull request #655 from CGDogan/subdir
birm Sep 11, 2023
737173f
DICOM button
CGDogan Sep 18, 2023
c141349
Merge pull request #656 from CGDogan/dicom
birm Sep 18, 2023
7849cd4
lintfix code, clean style
birm Oct 10, 2023
f4a6366
fix pkgs
birm Oct 10, 2023
50c9ac9
update dependent actions
birm Oct 10, 2023
cdf716b
cleanup unused files
birm Oct 10, 2023
3a7f5f2
Merge pull request #643 from BryanGsep/gsoc/ml_assistant
birm Oct 10, 2023
c9e6e5f
Merge branch 'develop' into gsoc/ml_assistant
birm Oct 10, 2023
cec0dee
style changes to assistant
birm Oct 10, 2023
78157bb
some guidance to ml model upload
birm Oct 10, 2023
86f977f
hide the smart pen icon
nanli-emory Nov 8, 2023
d31295c
Merge pull request #658 from camicroscope/gsoc/ml_assistant
nanli-emory Nov 8, 2023
4f1dd57
bump year to 2023
birm Nov 9, 2023
1262a6e
Update HISTORY.md for 3.11.0
birm Nov 17, 2023
a46d702
Index doesn't work, remove it.
birm Nov 17, 2023
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
Prev Previous commit
Next Next commit
Intergrate annotation assistant into brush preset label
  • Loading branch information
NAL-DungDT committed Aug 4, 2023
commit 71250999295b9c72ca212afd6a3ec5eac07a2948
1 change: 1 addition & 0 deletions apps/viewer/init.js
Original file line number Diff line number Diff line change
@@ -842,6 +842,7 @@ async function initUIcomponents() {
if ($UI.AssistantViewer.elt.style.display === 'none') {
$UI.AssistantViewer.elt.style.display = '';
} else {
// $UI.AssistantViewer.enableBtn.checked = false;
$UI.AssistantViewer.elt.style.display = 'none';
}
})
258 changes: 132 additions & 126 deletions apps/viewer/uicallbacks.js
Original file line number Diff line number Diff line change
@@ -318,6 +318,7 @@ function toolsOff() {
break;
case 'label':
presetLabelOff();
mlAsisstantOff();
break;
case 'download_selection':
downloadSelectionOff();
@@ -665,6 +666,7 @@ function mainMenuChange(data) {
$UI.labelsSideMenu.open();
} else {
presetLabelOff();
mlAsisstantOff();
}
}

@@ -1976,6 +1978,7 @@ function drawLabel(e) {
if (e.checked) {
if ($CAMIC.status == 'label') {
presetLabelOn.call(this, labels);
mlAsisstantOn();
return;
}
// turn off annotation
@@ -1987,13 +1990,15 @@ function drawLabel(e) {
// all tool has turn off
clearInterval(checkAllToolsOff);
presetLabelOn.call(this, labels);
mlAsisstantOn();
}
}.bind(this),
100,
);
} else {
// off preset label
presetLabelOff();
mlAsisstantOff();
}
}

@@ -2049,6 +2054,7 @@ function presetLabelOff() {

function mlAsisstantOff() {
$UI.AssistantViewer.enableBtn.checked = false;
$UI.AssistantViewer.elt.style.display = 'none';
$UI.AssistantSideMenu.close();
}

@@ -2057,7 +2063,6 @@ function mlAsisstantOn() {
}

function savePresetLabel() {
console.log('savePresetLabel');
if ($CAMIC.viewer.canvasDrawInstance._path_index === 0) {
// toast
$UI.message.addWarning('<i class="small material-icons">info</i>'+
@@ -2069,136 +2074,137 @@ function savePresetLabel() {
$UI.message.addWarning('No Label Selected. Please select One.', 4000);
return;
}
const execId = randomId();
const labelId = data.id;
const labelName = data.type;
// const parent = data.type;
const noteData = {
id: execId,
labelId: labelId,
name: labelName,
notes: data.type,
};
const feature = $CAMIC.viewer.canvasDrawInstance.getImageFeatureCollection()
.features[0];
let annotJson;
if (feature.properties.size) {
// brush
const values = getGrids(
feature.geometry.coordinates[0],
feature.properties.size,
);
const set = new Set();
values.map((i) => i.toString()).forEach((v) => set.add(v));
const points = Array.from(set).map((d) => d.split(','));
annotJson = {
creator: getUserId(),
created_date: new Date(),
provenance: {
image: {
slide: $D.params.slideId,
const features = $CAMIC.viewer.canvasDrawInstance.getImageFeatureCollection().features
for (let feature of features) {
const execId = randomId();
const labelId = data.id;
const labelName = data.type;
// const parent = data.type;
const noteData = {
id: execId,
labelId: labelId,
name: labelName,
notes: data.type,
};
let annotJson;
if (feature.properties.size) {
// brush
const values = getGrids(
feature.geometry.coordinates[0],
feature.properties.size,
);
const set = new Set();
values.map((i) => i.toString()).forEach((v) => set.add(v));
const points = Array.from(set).map((d) => d.split(','));
annotJson = {
creator: getUserId(),
created_date: new Date(),
provenance: {
image: {
slide: $D.params.slideId,
},
analysis: {
source: 'human',
execution_id: execId, // randomId
name: labelName, // labelName
labelId: labelId,
type: 'label',
isGrid: true,
},
},
analysis: {
source: 'human',
execution_id: execId, // randomId
name: labelName, // labelName
labelId: labelId,
type: 'label',
isGrid: true,
properties: {
annotations: noteData,
},
},
properties: {
annotations: noteData,
},
geometries: convertGeometries(points, {
note: data.type,
size: feature.properties.size,
color: feature.properties.style.color,
}),
};
} else {
// point / polygon / stringLine
annotJson = {
creator: getUserId(),
created_date: new Date(),
provenance: {
image: {
slide: $D.params.slideId,
geometries: convertGeometries(points, {
note: data.type,
size: feature.properties.size,
color: feature.properties.style.color,
}),
};
} else {
// point / polygon / stringLine
annotJson = {
creator: getUserId(),
created_date: new Date(),
provenance: {
image: {
slide: $D.params.slideId,
},
analysis: {
source: 'human',
execution_id: execId, // randomId
name: labelName, // labelName
labelId: labelId,
type: 'label',
},
},
analysis: {
source: 'human',
execution_id: execId, // randomId
name: labelName, // labelName
labelId: labelId,
type: 'label',
properties: {
annotations: noteData,
},
},
properties: {
annotations: noteData,
},
geometries: ImageFeaturesToVieweportFeatures(
$CAMIC.viewer,
$CAMIC.viewer.canvasDrawInstance.getImageFeatureCollection(),
),
};
}

$CAMIC.store
.addMark(annotJson)
.then((data) => {
// server error
if (data.error) {
$UI.message.addError(`${data.text}:${data.url}`);
Loading.close();
return;
}

// no data added
if (data.count < 1) {
geometries: ImageFeaturesToVieweportFeatures(
$CAMIC.viewer,
$CAMIC.viewer.canvasDrawInstance.getImageFeatureCollection(),
),
};
}

$CAMIC.store
.addMark(annotJson)
.then((data) => {
// server error
if (data.error) {
$UI.message.addError(`${data.text}:${data.url}`);
Loading.close();
return;
}

// no data added
if (data.count < 1) {
Loading.close();
$UI.message.addWarning(`Annotation Save Failed`);
return;
}
const __data = data.ops[0];
// create layer data
const newItem = {
id: execId,
name: noteData.name,
typeId: 'human',
typeName: 'human',
creator: getUserId(),
shape: annotJson.geometries.features[0].geometry.type,
isGrid: annotJson.provenance.analysis.isGrid? true: false,
label: {
id: annotJson.provenance.analysis.labelId,
name: annotJson.provenance.analysis.name,
},
data: null,
};
$D.humanlayers.push(newItem);
$UI.layersViewer.addHumanItem(newItem, 'human', labelId);
$UI.layersViewerMinor.addHumanItem(
newItem,
'human',
labelId,
$minorCAMIC && $minorCAMIC.viewer ? true : false,
);

__data._id = {$oid: __data._id};
addAnnotation(
execId,
__data,
'human',
labelId,
);
})
.catch((e) => {
Loading.close();
$UI.message.addWarning(`Annotation Save Failed`);
return;
}
const __data = data.ops[0];
// create layer data
const newItem = {
id: execId,
name: noteData.name,
typeId: 'human',
typeName: 'human',
creator: getUserId(),
shape: annotJson.geometries.features[0].geometry.type,
isGrid: annotJson.provenance.analysis.isGrid? true: false,
label: {
id: annotJson.provenance.analysis.labelId,
name: annotJson.provenance.analysis.name,
},
data: null,
};
$D.humanlayers.push(newItem);
$UI.layersViewer.addHumanItem(newItem, 'human', labelId);
$UI.layersViewerMinor.addHumanItem(
newItem,
'human',
labelId,
$minorCAMIC && $minorCAMIC.viewer ? true : false,
);

__data._id = {$oid: __data._id};
addAnnotation(
execId,
__data,
'human',
labelId,
);
})
.catch((e) => {
Loading.close();
console.log('save failed', e);
})
.finally(() => {
$UI.message.addSmall(`Added The '${noteData.name}' Annotation.`);
});
console.log('save failed', e);
})
.finally(() => {
$UI.message.addSmall(`Added The '${noteData.name}' Annotation.`);
});
}
}

function addAnnotation(id, data, type, parent) {
44 changes: 44 additions & 0 deletions common/util.js
Original file line number Diff line number Diff line change
@@ -592,6 +592,50 @@ function getGrids(points, size) {
return grids;
}

function areaCircumferenceToGrids(points, size) {
const grids = [];
// get boundary box of the points
const bound = getBounds(points);
const minX = bound[0][0];
const maxX = bound[2][0];
const minY = bound[0][1];
const maxY = bound[2][1];
// get all centers inside the boundary box
const topLeftCenter = [Math.ceil((minX - size[0] / 2) / size[0]) * size[0] + size[0] / 2, Math.ceil((minY - size[1] / 2) / size[1]) * size[1] + size[1] / 2];
for (let centerX = topLeftCenter[0]; centerX < maxX; centerX += size[0]) {
for (let centerY = topLeftCenter[1]; centerY < maxY; centerY += size[1]) {
if (isPointInsidePolygon([centerX, centerY], points)) {
grids.push(getTopLeft([centerX, centerY], size))
}
}
}
return grids;
}

function isPointInsidePolygon(point, polygon) {
const [x, y] = point;
const n = polygon.length;
let inside = false;

for (let i = 0; i < n; i++) {
const [x1, y1] = polygon[i];
const [x2, y2] = polygon[(i + 1) % n];

if (y === y1 && y1 === y2 && (x1 <= x && x <= x2 || x2 <= x && x <= x1)) {
return true;
}

if ((y1 < y && y < y2 || y2 < y && y < y1) && x < Math.max(x1, x2)) {
const intersectionX = (y - y1) * (x2 - x1) / (y2 - y1) + x1;
if (x < intersectionX) {
inside = !inside;
}
}
}

return inside;
}

function getTopLeft(point, size) {
return [
Math.floor(point[0] / size[0]) * size[0],
Loading