5 Commits

Author SHA1 Message Date
mario
cf5cb2dfbf adjust scroll area expertise | deploy sudirman 2025-06-17 18:24:18 +07:00
mario
860a738734 ganti google.js ke proxy cloud-pacs 2025-05-02 16:27:56 +07:00
mario
f323b0b046 seversi dengan hangtuah 2025-04-30 08:10:22 +07:00
mario
ca84179aa6 fix: button expertise view tiap study 2025-04-15 03:11:46 +00:00
mario
841f84bfdb expertise host, rounding button, dan issue view per study 2025-04-14 11:24:36 +07:00
22 changed files with 88 additions and 592 deletions

2
.gitignore vendored
View File

@@ -59,3 +59,5 @@ tests/playwright-report/
# Dummy
/dump
platform/app/dist.zip
platform/app/dist-sudirman.zip

View File

@@ -1,104 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port

View File

@@ -1,11 +0,0 @@
{
"plugins": ["prettier-plugin-tailwindcss"],
"trailingComma": "es5",
"printWidth": 100,
"proseWrap": "always",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"arrowParens": "avoid",
"endOfLine": "auto"
}

View File

@@ -1,96 +0,0 @@
const path = require('path');
const pkg = require('../package.json');
const outputFile = 'index.umd.js';
const rootDir = path.resolve(__dirname, '../');
const outputFolder = path.join(__dirname, `../dist/umd/${pkg.name}/`);
// Todo: add ESM build for the extension in addition to umd build
const config = {
mode: 'production',
entry: rootDir + '/' + pkg.module,
devtool: 'source-map',
output: {
path: outputFolder,
filename: outputFile,
library: pkg.name,
libraryTarget: 'umd',
chunkFilename: '[name].chunk.js',
umdNamedDefine: true,
globalObject: "typeof self !== 'undefined' ? self : this",
},
externals: [
{
react: {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react',
},
'@ohif/core': {
commonjs2: '@ohif/core',
commonjs: '@ohif/core',
amd: '@ohif/core',
root: '@ohif/core',
},
'@ohif/ui': {
commonjs2: '@ohif/ui',
commonjs: '@ohif/ui',
amd: '@ohif/ui',
root: '@ohif/ui',
},
},
],
module: {
rules: [
{
test: /\.svg?$/,
oneOf: [
{
use: [
{
loader: '@svgr/webpack',
options: {
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false
},
},
},
]
},
prettier: false,
svgo: true,
titleProp: true,
},
},
],
issuer: {
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
},
},
],
},
{
test: /(\.jsx|\.js|\.tsx|\.ts)$/,
loader: 'babel-loader',
exclude: /(node_modules|bower_components)/,
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
],
},
resolve: {
modules: [path.resolve('./node_modules'), path.resolve('./src')],
extensions: ['.json', '.js', '.jsx', '.tsx', '.ts'],
},
};
module.exports = config;

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) 2025 expertise-panel ()
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,7 +0,0 @@
# expertise-panel
## Description
Expertisi Panel extension
## Author
## License
MIT

View File

@@ -1,50 +0,0 @@
module.exports = {
plugins: [
['@babel/plugin-proposal-class-properties', { loose: true }],
'@babel/plugin-transform-typescript',
['@babel/plugin-proposal-private-property-in-object', { loose: true }],
['@babel/plugin-proposal-private-methods', { loose: true }],
],
env: {
test: {
presets: [
[
// TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2
'@babel/preset-env',
{
modules: 'commonjs',
debug: false,
},
],
'@babel/preset-react',
'@babel/preset-typescript',
],
plugins: [
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-transform-regenerator',
'@babel/plugin-transform-runtime',
'@babel/plugin-transform-typescript',
],
},
production: {
presets: [
// WebPack handles ES6 --> Target Syntax
['@babel/preset-env', { modules: false }],
'@babel/preset-react',
'@babel/preset-typescript',
],
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
},
development: {
presets: [
// WebPack handles ES6 --> Target Syntax
['@babel/preset-env', { modules: false }],
'@babel/preset-react',
'@babel/preset-typescript',
],
plugins: ['react-refresh/babel'],
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
},
},
};

View File

@@ -1,75 +0,0 @@
{
"name": "expertise-panel",
"version": "0.0.1",
"description": "Expertisi Panel extension",
"author": "",
"license": "MIT",
"main": "dist/umd/expertise-panel/index.umd.js",
"files": [
"dist/**",
"public/**",
"README.md"
],
"repository": "OHIF/Viewers",
"keywords": [
"ohif-extension"
],
"module": "src/index.tsx",
"engines": {
"node": ">=14",
"npm": ">=6",
"yarn": ">=1.18.0"
},
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo",
"dev:my-extension": "yarn run dev",
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
"build:package": "yarn run build",
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "^3.9.1",
"@ohif/extension-default": "^3.9.1",
"@ohif/extension-cornerstone": "^3.9.1",
"@ohif/i18n": "^1.0.0",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^12.2.2",
"react-router": "^6.8.1",
"react-router-dom": "^6.8.1",
"webpack": "5.89.0",
"webpack-merge": "^5.7.3"
},
"dependencies": {
"@babel/runtime": "^7.20.13"
},
"devDependencies": {
"@babel/core": "7.24.7",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-proposal-object-rest-spread": "^7.17.3",
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-arrow-functions": "^7.16.7",
"@babel/plugin-transform-regenerator": "^7.16.7",
"@babel/plugin-transform-runtime": "7.24.7",
"@babel/plugin-transform-typescript": "^7.13.0",
"@babel/preset-env": "7.24.7",
"@babel/preset-react": "^7.16.7",
"@babel/preset-typescript": "^7.13.0",
"@babel/plugin-proposal-private-property-in-object": "7.21.11",
"babel-eslint": "9.x",
"babel-loader": "^8.2.4",
"@svgr/webpack": "^8.1.0",
"babel-plugin-module-resolver": "^5.0.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.0",
"cross-env": "^7.0.3",
"dotenv": "^14.1.0",
"eslint": "^8.39.0",
"eslint-loader": "^2.0.0",
"webpack": "5.89.0",
"webpack-merge": "^5.7.3",
"webpack-cli": "^5.0.2"
}
}

View File

@@ -1,7 +0,0 @@
import React from 'react';
function ExpertiseSidePanelComponent() {
return <div className="w-full text-center text-white">Hello Worlds</div>;
}
export default ExpertiseSidePanelComponent;

View File

@@ -1,5 +0,0 @@
import packageJson from '../package.json';
const id = packageJson.name;
export { id };

View File

@@ -1,97 +0,0 @@
import ExpertiseSidePanelComponent from './ExpertiseSidePanelComponent';
import { id } from './id';
/**
* You can remove any of the following modules if you don't need them.
*/
export default {
/**
* Only required property. Should be a unique value across all extensions.
* You ID can be anything you want, but it should be unique.
*/
id,
/**
* Perform any pre-registration tasks here. This is called before the extension
* is registered. Usually we run tasks such as: configuring the libraries
* (e.g. cornerstone, cornerstoneTools, ...) or registering any services that
* this extension is providing.
*/
preRegistration: ({ servicesManager, commandsManager, configuration = {} }) => {},
/**
* PanelModule should provide a list of panels that will be available in OHIF
* for Modes to consume and render. Each panel is defined by a {name,
* iconName, iconLabel, label, component} object. Example of a panel module
* is the StudyBrowserPanel that is provided by the default extension in OHIF.
*/
getPanelModule: ({ servicesManager, commandsManager, extensionManager }) => {
return [
{
name: 'ExpertiseSidePanel',
iconName: 'logo-ohif-small',
iconLabel: 'Expertise Side Panel',
label: 'Expertise Side Panel Label',
component: ExpertiseSidePanelComponent,
},
];
},
/**
* ViewportModule should provide a list of viewports that will be available in OHIF
* for Modes to consume and use in the viewports. Each viewport is defined by
* {name, component} object. Example of a viewport module is the CornerstoneViewport
* that is provided by the Cornerstone extension in OHIF.
*/
getViewportModule: ({ servicesManager, commandsManager, extensionManager }) => {},
/**
* ToolbarModule should provide a list of tool buttons that will be available in OHIF
* for Modes to consume and use in the toolbar. Each tool button is defined by
* {name, defaultComponent, clickHandler }. Examples include radioGroupIcons and
* splitButton toolButton that the default extension is providing.
*/
getToolbarModule: ({ servicesManager, commandsManager, extensionManager }) => {},
/**
* LayoutTemplateMOdule should provide a list of layout templates that will be
* available in OHIF for Modes to consume and use to layout the viewer.
* Each layout template is defined by a { name, id, component}. Examples include
* the default layout template provided by the default extension which renders
* a Header, left and right sidebars, and a viewport section in the middle
* of the viewer.
*/
getLayoutTemplateModule: ({ servicesManager, commandsManager, extensionManager }) => {},
/**
* SopClassHandlerModule should provide a list of sop class handlers that will be
* available in OHIF for Modes to consume and use to create displaySets from Series.
* Each sop class handler is defined by a { name, sopClassUids, getDisplaySetsFromSeries}.
* Examples include the default sop class handler provided by the default extension
*/
getSopClassHandlerModule: ({ servicesManager, commandsManager, extensionManager }) => {},
/**
* HangingProtocolModule should provide a list of hanging protocols that will be
* available in OHIF for Modes to use to decide on the structure of the viewports
* and also the series that hung in the viewports. Each hanging protocol is defined by
* { name, protocols}. Examples include the default hanging protocol provided by
* the default extension that shows 2x2 viewports.
*/
getHangingProtocolModule: ({ servicesManager, commandsManager, extensionManager }) => {},
/**
* CommandsModule should provide a list of commands that will be available in OHIF
* for Modes to consume and use in the viewports. Each command is defined by
* an object of { actions, definitions, defaultContext } where actions is an
* object of functions, definitions is an object of available commands, their
* options, and defaultContext is the default context for the command to run against.
*/
getCommandsModule: ({ servicesManager, commandsManager, extensionManager }) => {},
/**
* ContextModule should provide a list of context that will be available in OHIF
* and will be provided to the Modes. A context is a state that is shared OHIF.
* Context is defined by an object of { name, context, provider }. Examples include
* the measurementTracking context provided by the measurementTracking extension.
*/
getContextModule: ({ servicesManager, commandsManager, extensionManager }) => {},
/**
* DataSourceModule should provide a list of data sources to be used in OHIF.
* DataSources can be used to map the external data formats to the OHIF's
* native format. DataSources are defined by an object of { name, type, createDataSource }.
*/
getDataSourcesModule: ({ servicesManager, commandsManager, extensionManager }) => {},
};

View File

@@ -26,6 +26,8 @@ const SidePanelWithServices = ({
const [sidePanelOpen, setSidePanelOpen] = useState(activeTabIndexProp !== null);
const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp);
const [tabs, setTabs] = useState(tabsProp ?? panelService.getPanels(side));
const [studyInstanceUID, setStudyInstanceUID] = useState('');
const [lastActivatedStudyUID, setLastActivatedStudyUID] = useState('');
const handleActiveTabIndexChange = useCallback(({ activeTabIndex }) => {
setActiveTabIndex(activeTabIndex);
@@ -71,23 +73,33 @@ const SidePanelWithServices = ({
const activatePanelSubscription = panelService.subscribe(
panelService.EVENTS.ACTIVATE_PANEL,
(activatePanelEvent: Types.ActivatePanelEvent) => {
// Handle the `-exp` suffix logic
const isExpertisePanel = activatePanelEvent.panelId.endsWith('-exp');
const isExpertisePanel = activatePanelEvent.panelId.includes('-exp-');
const realPanelID = isExpertisePanel
? activatePanelEvent.panelId.replace(/-exp$/, '')
? activatePanelEvent.panelId.split('-exp-')[0]
: activatePanelEvent.panelId;
// studyInstanceUID = take from activatePanelEvent.panelId after '-exp-
setStudyInstanceUID(isExpertisePanel ? activatePanelEvent.panelId.split('-exp-')[1] : null);
const tabIndex = tabs.findIndex(tab => tab.id === realPanelID);
if (isExpertisePanel && side === 'right') {
const shouldOpen = !sidePanelOpen; // Use sidePanelOpen to determine toggle state
setSidePanelOpen(shouldOpen);
// Extract study UID from the panel ID
const currentStudyUID = activatePanelEvent.panelId.split('-exp-')[1];
if (shouldOpen) {
setActiveTabIndex(tabIndex !== -1 ? tabIndex : null);
} else {
setActiveTabIndex(null);
}
// Toggle logic - close if same study is clicked again, open if different study
if (currentStudyUID === lastActivatedStudyUID && sidePanelOpen) {
// Same study - close panel
setSidePanelOpen(false);
setActiveTabIndex(null);
setLastActivatedStudyUID('');
} else {
// Different study or panel was closed - open panel with new study
setSidePanelOpen(true);
setActiveTabIndex(tabIndex !== -1 ? tabIndex : null);
setStudyInstanceUID(currentStudyUID);
setLastActivatedStudyUID(currentStudyUID);
}
} else if (tabIndex !== -1) {
setActiveTabIndex(tabIndex);
}
@@ -97,7 +109,7 @@ const SidePanelWithServices = ({
return () => {
activatePanelSubscription.unsubscribe();
};
}, [tabs, sidePanelOpen, panelService]);
}, [tabs, sidePanelOpen, panelService, lastActivatedStudyUID]);
return (
<SidePanel
@@ -110,6 +122,7 @@ const SidePanelWithServices = ({
onActiveTabIndexChange={handleActiveTabIndexChange}
expandedWidth={expandedWidth}
servicesManager={servicesManager} // Pass servicesManager ke SidePanel
studyInstanceUID={studyInstanceUID}
/>
);
};

View File

@@ -146,6 +146,7 @@ function ViewerLayout({
side="right"
activeTabIndex={rightPanelClosedState ? null : 0}
servicesManager={servicesManager}
expandedWidth={400}
/>
) : null}
</React.Fragment>

View File

@@ -42,8 +42,7 @@
"@ohif/extension-default": "3.9.1",
"@ohif/extension-dicom-pdf": "3.9.1",
"@ohif/extension-dicom-video": "3.9.1",
"@ohif/extension-measurement-tracking": "3.9.1",
"expertise-panel": "^0.0.1"
"@ohif/extension-measurement-tracking": "3.9.1"
},
"dependencies": {
"@babel/runtime": "^7.20.13",

View File

@@ -72,7 +72,6 @@ const extensionDependencies = {
'@ohif/extension-cornerstone-dicom-rt': '^3.0.0',
'@ohif/extension-dicom-pdf': '^3.0.1',
'@ohif/extension-dicom-video': '^3.0.1',
'expertise-panel': '^0.0.1',
};
function modeFactory({ modeConfiguration }) {
@@ -182,7 +181,7 @@ function modeFactory({ modeConfiguration }) {
return {
id: ohif.layout,
props: {
leftPanels: [tracked.thumbnailList, 'expertise-panel.panelModule.ExpertiseSidePanel'],
leftPanels: [tracked.thumbnailList],
rightPanels: [cornerstone.segmentation, tracked.measurements],
rightPanelClosed: true,
viewports: [

View File

@@ -78,10 +78,6 @@ module.exports = (env, argv) => {
// Hoisted Yarn Workspace Modules
path.resolve(__dirname, '../../../node_modules'),
SRC_DIR,
path.resolve(
__dirname,
'/home/mario/Works/ohif-viewer/expertise-panel-ext/expertise-panel/node_modules'
),
],
},
plugins: [

View File

@@ -61,10 +61,6 @@
"packageName": "@ohif/extension-cornerstone-dicom-rt",
"default": false,
"version": "3.0.0"
},
{
"packageName": "expertise-panel",
"version": "0.0.1"
}
],
"modes": [

View File

@@ -5,6 +5,7 @@ window.config = {
// whiteLabeling: {},
extensions: [],
modes: [],
customizationService: {},
showStudyList: true,
// some windows systems have issues with more than 3 web workers
maxNumberOfWebWorkers: 3,
@@ -23,21 +24,10 @@ window.config = {
prefetch: 25,
},
expertise: false, //* Tambahan untuk enable expertise (CustomizableViewportOverlay)
expertise_host: 'http://128.199.154.150', //* Tambahan untuk fetch data Expertise)
// filterQueryParam: false,
// defaultDataSourceName: 'dicomweb',
expertise_host: `http://192.168.2.13`, // IP ke NV di PACS Server, untuk fetch expertise bawaan versi NV
pacs_document_host: `192.168.2.13`, // IP ke NV di PACS Server untuk ambil pdf
pacs_document_port: 8080,
defaultDataSourceName: 'local-proxy',
/* Dynamic config allows user to pass "configUrl" query string this allows to load config without recompiling application. The regex will ensure valid configuration source */
// dangerouslyUseDynamicConfig: {
// enabled: true,
// // regex will ensure valid configuration source and default is /.*/ which matches any character. To use this, setup your own regex to choose a specific source of configuration only.
// // Example 1, to allow numbers and letters in an absolute or sub-path only.
// // regex: /(0-9A-Za-z.]+)(\/[0-9A-Za-z.]+)*/
// // Example 2, to restricts to either hosptial.com or othersite.com.
// // regex: /(https:\/\/hospital.com(\/[0-9A-Za-z.]+)*)|(https:\/\/othersite.com(\/[0-9A-Za-z.]+)*)/
// regex: /.*/,
// },
dataSources: [
{
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
@@ -45,9 +35,8 @@ window.config = {
configuration: {
friendlyName: 'Static WADO Local Data',
name: 'DCM4CHEE',
qidoRoot: 'http://128.199.154.150:5000/rs',
wadoRoot: 'http://128.199.154.150:5000/rs',
qidoSupportsIncludeField: false,
qidoRoot: `http://192.168.2.13:5000/rs`, // IP ke dicomweb-proxy PACS Server. URI selalu /rs
wadoRoot: `http://192.168.2.13:5000/rs`, // IP ke dicomweb-proxy PACS Server. URI selalu /rs qidoSupportsIncludeField: false,
supportsReject: true,
supportsStow: true,
imageRendering: 'wadors',
@@ -63,6 +52,7 @@ window.config = {
},
},
},
{
namespace: '@ohif/extension-default.dataSourcesModule.dicomwebproxy',
sourceName: 'dicomwebproxy',

View File

@@ -1,6 +1,9 @@
/** @type {AppTypes.Config} */
window.config = {
routerBasename: '/',
pacs_document_host: '152.42.173.210',
pacs_document_port: 8080,
expertise: false,
enableGoogleCloudAdapter: false,
// below flag is for performance reasons, but it might not work for all servers
showWarningMessageForCrossOrigin: true,
@@ -8,37 +11,24 @@ window.config = {
showLoadingIndicator: true,
strictZSpacingForVolumeViewport: true,
// This is an array, but we'll only use the first entry for now
oidc: [
{
// ~ REQUIRED
// Authorization Server URL
authority: 'https://accounts.google.com',
client_id: '382212153306-7q39hdie4ecj0uhemkitvedo93bnvfhn.apps.googleusercontent.com',
redirect_uri: '/callback',
response_type: 'id_token token',
scope:
'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid
// ~ OPTIONAL
post_logout_redirect_uri: '/logout-redirect.html',
revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=',
automaticSilentRenew: true,
revokeAccessTokenOnSignout: true,
// Tambahan dari Google CLoud Secret
project_id: "westone-433204",
auth_uri: "https://accounts.google.com/o/oauth2/auth",
token_uri: "https://oauth2.googleapis.com/token",
auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs",
client_secret: "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",
redirect_uris: [
"http://devkedungdoro.aplikasi.web.id:3000/callback"
],
javascript_origins: [
"https://devone.aplikasi.web.id",
"http://devkedungdoro.aplikasi.web.id:3000"
]
},
],
// Remove OIDC configuration since proxy handles authentication
// oidc: [
// {
// // ~ REQUIRED
// // Authorization Server URL
// authority: 'https://accounts.google.com',
// client_id: '382212153306-7q39hdie4ecj0uhemkitvedo93bnvfhn.apps.googleusercontent.com',
// redirect_uri: '/callback',
// response_type: 'id_token token',
// scope:
// 'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid
// // ~ OPTIONAL
// post_logout_redirect_uri: '/logout-redirect.html',
// revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=',
// automaticSilentRenew: true,
// revokeAccessTokenOnSignout: true,
// },
// ],
extensions: [],
modes: [],
showStudyList: true,
@@ -51,12 +41,9 @@ window.config = {
configuration: {
friendlyName: 'dcmjs DICOMWeb Server',
name: 'GCP',
wadoUriRoot:
'https://healthcare.googleapis.com/v1/projects/westone-433204/locations/asia-southeast2/datasets/sas-dicom-storage/dicomStores/ohif/dicomWeb',
qidoRoot:
'https://healthcare.googleapis.com/v1/projects/westone-433204/locations/asia-southeast2/datasets/sas-dicom-storage/dicomStores/ohif/dicomWeb',
wadoRoot:
'https://healthcare.googleapis.com/v1/projects/westone-433204/locations/asia-southeast2/datasets/sas-dicom-storage/dicomStores/ohif/dicomWeb',
wadoUriRoot: `http://152.42.173.210:5555/dicomWeb`,
qidoRoot: `http://152.42.173.210:5555/dicomWeb`,
wadoRoot: `http://152.42.173.210:5555/dicomWeb`,
qidoSupportsIncludeField: true,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
@@ -66,12 +53,12 @@ window.config = {
dicomUploadEnabled: true,
omitQuotationForMultipartRequest: true,
configurationAPI: 'ohif.dataSourceConfigurationAPI.google',
defaultDicomStoreConfiguredItems: {
id: 'projects/westone-433204/locations/asia-southeast2/datasets/sas-dicom-storage',
itemType: '3',
name: 'ohif',
url: 'https://healthcare.googleapis.com/v1/projects/westone-433204/locations/asia-southeast2/datasets/sas-dicom-storage/dicomStores/ohif'
},
// defaultDicomStoreConfiguredItems: {
// id: 'projects/ohifproxy/locations/asia-southeast2/datasets/sas-storage',
// itemType: '3',
// name: 'store-1',
// url: 'https://healthcare.googleapis.com/v1/projects/ohifproxy/locations/asia-southeast2/datasets/sas-storage/dicomStores/store-1'
// },
},
},
{

View File

@@ -154,6 +154,7 @@ const SidePanel = ({
expandedWidth = 280,
onActiveTabIndexChange,
servicesManager, // Tambah servicesManager as a prop
studyInstanceUID,
}) => {
const [panelOpen, setPanelOpen] = useState(activeTabIndexProp !== null);
const [activeTabIndex, setActiveTabIndex] = useState(0);
@@ -166,8 +167,8 @@ const SidePanel = ({
const [viewportData, setViewportData] = useState(null);
// Harusnya (viewportId), tapi karena gabutuh perubahan viewport maka dihardcode 'default'
const viewportInfo = cornerstoneViewportService.getViewportInfo('default');
const studyInstanceUID = viewportInfo?.viewportData?.data?.[0]?.StudyInstanceUID || '';
// const viewportInfo = cornerstoneViewportService.getViewportInfo('default');
// const studyInstanceUID = viewportInfo?.viewportData?.data?.[0]?.StudyInstanceUID || '';
const styleMap = createStyleMap(expandedWidth, borderSize, collapsedWidth);
const baseStyle = createBaseStyle(expandedWidth);
@@ -359,46 +360,7 @@ const SidePanel = ({
const response = await fetch(url);
const data = await response.json();
// console.log('Study data:', data);
const data = {
study: {
accession_no: 'CR.250411.001',
study_iuid: '1.2.826.0.1.3680043.9.7307.1.20250411001',
study_description: '',
study_datetime: '20250411093937',
number_of_series: '1',
number_of_instances: '1',
modality: 'CR',
patient_mrn: '00000941',
patient_name: 'NEFANNY RIDWAN',
patient_sex: 'F',
patient_date_of_birth: '19881127',
patient_age: '36Y 4M 14D',
expertise: [
{
expertise:
'Keterangan : MCU\r\n\r\nRadiografi Thorax PA (inspirasi kurang)\r\n\r\nCor : besar dan bentuk normal\r\nPulmo : tak tampak infiltrat\r\nTrachea tampak di tengah\r\nSinus phrenicocostalis kanan kiri tajam\r\nHemidiafragma kanan kiri tampak baik\r\nTulang-tulang tampak baik\r\nSoft tissue tak tampak kelainan\r\n\r\nKesan :\r\nTidak tampak kelainan signifikan pada pemeriksaan saat ini\r\n\r\nBTK,',
radiologist: 'dr. Hendra Boy Situmorang, Sp.Rad ',
expertise_dttm: '2025-04-11 09:43',
radiologist_edit: null,
expertise_edit_dttm: '0000-00-00 00:00',
ordering_physician: 'dr. Laksmitasari Dewi',
},
],
series: [
{
series_number: '1',
series_iuid: '1.2.156.112536.2.560.28134011043131122.1519098341436.1',
series_description: 'V04_0014',
number_of_instances: 1,
thumbnail:
'http://192.168.22.3/nv/wado_proxy_thumb.php?requestType=WADO&studyUID=1.2.826.0.1.3680043.9.7307.1.20250411001&seriesUID=1.2.156.112536.2.560.28134011043131122.1519098341436.1&objectUID=1.2.156.112536.2.560.28134011043131122.1519098341436.4&rows=123',
sop_iuids: ['1.2.156.112536.2.560.28134011043131122.1519098341436.4'],
},
],
},
};
console.log('Study data:', data);
if (data?.study?.expertise && data.study.expertise.length > 0) {
setExpertiseData(data.study.expertise[0]);
@@ -479,7 +441,7 @@ const SidePanel = ({
});
return (
<ScrollArea className="border-input bg-background h-[500px] w-[350px] rounded-md border p-2 text-sm text-white">
<ScrollArea className="border-input bg-background h-[500px] w-full rounded-md border p-2 text-sm text-white">
<h3 className="mb-4 text-lg font-bold">Expertise</h3>
{formattedData.map((section, index) => (
<div
@@ -490,11 +452,16 @@ const SidePanel = ({
{Array.isArray(section.value) ? (
<ul className="list-disc pl-6">
{section.value.map((item, idx) => (
<li key={idx}>{item}</li>
<li
key={idx}
className="break-words"
>
{item}
</li>
))}
</ul>
) : (
<p>{section.value}</p>
<p className="break-words">{section.value}</p>
)}
</div>
))}
@@ -662,6 +629,7 @@ SidePanel.propTypes = {
onActiveTabIndexChange: PropTypes.func,
expandedWidth: PropTypes.number,
servicesManager: PropTypes.object.isRequired, // Tambah servicesManager prop
studyInstanceUID: PropTypes.string, // Tambahkan prop studyInstanceUID
};
export { SidePanel };

View File

@@ -63,6 +63,7 @@ const StudyBrowser = ({
viewPreset={viewPreset}
onThumbnailContextMenu={onThumbnailContextMenu}
servicesManager={servicesManager} // Pass servicesManager ke Study Item
studyInstanceUid={studyInstanceUid}
/>
</React.Fragment>
);

View File

@@ -21,7 +21,10 @@ const StudyItem = ({
viewPreset = 'thumbnails',
onThumbnailContextMenu,
servicesManager, // Tambah servicesManager as a prop
studyInstanceUid = '',
}: withAppTypes) => {
// FETCHING ACCESSION NUMBER DAN EXPERTISE
return (
<Accordion
type="single"
@@ -59,16 +62,17 @@ const StudyItem = ({
<>
{/* Expertise Button */}
<div
className="bg-primary-dark hover:bg-primary-active my-4 w-full cursor-pointer border border-white py-2 text-center text-white"
className="bg-primary-dark hover:bg-primary-active mx-8 my-4 cursor-pointer rounded-lg border border-white py-3 text-center text-white"
onClick={() => {
// Trigger the expertise panel in the right side panel (segmentation Panel)
servicesManager.services.panelService.activatePanel(
'@ohif/extension-cornerstone.panelModule.panelSegmentation-exp',
// '@ohif/extension-cornerstone.panelModule.panelSegmentation-exp',
`@ohif/extension-cornerstone.panelModule.panelSegmentation-exp-${studyInstanceUid}`,
true
);
}}
>
View Expertise
Expertise
</div>
{/* Thumbnails */}
@@ -105,6 +109,7 @@ StudyItem.propTypes = {
onClickUntrack: PropTypes.func,
viewPreset: PropTypes.string,
servicesManager: PropTypes.object.isRequired, // Tambah servicesManager prop
studyInstanceUid: PropTypes.string.string,
};
export { StudyItem };