Skip to content

Commit

Permalink
merged master -> get-scu
Browse files Browse the repository at this point in the history
  • Loading branch information
amazy committed Jan 9, 2025
2 parents f4371fc + c479b1a commit ca67f21
Show file tree
Hide file tree
Showing 20 changed files with 825 additions and 254 deletions.
15 changes: 12 additions & 3 deletions Plugin/DefaultConfiguration.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@
"EnableLinkToLegacyUi": true, // Enables a link to the legacy Orthanc UI
"EnableChangePassword": true, // Enables the 'change password' button in the side bar. Only applicable if Keycloak is enabled
"EnableViewerQuickButton": true, // Enables a button in the study list to directly open a viewer
"EnableReportQuickButton": false, // Enables a button in the study list to directly open a PDF report if available in the study
"EnableReportQuickButton": false, // Enables a button in the study list to directly open a PDF report if available in the study

"EnableEditLabels": true, // Enables labels management (create/delete/assign/unassign)
"AvailableLabels": [], // If not empty, this list prevents the creation of new labels and only allows add/remove of the listed labels.
// This configuration may be overriden when you use Keycloak and an auth-service that implements roles/permissions API.
"EnableLabelsCount": true, // Enables display of study count next to the each label (this might slow down the UI)

"EnableShares": false, // Enables sharing studies. See "Tokens" section below.
"DefaultShareDuration": 0, // [in days]. 0 means no expiration date,
Expand Down Expand Up @@ -117,14 +118,22 @@
"wsi"
],

"MaxStudiesDisplayed": 100, // The maximum number of studies displayed in the study list
"MaxMyJobsHistorySize": 5, // The maximum number of jobs appearing under 'my jobs' in side bar (0 = unlimited)
"MaxStudiesDisplayed": 100, // The maximum number of studies displayed in the study list.
// From v 1.7.0, this option is not used anymore in the local study list when using
// a DB backend that supports ExtendedFind (SQLite and PostgreSQL)
// but is still used in the DicomWeb queries.
"PageLoadSize": 50, // The number of items that are loaded when scrolling the study or instance list.
// Only applicable with a DB backend that supports ExtendedFind.

"MaxMyJobsHistorySize": 5, // The maximum number of jobs appearing under 'my jobs' in side bar (0 = unlimited)

"StudyListSearchMode": "search-as-you-type",// mode to trigger a search in the StudyList. Accepted values: 'search-as-you-type' or 'search-button'
"StudyListSearchAsYouTypeMinChars": 3, // minimum number of characters to enter in a text search field before it starts searching the DB
"StudyListSearchAsYouTypeDelay": 400, // Delay [ms] between the last key stroke and the trigger of the search
"StudyListContentIfNoSearch": "most-recents", // Defines what to show if no search criteria has been entered
// Allowed values: "empty", "most-recents"
// From v 1.7.0, this option is always considered as "most-recents" when using
// a DB backend that supports ExtendedFind (SQLite and PostgreSQL)

// Default settings are ok for "small" Orthanc Databases. For large databases, it is recommended to use these settings:
// "StudyListSearchMode": "search-button"
Expand Down
21 changes: 18 additions & 3 deletions Plugin/Plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Json::Value pluginsConfiguration_;
bool hasUserProfile_ = false;
bool openInOhifV3IsExplicitelyDisabled = false;
bool enableShares_ = false;
bool isReadOnly_ = false;
std::string customCssPath_;
std::string theme_ = "light";
std::string customLogoPath_;
Expand Down Expand Up @@ -343,6 +344,8 @@ void ReadConfiguration()
}

enableShares_ = pluginJsonConfiguration_["UiOptions"]["EnableShares"].asBool(); // we are sure that the value exists since it is in the default configuration file

isReadOnly_ = orthancFullConfiguration_->GetBooleanValue("ReadOnly", false);
}

bool GetPluginConfiguration(Json::Value& jsonPluginConfiguration, const std::string& sectionName)
Expand Down Expand Up @@ -463,12 +466,12 @@ Json::Value GetPluginsConfiguration(bool& hasUserProfile)
else if (pluginName == "ohif")
{
pluginsConfiguration[pluginName]["Enabled"] = true;
std::string ohifDataSource = "dicom-json";
std::string ohifDataSource = "dicom-web";
if (GetPluginConfiguration(pluginConfiguration, "OHIF"))
{
if (pluginConfiguration.isMember("DataSource") && pluginConfiguration["DataSource"].asString() == "dicom-web")
if (pluginConfiguration.isMember("DataSource") && pluginConfiguration["DataSource"].asString() == "dicom-json")
{
ohifDataSource = "dicom-web";
ohifDataSource = "dicom-json";
}
}
pluginsConfiguration[pluginName]["DataSource"] = ohifDataSource;
Expand Down Expand Up @@ -692,6 +695,18 @@ void GetOE2Configuration(OrthancPluginRestOutput* output,

}

// disable operations on read only systems
if (isReadOnly_)
{
uiOptions["EnableUpload"] = false;
uiOptions["EnableAddSeries"] = false;
uiOptions["EnableDeleteResources"] = false;
uiOptions["EnableModification"] = false;
uiOptions["EnableAnonymization"] = false;
uiOptions["EnableEditLabels"] = false;
uiOptions["EnablePermissionsEdition"] = false;
}


oe2Configuration["Keycloak"] = GetKeycloakConfiguration();
std::string answer = oe2Configuration.toStyledString();
Expand Down
23 changes: 10 additions & 13 deletions TODO
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
- implement pagination to fetch studies 100 -> 200 when we scroll to the end of the study-list
- implement pagination to fetch instances 100 -> 200 when we scroll to the end of the instance-list (displaying 2000 instances in one go sometimes takes 10 seconds !)


- add a "reset" button (on Windows, to reload the config after you have changed it without going to the Services -> Orthanc -> Restart)
- show ohif-vr and ohif-tmtv buttons only when relevant (analyse the content of the study)
- handle the EnableLabels "permission"

- use DatePicker in remote modality study list

- predefined filters in config file to display below Studies:
- "Today": {"StudyDate": "$today"}
Expand All @@ -12,15 +13,18 @@
UI improvements:
- Admin theme including responsive tables (including multiline): https://github.com/lekoala/admini
- tags: https://github.com/lekoala/bootstrap5-tags

- when opening the series view I would prefer to see an image and it’s labels over a list of instance numbers and paired SOPInstanceUIDs:
https://discourse.orthanc-server.org/t/beginner-questions-from-horos-user/5322

- modification:
- configuration to hide DICOM UID options and select the right one directly for a given setup.
- add a Series Description renamer (one dialog to edit SeriesNumber + SeriesDescription of a study)

- show attachments
- settings:
- show the list of /tools/accepted-transfer-syntaxes
- show the list of /tools/accepted-sop-classes (in 1.12.6)

- support dicom-web plugin (QIDO-RS UI)
- show attachments

- support neuro plugin (download nifti)

Expand Down Expand Up @@ -53,13 +57,6 @@ UI improvements:
}
- configure other viewers url (ex: radiant://?n=pstv&v=0020000D&v=%22StudyInstanceUID%22 or osirix or horos ...)

- make table sortable and faster search by maintaining a cache DB (SQLite in each Orthanc instance handled by the oe2 plugin)
- only for studies. with a few indexed tags (the ones from the UI table)
- plugin monitors /changes route to see changes from all orthancs (instead of reacting to change events)
- to monitor deleted studies, react to change event + call other oe2 plugin API ? (need to list all orthanc urls somewhere)
- from OHIF doc for their study list: When the Study List is opened, the application queries the PACS for 101 studies by default. If there are greater than 100 studies
returned, the default sort for the study list is dictated by the image archive that hosts these studies for the viewer and study list sorting will be disabled. If there are less than or equal to 100 studies returned, they will be sorted by study date (most recent to oldest) and study list sorting will be enabled. Whenever a query returns greater than 100 studies, use filters to narrow results below 100 studies to enable Study List sorting.

- orthanc-share should generate QR code with publication links

- Q&R on multiple modalities at a same time (select the modalities you want to Q&R and display the modality in the study list)
Expand Down
49 changes: 29 additions & 20 deletions WebApplication/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion WebApplication/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"vue": "^3.4.21",
"vue-i18n": "^9.10.2",
"vue-router": "^4.3.0",
"vuex": "^4.1.0"
"vuex": "^4.1.0",
"vue3-observe-visibility": "^1.0.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
Expand Down
128 changes: 128 additions & 0 deletions WebApplication/src/components/InstanceListExtended.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<script>
import InstanceItem from "./InstanceItem.vue"
import api from "../orthancApi"
import { ObserveVisibility as vObserveVisibility } from 'vue3-observe-visibility'
export default {
props: ['seriesId', 'seriesMainDicomTags', 'studyMainDicomTags', 'patientMainDicomTags', 'seriesInstances'],
emits: ['deletedInstance'],
data() {
return {
loaded: false,
instancesInfo: {}
};
},
computed: {
sortedInstancesIds() {
if (this.loaded) {
let keys = Object.keys(this.instancesInfo);
keys.sort((a, b) => (parseInt(this.instancesInfo[a].IndexInSeries) > parseInt(this.instancesInfo[b].IndexInSeries) ? 1 : -1))
return keys;
} else {
return [];
}
},
},
watch: {
seriesInstances(newValue, oldValue) {
for (const instanceInfo of this.seriesInstances) {
this.instancesInfo[instanceInfo.ID] = instanceInfo;
}
this.loaded = true;
}
},
async mounted() {
},
methods: {
onDeletedInstance(instanceId) {
delete this.instancesInfo[instanceId];
this.$emit("deletedInstance", instanceId);
},
async visibilityChanged(isVisible, entry) {
if (isVisible) {
let instanceId = entry.target.id;
// console.log(instanceId, this.seriesInstances);
if (instanceId == this.seriesInstances[this.seriesInstances.length - 1]['ID']) {
// console.log("Last element shown -> should load more instances");
const instances = await api.getSeriesInstancesExtended(this.seriesId, this.seriesInstances.length);
this.seriesInstances.push(...instances);
for (const instanceInfo of instances) {
this.instancesInfo[instanceInfo.ID] = instanceInfo;
}
}
}
}
},
components: { InstanceItem }
}
</script>

<template>
<table class="table table-responsive table-sm instance-table">
<thead>
<tr>
<th width="2%" scope="col" class="instance-table-header"></th>
<th width="7%" scope="col" class="instance-table-header cut-text" data-bs-toggle="tooltip"
:title="$t('dicom_tags.InstanceNumber')">{{ $t('dicom_tags.InstanceNumber') }}</th>
<th width="40%" scope="col" class="instance-table-header cut-text" data-bs-toggle="tooltip"
title="SOP Instance UID">SOP Instance UID</th>
<th width="5%" scope="col" class="series-table-header cut-text text-center" data-bs-toggle="tooltip"
:title="$t('dicom_tags.NumberOfFrames')"># {{$t('frames')}}</th>
</tr>
</thead>
<InstanceItem v-for="instanceId in sortedInstancesIds" :key="instanceId" :id="instanceId" :instanceId="instanceId"
:instanceInfo="instancesInfo[instanceId]" :studyMainDicomTags="this.studyMainDicomTags"
:seriesMainDicomTags="this.seriesMainDicomTags" :patientMainDicomTags="this.patientMainDicomTags"
@deletedInstance="onDeletedInstance" v-observe-visibility="{callback: visibilityChanged, once: true}">
</InstanceItem>
</table>
</template>

<style>
.instance-table {
}
.instance-table> :not(:first-child) {
border-top: 0px !important;
}
.instance-table>:first-child {
border-bottom: 2px !important;
border-style: solid !important;
border-color: black !important;
}
.instance-table>:nth-child(odd) >* >* {
background-color: var(--instance-odd-bg-color);
}
.instance-table>:nth-child(even) >* >* {
background-color: var(--instance-even-bg-color);
}
.instance-table td {
text-align: left;
padding-left: 10px;
}
.instance-table > tbody > tr:hover > * {
background-color: var(--instance-hover-color);
}
.instance-table > tbody > tr.instance-row-expanded:hover > * {
background-color: var(--instance-details-bg-color);
}
.instance-table > tbody > tr.instance-details-expanded:hover > * {
background-color: var(--instance-details-bg-color);
}
.instance-table-header {
text-align: left;
padding-left: 10px;
}
</style>
Loading

0 comments on commit ca67f21

Please sign in to comment.