From 8ea54e8b71a025ac5657ac0067a67fa7b92d3725 Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Wed, 3 Apr 2024 22:54:54 -0400 Subject: [PATCH 01/25] create the dicom connect table to show studies/series/instances --- apps/dicom-connect/table.css | 242 ++++++++++++++++++++++++++++++++++ apps/dicom-connect/table.html | 118 +++++++++++++++++ apps/dicom-connect/table.js | 204 ++++++++++++++++++++++++++++ 3 files changed, 564 insertions(+) create mode 100644 apps/dicom-connect/table.css create mode 100644 apps/dicom-connect/table.html create mode 100644 apps/dicom-connect/table.js diff --git a/apps/dicom-connect/table.css b/apps/dicom-connect/table.css new file mode 100644 index 000000000..60b99f205 --- /dev/null +++ b/apps/dicom-connect/table.css @@ -0,0 +1,242 @@ +html, +body { + width: 100%; + height: 100%; + min-height: 100%; + font-family: Arial, Helvetica, sans-serif; +} + +footer { + /* position: sticky; + /* bottom: -8px; */ + width: 100%; +} +.header { + margin-top: 60px; +} + +.page-container { + height: 100vh; + /* flex-direction: column; */ + /* justify-content: space-between; */ +} + +.link { + letter-spacing: 0.02em; + text-transform: uppercase; + font-size: 0.99em; + font-weight: bold; + color: #5e6875; + text-decoration: none; + padding-right: 0.7rem; + padding-left: 0.7rem; + margin-top: 0.2rem; + font-family: "Open Sans", Helvetica, sans-serif; + /* line-height: 1.8; */ +} +.bg-dark { + background-color: #343a40 !important; +} +.bg-info { + background-color: #17a2b8 !important; +} +#collection-list li.item { + cursor: pointer; +} +#collection-list li.item:hover { + background-color: #deeeff; +} +#entries { + cursor: pointer; +} +.page-item { + cursor: pointer; +} + +/* #collection-list .item:hover { + background-color:#f8f9fa; +} */ + +#collection-list .item { + /* font-weight:bold; */ + color: #007bff; + padding: 0.5rem; +} +#collection-list .item i { + padding: 0.25rem; +} + +nav li { + transition: background 0.5s; + border-radius: 3px; +} +nav li:not(:first-child) { + margin-left: 0.3em !important; +} +nav li:not(.active):hover { + background: white; +} +nav li:not(.active):hover a { + color: black !important; +} +.active { + background: white; +} +.active a { + color: black !important; +} + +nav li:not(.active):hover{ + background: white; +} + +.overall { + display: flex; +} + +.reload { + display: none; + padding: 0 4px; +} + +.btn2{ + display: flex; + align-items: center; +} +@media screen and (min-width: 480px){ + .reload { + display: block; + } +} + +#deleteBtn, +#downloadBtn { + margin-left: 0.6em; +} +#deleteBtn i { + color: white; +} +#open-delete { + display: inline-flex; +} +.custom-file-input, +.sort-btn { + cursor: pointer; +} + +#notification-box { + overflow-y: scroll; + max-height: 40em; +} + +/* Tooltip container */ +.tooltipCustom { + position: relative; + display: inline-block; + /* border-bottom: 1px dotted black; If you want dots under the hoverable text */ +} + +/* Tooltip text */ +.tooltipCustom .tooltiptextCustom { + visibility: hidden; + background-color: black; + color: #fff; + text-align: center; + padding: 5px 0; + border-radius: 6px; + + /* Position the tooltip text */ + position: absolute; + z-index: 100; + width: 200px; + bottom: 100%; + left: 50%; + margin-left: -100px; /* Use half of the width (120/2 = 60), to center the tooltip */ +} + +/* Show the tooltip text when you mouse over the tooltip container */ +.tooltipCustom:hover .tooltiptextCustom { + visibility: visible; + z-index: 1000; +} + +.notification-box { + padding: 10px 0px; + color: black; +} + +#tabs, +#content { + width: 100%; +} + +.bg-gray { + background-color: #eee; +} +@media (max-width: 640px) { + #dropNot { + top: 50px; + left: -16px; + width: 290px; + } + .nav { + display: block; + } + .nav .nav-item, + .nav .nav-item a { + padding-left: 0px; + } + .message { + font-size: 13px; + } +} +#dropNot { + top: 60px; + left: 0px; + right: unset; + width: 460px; + box-shadow: 0px 5px 7px -1px #c1c1c1; + padding-bottom: 0px; + padding: 0px; +} +.dropdown-menu:before { + content: ""; + position: absolute; + top: -20px; + left: 12px; + border: 10px solid #343a40; + border-color: transparent transparent #343a40 transparent; +} + +.has-search .form-control { + padding-left: 2.375rem; +} + +.has-search .form-control-feedback { + position: absolute; + z-index: 2; + display: block; + width: 2.375rem; + height: 2.375rem; + line-height: 2.375rem; + text-align: center; + pointer-events: none; + color: #aaa; +} + +/* .nav-tabs { + display: flex; +} */ + +.collapse.in { + display: inline !important; +} +.p { + margin-bottom: 0; +} + +/* .main-container { + max-height: calc(100% - 199px); + overflow-x: hidden; + overflow-y: auto; +} */ \ No newline at end of file diff --git a/apps/dicom-connect/table.html b/apps/dicom-connect/table.html new file mode 100644 index 000000000..1fc0328e1 --- /dev/null +++ b/apps/dicom-connect/table.html @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + CaMicroscope Data Table + + + + + + + + + + + + + + + + + +
+ + +
+

caMicroscope

+

Digital pathology image viewer with support for human/machine generated annotations and markups.

+
+ +
+
+
+ + + +
+
+
+
+
+ + + + + + + + + diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js new file mode 100644 index 000000000..37ad3fe3f --- /dev/null +++ b/apps/dicom-connect/table.js @@ -0,0 +1,204 @@ +/** + * static variables + */ + +const sources = { + 'j4care': 'https://development.j4care.com:11443/dcm4chee-arc/aets/DCM4CHEE/rs', + 'dicomweb': 'https://dicomwebproxy-bqmq3usc3a-uc.a.run.app/dicomWeb' +} +// const j4careStudiesUrl = 'https://development.j4care.com:11443/dcm4chee-arc/aets/DCM4CHEE/rs' +// const dicomWebStudiesUrl = 'https://dicomwebproxy-bqmq3usc3a-uc.a.run.app/dicomWeb' + +/** + * global variables + */ + +const datatableConfig = { + scrollX: true, + lengthMenu: [ + [10, 25, 50, -1], + [10, 25, 50, 'All'] + ] +} + + +const page_states = { + studies: { + data: null, + }, + series: { + data: null, + }, + instances: { + data: null, + }, + status: 'studies', // 'studies, series, instsances' +} +var studies = [] + + + +function getStudies(baseUrl) { + const url = `${baseUrl}/studies` + return fetch(url).then(resp=>resp.json()); +} + +function getSeries(baseUrl, studyId) { + const url = `${baseUrl}/studies/${studyId}/series` + return fetch(url).then(resp=>resp.json()); +} + +function getInstances(baseUrl, studyId, seriesId) { + const url = `${baseUrl}/studies/${studyId}/series/${seriesId}/instances` + return fetch(url).then(resp=>resp.json()); +} + + + + + +function sanitize(string) { + string = string || ''; + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + '/': '/', + }; + const reg = /[&<>"'/]/ig; + return string.replace(reg, (match) => (map[match])); +} + + + +function initialize() { + const params = getUrlVars(); + console.log('params') + console.log(params) + + if(params.status=='series'&¶ms.studyId) { // series table + page_states.status = params.status; + + + } else if(params.status=='instances'&¶ms.studyId&¶ms.seriesId) { // isntasnces table + page_states.status = params.status; + } + + + + // + // + // + switch (page_states.status) { + case 'studies': + // create breadcrumb for studies + $('#breadcrumb').append(``); + // get all studies + const Promises = []; + Promises.push(getStudies(sources.j4care)); + Promises.push(getStudies(sources.dicomweb)); + Promise.all(Promises) + .then(function(data) { + // get studies from j4care and dicomweb + const j4careStudies = data[0]; + const dicomWebStudies = data[1]; + + // mapping and merge + j4careStudies.forEach(elt=>elt.source='j4care') + dicomWebStudies.forEach(elt=>elt.source='dicomweb') + page_states[page_states.status].data = [...j4careStudies, ...dicomWebStudies]//studiesTransformer([...j4careStudies, ...dicomWebStudies]) + + // ${baseUrl}/studies/${studyId}/series + function generateLink (data, type, row) { + const studyId = row['0020000D']['Value'][0] + return `${studyId}`; + } + datatable = $('#datatable').DataTable({ + ... datatableConfig, + 'data': page_states[page_states.status].data, + 'columns': [ + {data: '0020000D.Value.0', title: 'Study Id', render: generateLink}, + {data: '00100020.Value.0', title: 'Name'}, + {data: 'source', title: 'Source'} + ] + }); + }) + + break; + case 'series': + // create breadcrumb for series + $('#breadcrumb').append(``); + $('#breadcrumb').append(``); + // get all series + getSeries(sources[params.source], params.studyId).then(function(data) { + // add source and study id + data.forEach(elt=>{ + elt.source=params.source + elt.studyId=params.studyId + + }) + + + page_states[page_states.status].data = data + function generateLink (data, type, row) { + const seriesId = row['0020000E']['Value'][0] + return `${seriesId}`; + } + console.log(data) + datatable = $('#datatable').DataTable({ + ... datatableConfig, + 'data': page_states[page_states.status].data, + 'columns': [ + {data: '0020000E.Value.0', title: 'Series Id', render: generateLink}, + {data: '00080060.Value.0', title: 'Modality'}, + {data: 'source', title: 'Source'}, + {data: 'studyId', title: 'study Id'} + + ] + }); + }) + break; + case 'instances': + // create breadcrumb for instances + const backSeriesUrl = `../dicom-connect/table.html?source=${params.source}&status=series&studyId=${params.studyId}` + $('#breadcrumb').append(``); + $('#breadcrumb').append(``); + $('#breadcrumb').append(``); + + getInstances(sources[params.source], params.studyId, params.seriesId).then(function(data) { + console.log(data) + data.forEach(elt=>{ + elt.source=params.source + elt.studyId=params.studyId + elt.seriesId=params.seriesId + }) + page_states[page_states.status].data = data + function generateLink (data, type, row) { + const {studyId, seriesId, source}= row + const instanceId = row['00080018']['Value'][0] + return `${instanceId}`; + } + datatable = $('#datatable').DataTable({ + ... datatableConfig, + 'data': page_states[page_states.status].data, + 'columns': [ + {data: '0020000D.Value.0', title: 'Instance Id', render: generateLink}, + {data: 'source', title: 'Source'}, + {data: 'seriesId', title: 'Series Id'}, + {data: 'studyId', title: 'study Id'} + ] + }); + }) + break; + + default: + break; + } +} + + +$(document).ready(function() { + initialize(); +}); From 5aec754e88ff0c4bfa29ce83a75654ceb054dec0 Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Fri, 5 Apr 2024 10:23:58 -0400 Subject: [PATCH 02/25] has series status and check series based on slides status --- apps/dicom-connect/table.css | 20 +++- apps/dicom-connect/table.js | 191 +++++++++++++++++++++++++++-------- 2 files changed, 170 insertions(+), 41 deletions(-) diff --git a/apps/dicom-connect/table.css b/apps/dicom-connect/table.css index 60b99f205..786b61235 100644 --- a/apps/dicom-connect/table.css +++ b/apps/dicom-connect/table.css @@ -235,8 +235,26 @@ nav li:not(.active):hover{ margin-bottom: 0; } +.icon-center { + text-align: center!important +} /* .main-container { max-height: calc(100% - 199px); overflow-x: hidden; overflow-y: auto; -} */ \ No newline at end of file +} */ + +.loader { + -webkit-animation: spin 2s linear infinite; + animation: spin 2s linear infinite; +} + +@-webkit-keyframes spin { + 0% { -webkit-transform: rotate(0deg); } + 100% { -webkit-transform: rotate(360deg); } +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} \ No newline at end of file diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 37ad3fe3f..050f80295 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -2,16 +2,21 @@ * static variables */ -const sources = { - 'j4care': 'https://development.j4care.com:11443/dcm4chee-arc/aets/DCM4CHEE/rs', - 'dicomweb': 'https://dicomwebproxy-bqmq3usc3a-uc.a.run.app/dicomWeb' -} +const sources = [{ + 'name':'j4care', + 'url':'https://development.j4care.com:11443/dcm4chee-arc/aets/DCM4CHEE/rs' + +},{ + 'name': 'google', + 'url': 'https://dicomwebproxy-bqmq3usc3a-uc.a.run.app/dicomWeb' +}] // const j4careStudiesUrl = 'https://development.j4care.com:11443/dcm4chee-arc/aets/DCM4CHEE/rs' // const dicomWebStudiesUrl = 'https://dicomwebproxy-bqmq3usc3a-uc.a.run.app/dicomWeb' /** * global variables */ +isAllSeriesSynced = false; const datatableConfig = { scrollX: true, @@ -23,6 +28,16 @@ const datatableConfig = { const page_states = { + sources: { + data: [{ + 'name':'j4care', + 'url':'https://development.j4care.com:11443/dcm4chee-arc/aets/DCM4CHEE/rs' + + },{ + 'name': 'google', + 'url': 'https://dicomwebproxy-bqmq3usc3a-uc.a.run.app/dicomWeb' + }], + }, studies: { data: null, }, @@ -32,7 +47,7 @@ const page_states = { instances: { data: null, }, - status: 'studies', // 'studies, series, instsances' + status: 'sources', // 'sources, studies, series, instsances' } var studies = [] @@ -72,17 +87,17 @@ function sanitize(string) { } - function initialize() { const params = getUrlVars(); console.log('params') console.log(params) - - if(params.status=='series'&¶ms.studyId) { // series table + // store + const store = new Store('../../../data/'); + if(params.status=='studies'&¶ms.source){ page_states.status = params.status; - - - } else if(params.status=='instances'&¶ms.studyId&¶ms.seriesId) { // isntasnces table + }else if(params.status=='series'&¶ms.source&¶ms.studyId) { // series table + page_states.status = params.status; + } else if(params.status=='instances'&¶ms.source&¶ms.studyId&¶ms.seriesId) { // isntasnces table page_states.status = params.status; } @@ -92,28 +107,40 @@ function initialize() { // // switch (page_states.status) { + case 'sources': + $('#breadcrumb').append(``); + function generateLink (data, type, row) { + return `${row.name}`; + } + datatable = $('#datatable').DataTable({ + ... datatableConfig, + 'data': page_states[page_states.status].data, + 'columns': [ + {data: 'name', title: 'Name', render:generateLink} + ] + }); + + break; case 'studies': + + //get source info + var idx = sources.findIndex(elt=>elt.name==params.source) + var src = sources[idx] + // create breadcrumb for studies - $('#breadcrumb').append(``); + $('#breadcrumb').append(``); + $('#breadcrumb').append(``); // get all studies - const Promises = []; - Promises.push(getStudies(sources.j4care)); - Promises.push(getStudies(sources.dicomweb)); - Promise.all(Promises) - .then(function(data) { - // get studies from j4care and dicomweb - const j4careStudies = data[0]; - const dicomWebStudies = data[1]; - + + getStudies(src.url).then(function(data) { // mapping and merge - j4careStudies.forEach(elt=>elt.source='j4care') - dicomWebStudies.forEach(elt=>elt.source='dicomweb') - page_states[page_states.status].data = [...j4careStudies, ...dicomWebStudies]//studiesTransformer([...j4careStudies, ...dicomWebStudies]) + data.forEach(elt=>elt.source=src.name) + page_states[page_states.status].data = data // ${baseUrl}/studies/${studyId}/series function generateLink (data, type, row) { const studyId = row['0020000D']['Value'][0] - return `${studyId}`; + return `${studyId}`; } datatable = $('#datatable').DataTable({ ... datatableConfig, @@ -128,66 +155,137 @@ function initialize() { break; case 'series': + //get source info + var idx = sources.findIndex(elt=>elt.name==params.source) + var src = sources[idx] + // create breadcrumb for series - $('#breadcrumb').append(``); + $('#breadcrumb').append(``); + $('#breadcrumb').append(``); $('#breadcrumb').append(``); // get all series - getSeries(sources[params.source], params.studyId).then(function(data) { + + + getSeries(src.url, params.studyId).then(function(data) { // add source and study id data.forEach(elt=>{ - elt.source=params.source + elt.source=src.name elt.studyId=params.studyId - + elt.status='loading' // 'loading', 'unsync', 'syncing', 'done' }) page_states[page_states.status].data = data function generateLink (data, type, row) { const seriesId = row['0020000E']['Value'][0] - return `${seriesId}`; + if (row.status !='done') return seriesId + return `${seriesId}`; } - console.log(data) + function generateStatus (data, type, row) { + switch (row.status) { + case 'loading': + // return spin + return '
'; + case 'unsync': + // return btn + const seriesId = row['0020000E']['Value'][0] + return `
`; + case 'syncing': + // return downloading + // return '
'; + return `
` + case 'done': + // return url + + return '
'; + + default: + + return '
'; + } + } datatable = $('#datatable').DataTable({ ... datatableConfig, 'data': page_states[page_states.status].data, 'columns': [ - {data: '0020000E.Value.0', title: 'Series Id', render: generateLink}, + {data: 'status', title: 'Status', render: generateStatus}, + {data: '0020000E.Value.0', title: 'Series Id',render:generateLink }, {data: '00080060.Value.0', title: 'Modality'}, {data: 'source', title: 'Source'}, {data: 'studyId', title: 'study Id'} ] - }); + }); + + // set interval to check series status + var updateSeriesStatus = setInterval(async function() { + // get slides status + const query = { + 'dicomSource': params.source, + } + // ('dicomSource', 'study', 'series', 'instance' + const slides = await store.findSlide(null, null, params.studyId, null, query) + // update series data + datatable.data().each(function (d) { + d['0020000E']['Value'][0] + const idx = slides.findIndex(slide=>d['0020000E']['Value'][0]==slide.series) + if (idx!=-1) { + d.status = slides[idx].dicomSync + } + }); + + // invalidate all rows and redraw + datatable.rows().invalidate().draw(); + const series = page_states[page_states.status].data + if (series.map(s=>s.status).every(status=>status!='unsync'&&status!='syncing')) { // clear interval if all synced + console.log('clear'); + clearInterval(updateSeriesStatus); + } + console.log('running') + }, 1000); }) break; case 'instances': + //get source info + var idx = sources.findIndex(elt=>elt.name==params.source) + var src = sources[idx] // create breadcrumb for instances const backSeriesUrl = `../dicom-connect/table.html?source=${params.source}&status=series&studyId=${params.studyId}` - $('#breadcrumb').append(``); + $('#breadcrumb').append(``); + $('#breadcrumb').append(``); $('#breadcrumb').append(``); $('#breadcrumb').append(``); - getInstances(sources[params.source], params.studyId, params.seriesId).then(function(data) { - console.log(data) + + + + + getInstances(src.url, params.studyId, params.seriesId).then(function(data) { + // add status data.forEach(elt=>{ elt.source=params.source elt.studyId=params.studyId elt.seriesId=params.seriesId + }) + console.log(data) page_states[page_states.status].data = data function generateLink (data, type, row) { - const {studyId, seriesId, source}= row + const {studyId, seriesId, status}= row const instanceId = row['00080018']['Value'][0] - return `${instanceId}`; + if (status=='done') return instanceId + return `${instanceId}`; } + datatable = $('#datatable').DataTable({ ... datatableConfig, 'data': page_states[page_states.status].data, 'columns': [ - {data: '0020000D.Value.0', title: 'Instance Id', render: generateLink}, + {data: '00080018.Value.0', title: 'Instance Id', render: generateLink}, {data: 'source', title: 'Source'}, {data: 'seriesId', title: 'Series Id'}, - {data: 'studyId', title: 'study Id'} + {data: 'studyId', title: 'Study Id'}, + ] }); }) @@ -202,3 +300,16 @@ function initialize() { $(document).ready(function() { initialize(); }); + + +function syncSeries(source, studyId, seriesId) { + console.log(`syncSeries: ${source}, ${studyId}, ${seriesId}`) +} + +function checkSeriesStatus() { + const series = page_states[page_states.status].data + series.map() + +} +// table.rows.add( dataset ).draw(). + From 2a9741d87ac759750992b0dd079e628dec4ade36 Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Fri, 5 Apr 2024 10:57:33 -0400 Subject: [PATCH 03/25] set default series status to 'unsync' if can't find one --- apps/dicom-connect/table.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 050f80295..2c118f2f0 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -231,6 +231,8 @@ function initialize() { const idx = slides.findIndex(slide=>d['0020000E']['Value'][0]==slide.series) if (idx!=-1) { d.status = slides[idx].dicomSync + } else { + d.status = 'unsync' } }); From 449b5f1a9ba5eb34f9e2c1ac10eeb53a8bdb8847 Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Fri, 5 Apr 2024 16:57:15 -0400 Subject: [PATCH 04/25] change the download icon --- apps/dicom-connect/table.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 2c118f2f0..7a165a631 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -136,7 +136,7 @@ function initialize() { // mapping and merge data.forEach(elt=>elt.source=src.name) page_states[page_states.status].data = data - + console.log(data) // ${baseUrl}/studies/${studyId}/series function generateLink (data, type, row) { const studyId = row['0020000D']['Value'][0] @@ -174,7 +174,7 @@ function initialize() { elt.status='loading' // 'loading', 'unsync', 'syncing', 'done' }) - + console.log(data) page_states[page_states.status].data = data function generateLink (data, type, row) { const seriesId = row['0020000E']['Value'][0] @@ -189,7 +189,7 @@ function initialize() { case 'unsync': // return btn const seriesId = row['0020000E']['Value'][0] - return `
`; + return `
`; // case 'syncing': // return downloading // return '
'; From 78e6ad58603ae81cfc251adca8b0541371a42cef Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Fri, 5 Apr 2024 16:58:30 -0400 Subject: [PATCH 05/25] remove logs --- apps/dicom-connect/table.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 7a165a631..031c25dce 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -135,8 +135,7 @@ function initialize() { getStudies(src.url).then(function(data) { // mapping and merge data.forEach(elt=>elt.source=src.name) - page_states[page_states.status].data = data - console.log(data) + page_states[page_states.status].data = data // ${baseUrl}/studies/${studyId}/series function generateLink (data, type, row) { const studyId = row['0020000D']['Value'][0] @@ -173,8 +172,6 @@ function initialize() { elt.studyId=params.studyId elt.status='loading' // 'loading', 'unsync', 'syncing', 'done' }) - - console.log(data) page_states[page_states.status].data = data function generateLink (data, type, row) { const seriesId = row['0020000E']['Value'][0] @@ -270,7 +267,6 @@ function initialize() { elt.seriesId=params.seriesId }) - console.log(data) page_states[page_states.status].data = data function generateLink (data, type, row) { const {studyId, seriesId, status}= row From 3f4ece70c16438085a3addd4b4ff793fa6dfd8af Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Fri, 5 Apr 2024 17:00:27 -0400 Subject: [PATCH 06/25] add drawing circle and ellipse annotations --- apps/mini/uicallbacks.js | 3 +- apps/viewer/uicallbacks.js | 3 +- common/DrawHelper.js | 67 ++++++++++++------- common/util.js | 11 ++- .../openseadragon-overlays-manage.js | 12 ++++ 5 files changed, 70 insertions(+), 26 deletions(-) diff --git a/apps/mini/uicallbacks.js b/apps/mini/uicallbacks.js index e24265d19..c1ba9bb08 100644 --- a/apps/mini/uicallbacks.js +++ b/apps/mini/uicallbacks.js @@ -1359,7 +1359,8 @@ function locationCallback(layerData) { return; } // locate annotation 3.0 - if (item.data.geometries.features[0].geometry.type == 'Point') { + const geoType = item.data.geometries.features[0].geometry.type + if (geoType == 'Point'||geoType == 'Circle'||geoType == 'Ellipse') { const bound = item.data.geometries.features[0].bound.coordinates; const center = $CAMIC.viewer.viewport.imageToViewportCoordinates( bound[0], diff --git a/apps/viewer/uicallbacks.js b/apps/viewer/uicallbacks.js index 8f2015fd0..c06970d6d 100644 --- a/apps/viewer/uicallbacks.js +++ b/apps/viewer/uicallbacks.js @@ -1547,7 +1547,8 @@ function locationCallback(layerData) { return; } // locate annotation 3.0 - if (item.data.geometries.features[0].geometry.type == 'Point') { + const geoType = item.data.geometries.features[0].geometry.type + if (geoType == 'Point'||geoType == 'Circle'||geoType == 'Ellipse') { const bound = item.data.geometries.features[0].bound.coordinates; const center = $CAMIC.viewer.viewport.imageToViewportCoordinates( bound[0], diff --git a/common/DrawHelper.js b/common/DrawHelper.js index 027d3969b..977a93a59 100644 --- a/common/DrawHelper.js +++ b/common/DrawHelper.js @@ -82,7 +82,9 @@ caDrawHelper.prototype.drawMultiline = function(ctx,array){ this.drawLine(ctx,array[i-1],array[i]); } } -caDrawHelper.prototype.circle = function(ctx, point, radius){ +caDrawHelper.prototype.circle = function(ctx, point, radius, isPoint=true){ + + const path = new Path(); path.arc( point[0], @@ -90,11 +92,32 @@ caDrawHelper.prototype.circle = function(ctx, point, radius){ radius, 0, 2 * Math.PI ); path.closePath(); - path.strokeAndFill(ctx); - //path.stroke(ctx); + if(isPoint) { + path.strokeAndFill(ctx); + } else { + path.stroke(ctx); + } // return points and path return path; } +caDrawHelper.prototype.ellipse = function(ctx, point, radius, rotation){ + const path = new Path(); + path.ellipse( + point[0], + point[1], + radius[0], + radius[1], + rotation * Math.PI, + 0, + 2 * Math.PI + ); + path.closePath(); + // path.strokeAndFill(ctx); + path.stroke(ctx); + // return points and path + return path; +} + caDrawHelper.prototype.drawMultiGrid = function(ctx, points, size){ const path = new Path(); points.forEach(p=>{ @@ -125,25 +148,6 @@ caDrawHelper.prototype.drawLine = function(ctx, start, end){ ctx.stroke(); } -/** - * draw a circle - * @param {CanvasRenderingContext2D} ctx - * is used for drawing rectangles, text, images and other objects onto the canvas element - * @param {Number} cx - * The x-coordinate of the center of the circle - * @param {Number} xy - * The x-coordinate of the center of the circle - * @param {Number} r - * The radius of the circle - */ -caDrawHelper.prototype.drawCircle = function(ctx, cx, cy, r){ - // draw line - ctx.beginPath(); - ctx.arc(cx, cy, r, 0, 2 * Math.PI); - ctx.stroke(); - ctx.closePath() - -} /** * draw a polygon on a canvas * @param {CanvasRenderingContext2D} ctx @@ -221,8 +225,25 @@ caDrawHelper.prototype.draw = function(ctx, image_data){ && !this.isPointInBBox(ctx.viewBoundBoxInData, {x:point[0],y:point[1]})) continue; ctx.fillStyle = (ctx.isFill ==undefined || ctx.isFill)?hexToRgbA(style.color,1):style.color; + console.log(this) polygon.geometry.path = this.circle(ctx, polygon.geometry.coordinates, ctx.radius); - }else if(false){ + } + else if(polygon.geometry.type=='Circle') { + const point = polygon.geometry.coordinates + if(ctx.viewBoundBoxInData + && !this.isPointInBBox(ctx.viewBoundBoxInData, {x:point[0],y:point[1]})) continue; + + ctx.fillStyle = (ctx.isFill ==undefined || ctx.isFill)?hexToRgbA(style.color,1):style.color; + polygon.geometry.path = this.circle(ctx, polygon.geometry.coordinates, polygon.geometry.radius, false); + }else if(polygon.geometry.type=='Ellipse'){ + const point = polygon.geometry.coordinates + if(ctx.viewBoundBoxInData + && !this.isPointInBBox(ctx.viewBoundBoxInData, {x:point[0],y:point[1]})) continue; + + ctx.fillStyle = (ctx.isFill ==undefined || ctx.isFill)?hexToRgbA(style.color,1):style.color; + polygon.geometry.path = this.ellipse(ctx, polygon.geometry.coordinates, polygon.geometry.radius, polygon.geometry.rotation); + } + else if(false){ }else{ // determine drawing or not diff --git a/common/util.js b/common/util.js index c695c5af0..aae1477e4 100644 --- a/common/util.js +++ b/common/util.js @@ -405,13 +405,22 @@ function VieweportFeaturesToImageFeatures(viewer, geometries) { this.imgHeight = image.source.dimensions.y; geometries.features = geometries.features.map((feature) => { - if (feature.geometry.type=='Point') { + if (feature.geometry.type=='Point'||feature.geometry.type=='Circle'||feature.geometry.type=='Ellipse') { feature.geometry.coordinates = [ Math.round(feature.geometry.coordinates[0] * imgWidth), Math.round(feature.geometry.coordinates[1] * imgHeight)]; feature.bound.coordinates =[ Math.round(feature.bound.coordinates[0] * imgWidth), Math.round(feature.bound.coordinates[1] * imgHeight)]; + + + + if (feature.geometry.type=='Circle') { + feature.geometry.radius = Math.round(feature.geometry.radius * imgWidth) + } + if (feature.geometry.type=='Ellipse') { + feature.geometry.radius = [Math.round(feature.geometry.radius[0] * imgWidth), Math.round(feature.geometry.radius[1] * imgHeight)]; + } return feature; } feature.geometry.coordinates[0] = feature.geometry.coordinates[0].map( diff --git a/core/extension/openseadragon-overlays-manage.js b/core/extension/openseadragon-overlays-manage.js index 6116e7d9b..889cb4400 100644 --- a/core/extension/openseadragon-overlays-manage.js +++ b/core/extension/openseadragon-overlays-manage.js @@ -383,6 +383,12 @@ pointPath.closePath(); pointPath.strokeAndFill(ctx); this.editPointPathList.push(pointPath); + } else if (this.editPathData.geometry.type === 'Circle') { + // TODO editor + console.log('drawEditPoints Circle'); + } else if (this.editPathData.geometry.type === 'Ellipse') { + // TODO editor + console.log('drawEditPoints Ellipse'); } else { pathData[0].map((point) => { const pointPath = new Path(); @@ -497,6 +503,12 @@ pointPath.closePath(); pointPath.strokeAndFill(this._edit_tool_ctx_); this.editPointPathList.push(pointPath); + } else if (this.editPathData.geometry.type === 'Circle') { + // TODO editor + console.log('onEditPointMouseMove Circle'); + } else if (this.editPathData.geometry.type === 'Ellipse') { + // TODO editor + console.log('onEditPointMouseMove Ellipse'); } else { // brush this.editPathData.geometry.coordinates[0][this.onEditIndex] = [img_point.x, img_point.y]; From d2050a80fef45ea3042c9b49fe1a9b9916d60884 Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Fri, 5 Apr 2024 17:04:13 -0400 Subject: [PATCH 07/25] add semicolon --- apps/mini/uicallbacks.js | 2 +- apps/viewer/uicallbacks.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/mini/uicallbacks.js b/apps/mini/uicallbacks.js index c1ba9bb08..4c37458fb 100644 --- a/apps/mini/uicallbacks.js +++ b/apps/mini/uicallbacks.js @@ -1359,7 +1359,7 @@ function locationCallback(layerData) { return; } // locate annotation 3.0 - const geoType = item.data.geometries.features[0].geometry.type + const geoType = item.data.geometries.features[0].geometry.type; if (geoType == 'Point'||geoType == 'Circle'||geoType == 'Ellipse') { const bound = item.data.geometries.features[0].bound.coordinates; const center = $CAMIC.viewer.viewport.imageToViewportCoordinates( diff --git a/apps/viewer/uicallbacks.js b/apps/viewer/uicallbacks.js index c06970d6d..69a7b5e8f 100644 --- a/apps/viewer/uicallbacks.js +++ b/apps/viewer/uicallbacks.js @@ -1547,7 +1547,7 @@ function locationCallback(layerData) { return; } // locate annotation 3.0 - const geoType = item.data.geometries.features[0].geometry.type + const geoType = item.data.geometries.features[0].geometry.type; if (geoType == 'Point'||geoType == 'Circle'||geoType == 'Ellipse') { const bound = item.data.geometries.features[0].bound.coordinates; const center = $CAMIC.viewer.viewport.imageToViewportCoordinates( From da54165fb63e0e53bab34932bf80afb80e576395 Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Fri, 5 Apr 2024 19:55:34 -0400 Subject: [PATCH 08/25] add syncSeries to import Series --- apps/dicom-connect/table.js | 12 ++++++++---- core/Store.js | 30 +++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 031c25dce..223fa1e3e 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -185,8 +185,9 @@ function initialize() { return '
'; case 'unsync': // return btn - const seriesId = row['0020000E']['Value'][0] - return `
`; // + const seriesId = row['0020000E']['Value'][0]; + const modality = row['00080060']['Value'][0]; + return `
`; // case 'syncing': // return downloading // return '
'; @@ -300,8 +301,11 @@ $(document).ready(function() { }); -function syncSeries(source, studyId, seriesId) { - console.log(`syncSeries: ${source}, ${studyId}, ${seriesId}`) +async function syncSeries(source_url, study, series, modality) { + console.log(source_url, study, series, modality); + const result = await store.syncSeries('../../../', {source_url, study, series, modality}) + console.log('syncSeries:'); + console.log(result); } function checkSeriesStatus() { diff --git a/core/Store.js b/core/Store.js index 65ed127eb..57ff35c84 100644 --- a/core/Store.js +++ b/core/Store.js @@ -36,6 +36,7 @@ function objToParamStr(obj) { class Store { constructor(base, validation, config) { this.base = base || './data/'; + this.validation = validation || {}; this.config = config; } @@ -945,7 +946,34 @@ class Store { body: JSON.stringify(update), }); } - + /*** + * dicom api start + * + */ + async syncSeries(baseUrl, data = {}) { + // the data structure: + // const {source_url, study, series, modality} = data + + const suffix = 'loader/dicomWeb/importSeries'; + const url = baseUrl + suffix; + // Default options are marked with * + const response = await fetch(url, { + method: "POST", + mode: "cors", + cache: "no-cache", + headers: { + "Content-Type": "application/json", + }, + redirect: "follow", + referrerPolicy: "no-referrer", + body: JSON.stringify(data), + }); + return response.json(); + } + /*** + * dicom api end + * + */ addPresetLabels(labels) { const suffix = 'Presetlabels/add'; const url = this.base + suffix; From 6c3b24498f664fb0c055085e4ec48a72745554c7 Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Fri, 5 Apr 2024 20:00:25 -0400 Subject: [PATCH 09/25] change rotation in radians --- common/DrawHelper.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/DrawHelper.js b/common/DrawHelper.js index 977a93a59..5e0d608ae 100644 --- a/common/DrawHelper.js +++ b/common/DrawHelper.js @@ -100,6 +100,8 @@ caDrawHelper.prototype.circle = function(ctx, point, radius, isPoint=true){ // return points and path return path; } + +// rotation in radians caDrawHelper.prototype.ellipse = function(ctx, point, radius, rotation){ const path = new Path(); path.ellipse( @@ -107,7 +109,7 @@ caDrawHelper.prototype.ellipse = function(ctx, point, radius, rotation){ point[1], radius[0], radius[1], - rotation * Math.PI, + rotation, 0, 2 * Math.PI ); From 58c1034de3b73b5f76b010f9734555023ff79702 Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Fri, 5 Apr 2024 22:12:56 -0400 Subject: [PATCH 10/25] add url for source --- apps/dicom-connect/table.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 223fa1e3e..a9ff33b59 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -169,6 +169,7 @@ function initialize() { // add source and study id data.forEach(elt=>{ elt.source=src.name + elt.url=src.url elt.studyId=params.studyId elt.status='loading' // 'loading', 'unsync', 'syncing', 'done' }) @@ -187,7 +188,7 @@ function initialize() { // return btn const seriesId = row['0020000E']['Value'][0]; const modality = row['00080060']['Value'][0]; - return `
`; // + return `
`; // case 'syncing': // return downloading // return '
'; From 43022c777e289eac11680fa968e6174965a12b2d Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Fri, 5 Apr 2024 23:39:56 -0400 Subject: [PATCH 11/25] match params for findSlide() --- apps/dicom-connect/table.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index a9ff33b59..284e5a02b 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -171,7 +171,7 @@ function initialize() { elt.source=src.name elt.url=src.url elt.studyId=params.studyId - elt.status='loading' // 'loading', 'unsync', 'syncing', 'done' + elt.status='searching' // 'searching', 'unsync', 'loading', 'done' }) page_states[page_states.status].data = data function generateLink (data, type, row) { @@ -181,7 +181,7 @@ function initialize() { } function generateStatus (data, type, row) { switch (row.status) { - case 'loading': + case 'searching': // return spin return '
'; case 'unsync': @@ -189,7 +189,7 @@ function initialize() { const seriesId = row['0020000E']['Value'][0]; const modality = row['00080060']['Value'][0]; return `
`; // - case 'syncing': + case 'loading': // return downloading // return '
'; return `
` @@ -220,7 +220,7 @@ function initialize() { var updateSeriesStatus = setInterval(async function() { // get slides status const query = { - 'dicomSource': params.source, + 'dicom-source-url': src.url, } // ('dicomSource', 'study', 'series', 'instance' const slides = await store.findSlide(null, null, params.studyId, null, query) @@ -229,7 +229,7 @@ function initialize() { d['0020000E']['Value'][0] const idx = slides.findIndex(slide=>d['0020000E']['Value'][0]==slide.series) if (idx!=-1) { - d.status = slides[idx].dicomSync + d.status = slides[idx].status } else { d.status = 'unsync' } From afcdf5629ea930266bf0cec1af9097544df9f8bf Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Mon, 8 Apr 2024 11:00:25 -0400 Subject: [PATCH 12/25] update the slide link --- apps/dicom-connect/table.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 284e5a02b..333f42005 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -176,7 +176,12 @@ function initialize() { page_states[page_states.status].data = data function generateLink (data, type, row) { const seriesId = row['0020000E']['Value'][0] - if (row.status !='done') return seriesId + const modality = row['00080060']['Value'][0] + if (row.status !='done') return seriesId; + const slideId = row.slideId; + if (modality=='SM') return `${seriesId}` + + return `${seriesId}`; } function generateStatus (data, type, row) { @@ -228,8 +233,10 @@ function initialize() { datatable.data().each(function (d) { d['0020000E']['Value'][0] const idx = slides.findIndex(slide=>d['0020000E']['Value'][0]==slide.series) + if (idx!=-1) { d.status = slides[idx].status + d.slideId = slides[idx]._id.$oid } else { d.status = 'unsync' } From 2451332f6a1583237b21868598e4905082235ebd Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Mon, 8 Apr 2024 12:52:56 -0400 Subject: [PATCH 13/25] check annotation stsatus if annotation exist and added findMarks into Store --- apps/dicom-connect/table.js | 43 ++++++++++++++++++++++++++++--------- core/Store.js | 38 ++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 333f42005..8c3d5befc 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -179,10 +179,11 @@ function initialize() { const modality = row['00080060']['Value'][0] if (row.status !='done') return seriesId; const slideId = row.slideId; - if (modality=='SM') return `${seriesId}` + //if (modality=='SM') + return `${seriesId}` - return `${seriesId}`; + // return `${seriesId}`; } function generateStatus (data, type, row) { switch (row.status) { @@ -229,17 +230,39 @@ function initialize() { } // ('dicomSource', 'study', 'series', 'instance' const slides = await store.findSlide(null, null, params.studyId, null, query) + const annotationQuery = { + 'provenance.image.dicom-source-url':src.url, + 'provenance.image.dicom-study': params.studyId // study + + } + const annotations = await store.findMarks(annotationQuery) // update series data datatable.data().each(function (d) { - d['0020000E']['Value'][0] - const idx = slides.findIndex(slide=>d['0020000E']['Value'][0]==slide.series) - - if (idx!=-1) { - d.status = slides[idx].status - d.slideId = slides[idx]._id.$oid - } else { - d.status = 'unsync' + const modality = d['00080060']['Value'][0] + const series = d['0020000E']['Value'][0] + + if (modality == 'SM'){ + // match slide + const idx = slides.findIndex(slide=>series==slide.series) + + if (idx!=-1) { + d.status = slides[idx].status + d.slideId = slides[idx]._id.$oid + } else { + d.status = 'unsync' + } } + if (modality == 'ANN'){ + // match annotations + const idx_annot = annotations.findIndex(annot=>annot.provenance.image['dicom-series']&&series==annot.provenance.image['dicom-series']) + if (idx_annot!=-1) { + d.status = 'done'; + d.slideId = annotations[idx_annot].provenance.image.slide; + } else { + d.status = 'unsync'; + } + } + }); // invalidate all rows and redraw diff --git a/core/Store.js b/core/Store.js index 57ff35c84..9cba27a2c 100644 --- a/core/Store.js +++ b/core/Store.js @@ -155,6 +155,44 @@ class Store { mode: 'cors', }); } + findMarks(q) { + const suffix = 'Mark/find'; + const url = this.base + suffix; + var query = {}; + if (q) { + query = q; + } + return fetch(url + '?' + objToParamStr(query), { + credentials: 'include', + mode: 'cors', + }).then(this.errorHandler).then((x) => this.filterBroken(x, 'mark')); + } + + findSlide(name, specimen, study, location, q) { + let query = {}; + const suffix = 'Slide/find'; + const url = this.base + suffix; + if (q) { + query = q; + } else { + if (name) { + query.name = name; + } + if (study) { + query.study = study; + } + if (specimen) { + query.specimen = specimen; + } + if (location) { + query.location = location; + } + } + return fetch(url + '?' + objToParamStr(query), { + credentials: 'include', + mode: 'cors', + }).then(this.errorHandler); + } /** * find marks matching slide and/or marktype * will search by slide field as exactly given and by the oid slide of that name From d03344ea3418c1a14155d1f74e0024ae372cb3a9 Mon Sep 17 00:00:00 2001 From: Birm Date: Tue, 23 Apr 2024 17:11:37 -0400 Subject: [PATCH 14/25] make checks much less frequent, per min vs per second --- apps/dicom-connect/table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 8c3d5befc..915a2babb 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -273,7 +273,7 @@ function initialize() { clearInterval(updateSeriesStatus); } console.log('running') - }, 1000); + }, 60000); }) break; case 'instances': From 30dfd2aedd8374dfdb0ac8920ce670276e710e88 Mon Sep 17 00:00:00 2001 From: Birm Date: Tue, 23 Apr 2024 17:14:34 -0400 Subject: [PATCH 15/25] split out refresh --- apps/dicom-connect/table.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 915a2babb..62d1a1718 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -222,8 +222,7 @@ function initialize() { ] }); - // set interval to check series status - var updateSeriesStatus = setInterval(async function() { + async function checkInterval() { // get slides status const query = { 'dicom-source-url': src.url, @@ -273,7 +272,12 @@ function initialize() { clearInterval(updateSeriesStatus); } console.log('running') - }, 60000); + } + + // initialize + checkInterval() + // update every 30 seconds + var updateSeriesStatus = setInterval(checkInterval, 30000); }) break; case 'instances': From 703dfbf5e7fc7227c91ded6a04e6f17f21988243 Mon Sep 17 00:00:00 2001 From: Birm Date: Tue, 23 Apr 2024 18:39:23 -0400 Subject: [PATCH 16/25] try using count for annots --- apps/dicom-connect/table.js | 16 +++++++++------- core/Store.js | 13 +++++++++++++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 62d1a1718..13e0de537 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -234,7 +234,7 @@ function initialize() { 'provenance.image.dicom-study': params.studyId // study } - const annotations = await store.findMarks(annotationQuery) + const annotationCount = await store.countMarks(annotationQuery) // update series data datatable.data().each(function (d) { const modality = d['00080060']['Value'][0] @@ -252,11 +252,13 @@ function initialize() { } } if (modality == 'ANN'){ - // match annotations - const idx_annot = annotations.findIndex(annot=>annot.provenance.image['dicom-series']&&series==annot.provenance.image['dicom-series']) - if (idx_annot!=-1) { + // if we see a count, note this + + if (annotationCount[0].count > 0) { d.status = 'done'; - d.slideId = annotations[idx_annot].provenance.image.slide; + const idx = slides.findIndex(slide=>series==slide.series) + d.slideId = slides[idx]._id.$oid + //d.slideId = annotations[idx_annot].provenance.image.slide; } else { d.status = 'unsync'; } @@ -276,8 +278,8 @@ function initialize() { // initialize checkInterval() - // update every 30 seconds - var updateSeriesStatus = setInterval(checkInterval, 30000); + // update every 10 seconds + var updateSeriesStatus = setInterval(checkInterval, 10000); }) break; case 'instances': diff --git a/core/Store.js b/core/Store.js index 9cba27a2c..30374c993 100644 --- a/core/Store.js +++ b/core/Store.js @@ -168,6 +168,19 @@ class Store { }).then(this.errorHandler).then((x) => this.filterBroken(x, 'mark')); } + countMarks(q) { + const suffix = 'Mark/count'; + const url = this.base + suffix; + var query = {}; + if (q) { + query = q; + } + return fetch(url + '?' + objToParamStr(query), { + credentials: 'include', + mode: 'cors', + }).then(this.errorHandler).then((x) => this.filterBroken(x, 'mark')); + } + findSlide(name, specimen, study, location, q) { let query = {}; const suffix = 'Slide/find'; From f511a01f3c40af6884c15e8bfb9ba08cef746b12 Mon Sep 17 00:00:00 2001 From: Birm Date: Tue, 23 Apr 2024 18:46:18 -0400 Subject: [PATCH 17/25] slide hotfix for now --- apps/dicom-connect/table.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 13e0de537..5092e7da2 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -256,8 +256,8 @@ function initialize() { if (annotationCount[0].count > 0) { d.status = 'done'; - const idx = slides.findIndex(slide=>series==slide.series) - d.slideId = slides[idx]._id.$oid + console.log(slides) + d.slideId = slides[0]._id.$oid //d.slideId = annotations[idx_annot].provenance.image.slide; } else { d.status = 'unsync'; From 86c747601fd7ef9995d47e5a4a75d8413752aee8 Mon Sep 17 00:00:00 2001 From: Birm Date: Tue, 23 Apr 2024 23:55:26 -0400 Subject: [PATCH 18/25] annots check separate --- apps/dicom-connect/table.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 5092e7da2..a532aec33 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -229,17 +229,11 @@ function initialize() { } // ('dicomSource', 'study', 'series', 'instance' const slides = await store.findSlide(null, null, params.studyId, null, query) - const annotationQuery = { - 'provenance.image.dicom-source-url':src.url, - 'provenance.image.dicom-study': params.studyId // study - - } - const annotationCount = await store.countMarks(annotationQuery) + // update series data - datatable.data().each(function (d) { + datatable.data().each(async function (d) { const modality = d['00080060']['Value'][0] const series = d['0020000E']['Value'][0] - if (modality == 'SM'){ // match slide const idx = slides.findIndex(slide=>series==slide.series) @@ -252,11 +246,16 @@ function initialize() { } } if (modality == 'ANN'){ - // if we see a count, note this + let annotationQuery = { + 'provenance.image.dicom-source-url':src.url, + 'provenance.image.dicom-study': params.studyId, // study + 'provenance.image.dicom-series': series + } + let annotationCount = await store.countMarks(annotationQuery) + console.info("Counted " + annotationCount[0].count + " mark objects for " + series) if (annotationCount[0].count > 0) { d.status = 'done'; - console.log(slides) d.slideId = slides[0]._id.$oid //d.slideId = annotations[idx_annot].provenance.image.slide; } else { From 35b8159401c272fb3c82a0aca1ac8da1e9a2cb20 Mon Sep 17 00:00:00 2001 From: Birm Date: Tue, 23 Apr 2024 23:59:19 -0400 Subject: [PATCH 19/25] try simplify thing --- apps/dicom-connect/table.js | 94 +++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index a532aec33..8114a66aa 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -223,57 +223,59 @@ function initialize() { }); async function checkInterval() { - // get slides status const query = { - 'dicom-source-url': src.url, - } - // ('dicomSource', 'study', 'series', 'instance' - const slides = await store.findSlide(null, null, params.studyId, null, query) - - // update series data - datatable.data().each(async function (d) { - const modality = d['00080060']['Value'][0] - const series = d['0020000E']['Value'][0] - if (modality == 'SM'){ - // match slide - const idx = slides.findIndex(slide=>series==slide.series) - - if (idx!=-1) { - d.status = slides[idx].status - d.slideId = slides[idx]._id.$oid - } else { - d.status = 'unsync' - } - } - if (modality == 'ANN'){ - let annotationQuery = { - 'provenance.image.dicom-source-url':src.url, - 'provenance.image.dicom-study': params.studyId, // study - 'provenance.image.dicom-series': series + 'dicom-source-url': src.url, + }; + + const slides = await store.findSlide(null, null, params.studyId, null, query); + + const data = datatable.data(); + + for (let i = 0; i < data.length; i++) { + const d = data[i]; + const modality = d['00080060']['Value'][0]; + const series = d['0020000E']['Value'][0]; + + if (modality === 'SM') { + const idx = slides.findIndex(slide => series === slide.series); + if (idx !== -1) { + d.status = slides[idx].status; + d.slideId = slides[idx]._id.$oid; + } else { + d.status = 'unsync'; + } } - let annotationCount = await store.countMarks(annotationQuery) - console.info("Counted " + annotationCount[0].count + " mark objects for " + series) - - if (annotationCount[0].count > 0) { - d.status = 'done'; - d.slideId = slides[0]._id.$oid - //d.slideId = annotations[idx_annot].provenance.image.slide; - } else { - d.status = 'unsync'; + + if (modality === 'ANN') { + let annotationQuery = { + 'provenance.image.dicom-source-url': src.url, + 'provenance.image.dicom-study': params.studyId, + 'provenance.image.dicom-series': series + }; + + let annotationCount = await store.countMarks(annotationQuery); + console.info("Counted " + annotationCount[0].count + " mark objects for " + series); + + if (annotationCount[0].count > 0) { + d.status = 'done'; + d.slideId = slides[0]._id.$oid; + } else { + d.status = 'unsync'; + } } - } - - }); - - // invalidate all rows and redraw + } + datatable.rows().invalidate().draw(); - const series = page_states[page_states.status].data - if (series.map(s=>s.status).every(status=>status!='unsync'&&status!='syncing')) { // clear interval if all synced - console.log('clear'); - clearInterval(updateSeriesStatus); + + const series = page_states[page_states.status].data; + + if (series.every(s => s.status !== 'unsync' && s.status !== 'syncing')) { + console.log('clear'); + clearInterval(updateSeriesStatus); } - console.log('running') - } + + console.log('running'); + } // initialize checkInterval() From 78eb7e7c926ca0d90ecd7c2fa31214a94371252c Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 24 Apr 2024 00:22:11 -0400 Subject: [PATCH 20/25] try fix slide search --- apps/dicom-connect/table.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 8114a66aa..400898da7 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -225,9 +225,11 @@ function initialize() { async function checkInterval() { const query = { 'dicom-source-url': src.url, + 'study': params.studyId, }; const slides = await store.findSlide(null, null, params.studyId, null, query); + console.log(slides) const data = datatable.data(); From 39f340be77e42593593a7c8d8d503f32bbc57137 Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 24 Apr 2024 16:01:00 -0400 Subject: [PATCH 21/25] fill in marks --- common/DrawHelper.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/DrawHelper.js b/common/DrawHelper.js index 5e0d608ae..f82107330 100644 --- a/common/DrawHelper.js +++ b/common/DrawHelper.js @@ -171,8 +171,8 @@ caDrawHelper.prototype.drawPolygon = function(ctx, paths){ // close path and set style path.closePath() if(ctx.isFill ==undefined || ctx.isFill){ - // path.fill(ctx); - path.stroke(ctx); + path.fill(ctx); + //path.stroke(ctx); }else{ path.stroke(ctx); } @@ -348,4 +348,4 @@ caDrawHelper.prototype.clearCanvas = function(canvas){ } var DrawHelper = new caDrawHelper(); -//OpenSeadragon.DrawHelper = DrawHelper; \ No newline at end of file +//OpenSeadragon.DrawHelper = DrawHelper; From 9572ed08249ab1909a8c9e3f300b39ff0486c2f0 Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 24 Apr 2024 17:12:16 -0400 Subject: [PATCH 22/25] fix relative paths --- apps/dicom-connect/table.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/dicom-connect/table.html b/apps/dicom-connect/table.html index 1fc0328e1..3c9a662ca 100644 --- a/apps/dicom-connect/table.html +++ b/apps/dicom-connect/table.html @@ -18,10 +18,10 @@ // __auth_check(1) - - - - + + + + From 65970d814cf23594278502656013e4f74fe7e01f Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 24 Apr 2024 17:19:32 -0400 Subject: [PATCH 23/25] bring up store --- apps/dicom-connect/table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 400898da7..74f8a5854 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -92,7 +92,7 @@ function initialize() { console.log('params') console.log(params) // store - const store = new Store('../../../data/'); + const store = new Store('../../data/'); if(params.status=='studies'&¶ms.source){ page_states.status = params.status; }else if(params.status=='series'&¶ms.source&¶ms.studyId) { // series table From c187d13240288acdb255980e843bf9cfbcb36102 Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 24 Apr 2024 17:19:59 -0400 Subject: [PATCH 24/25] Update table.js --- apps/dicom-connect/table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dicom-connect/table.js b/apps/dicom-connect/table.js index 74f8a5854..785cf4542 100644 --- a/apps/dicom-connect/table.js +++ b/apps/dicom-connect/table.js @@ -343,7 +343,7 @@ $(document).ready(function() { async function syncSeries(source_url, study, series, modality) { console.log(source_url, study, series, modality); - const result = await store.syncSeries('../../../', {source_url, study, series, modality}) + const result = await store.syncSeries('../../', {source_url, study, series, modality}) console.log('syncSeries:'); console.log(result); } From bcb25a37cdbb16f316a8be9a04d416cfc33800e3 Mon Sep 17 00:00:00 2001 From: Birm Date: Mon, 29 Apr 2024 11:07:31 -0400 Subject: [PATCH 25/25] add enum hotfix to dicom connect --- common/util.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/common/util.js b/common/util.js index aae1477e4..9e367ae26 100644 --- a/common/util.js +++ b/common/util.js @@ -367,6 +367,12 @@ function getUrlVars() { deleteProperty: function(target, prop) { delete state[prop.toLowerCase()]; return true; + }, + getOwnPropertyDescriptor(target, prop) { + return { configurable: true, enumerable: true }; + }, + ownKeys: function(target) { + return Reflect.ownKeys(state) } }); }