Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

GUI HCS Viewer: add 3d-mode options #3774

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/public/hcs-image-viewer/hcs-image-inject.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion client/public/hcs-image-viewer/hcs-image.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2017-2024 EPAM Systems, Inc. (https://www.epam.com/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

.modal-overlay {
padding-top: 0px;
}
.modal-overlay div[class~="ant-popover-arrow"] {
display: none;
}

.volumetric-button {
line-height: 1;
display: inline-flex;
margin: 0 5px;
}

.overlay-container {
display: flex;
flex-direction: column;
gap: 8px;
}

.selector-wrapper {
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 5px;
width: 100%;
}

.slice-wrapper {
display: flex;
flex-direction: row;
gap: 5px;
}

.title {
font-weight: bold;
}

.content {
flex: 1;
overflow: auto;
}
290 changes: 290 additions & 0 deletions client/src/components/special/hcs-image/hcs-3d-button/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
/*
* Copyright 2017-2024 EPAM Systems, Inc. (https://www.epam.com/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import {inject, observer} from 'mobx-react';
import {computed, isObservableArray} from 'mobx';
import {Slider, Select, Checkbox, Popover, Button, Icon} from 'antd';
import displaySize from '../../../../utils/displaySize';
import styles from './hcs-3d-button.css';
import classNames from "classnames";


function getSliceRangeSafe(range) {
if (!range || typeof range !== 'object' || !(Array.isArray(range) || isObservableArray(range))) {
return [0, 100];
}
const [min = 0, max = 100] = range;
if (min >= max) {
return [0, 100];
}
return [min, max];
}

function getSliceEnabled(range) {
if (!range || typeof range !== 'object' || !(Array.isArray(range) || isObservableArray(range)) || range.length !== 2) {
return false;
}
const [min = 0, max = 100] = range;
return min < max;
}

@inject('hcsViewerState')
@observer
export default class HCS3DButton extends React.Component {
state = {
modalVisible: false
};

@computed
get use3dMode () {
const {hcsViewerState} = this.props;
return hcsViewerState?.use3D;
}

@computed
get downsamplingMode () {
const {hcsViewerState} = this.props;
const {downsamplingMode} = hcsViewerState ?? {};
return downsamplingMode === undefined ? undefined : `${downsamplingMode}`;
}

@computed
get renderingMode () {
const {hcsViewerState} = this.props;
const {renderingMode} = hcsViewerState ?? {};
return renderingMode === undefined ? undefined : `${renderingMode}`;
}

@computed
get xSlice () {
const {hcsViewerState} = this.props;
return hcsViewerState?.xSlice || [];
}

@computed
get xSliceRange () {
const {hcsViewerState} = this.props;
return getSliceRangeSafe(hcsViewerState?.xSliceRange)
}

@computed
get xSliceEnabled () {
const {hcsViewerState} = this.props;
return getSliceEnabled(hcsViewerState?.xSlice);
}

@computed
get ySlice () {
const {hcsViewerState} = this.props;
return hcsViewerState?.ySlice || [];
}

@computed
get ySliceRange () {
const {hcsViewerState} = this.props;
return getSliceRangeSafe(hcsViewerState?.ySliceRange)
}

@computed
get ySliceEnabled () {
const {hcsViewerState} = this.props;
return getSliceEnabled(hcsViewerState?.ySlice);
}

@computed
get zSlice () {
const {hcsViewerState} = this.props;
return hcsViewerState?.zSlice || [];
}

@computed
get zSliceRange () {
const {hcsViewerState} = this.props;
return getSliceRangeSafe(hcsViewerState?.zSliceRange)
}

@computed
get zSliceEnabled () {
const {hcsViewerState} = this.props;
return getSliceEnabled(hcsViewerState?.zSlice);
}

onChangeDownsampleMode = (key) => {
const {hcsViewerState} = this.props;
if (hcsViewerState?.changeDownsamplingMode) {
hcsViewerState.changeDownsamplingMode(Number.isNaN(Number(key)) ? undefined : Number(key));
}
};

onChangeRenderingMode = (key) => {
const {hcsViewerState} = this.props;
if (hcsViewerState?.changeRenderingMode) {
hcsViewerState.changeRenderingMode(Number.isNaN(Number(key)) ? undefined : Number(key));
}
};

toggle3DMode = () => {
const {hcsViewerState} = this.props;
if (hcsViewerState?.change3dMode) {
hcsViewerState.change3dMode(!this.use3dMode);
}
};

visibilityChanged = visible => visible ? this.openModal() : this.closeModal();
openModal = () => this.setState({modalVisible: true});
closeModal = () => this.setState({modalVisible: false});

renderDropdownContent = () => {
const {hcsViewerState} = this.props;
const downsamplingModes = hcsViewerState?.downsamplingModes || [];
const renderingModes = hcsViewerState?.renderingModes || [];
const sliceControls = [{
min: this.xSliceRange[0],
max: this.xSliceRange[1],
title: 'X slice',
onChange: hcsViewerState?.changeXSlice,
enabled: this.xSliceEnabled && this.use3dMode,
value: [...this.xSlice]
}, {
min: this.ySliceRange[0],
max: this.ySliceRange[1],
title: 'Y slice',
onChange: hcsViewerState?.changeYSlice,
enabled: this.ySliceEnabled && this.use3dMode,
value: [...this.ySlice]
}, {
min: this.zSliceRange[0],
max: this.zSliceRange[1],
title: 'Z slice',
onChange: hcsViewerState?.changeZSlice,
enabled: this.zSliceEnabled && this.use3dMode,
value: [...this.zSlice]
}];
const TitleWrapper = ({title, children}) => (
<div className={styles.selectorWrapper}>
<span className={styles.title} style={{minWidth: 130}}>{title}</span>
<div className={styles.content}>{children}</div>
</div>
);
return (
<div className={styles.overlayContainer}>
<b>Volume rendering settings:</b>
<Checkbox
checked={this.use3dMode}
onChange={this.toggle3DMode}
className={styles.title}
>
Use volume renderer
</Checkbox>
<TitleWrapper title="Downsampling mode:">
<Select
style={{width: '100%'}}
value={this.downsamplingMode}
onChange={this.onChangeDownsampleMode}
getPopupContainer={triggerNode => triggerNode.parentNode}
>
{downsamplingModes.map(mode => (
<Select.Option key={mode.id} value={`${mode.id}`}>
{`${mode.name} (${displaySize(mode.bytes)} per channel)`}
</Select.Option>
))}
</Select>
</TitleWrapper>
<TitleWrapper title="Rendering mode:">
<Select
value={this.renderingMode}
onChange={this.onChangeRenderingMode}
style={{width: '100%'}}
getPopupContainer={triggerNode => triggerNode.parentNode}
>
{renderingModes.map(mode => (
<Select.Option key={mode.id} value={`${mode.id}`}>
{mode.name}
</Select.Option>
))}
</Select>
</TitleWrapper>
{sliceControls.map(({min, max, title, value, onChange, enabled}) => (
<div key={title} className={styles.sliceWrapper}>
<span>{title}</span>
<Slider
style={{margin: '2px 6px', flex: 1}}
disabled={!enabled}
value={value}
min={min}
max={max}
onChange={onChange}
range
/>
</div>
))}
</div>
);
};

render () {
const {size, className} = this.props;
const {modalVisible} = this.state;
return (
<div className={classNames(className, styles.volumetricButton)}>
<Button
size={size}
onClick={this.toggle3DMode}
type={this.use3dMode ? 'primary' : 'default'}
style={{
borderBottomRightRadius: '0px',
borderTopRightRadius: '0px',
borderBottomLeftRadius: '4px',
borderTopLeftRadius: '4px'
}}
>
3D
</Button>
<Popover
getPopupContainer={triggerNode => triggerNode.parentNode}
onVisibleChange={this.visibilityChanged}
visible={modalVisible}
trigger="click"
title={false}
content={this.renderDropdownContent()}
placement="bottom"
overlayStyle={{
width: '35vw',
minWidth: 350
}}
overlayClassName={styles.modalOverlay}
maskClosable={false}
>
<Button size={size} style={{
borderBottomRightRadius: '4px',
borderTopRightRadius: '4px',
borderBottomLeftRadius: '0px',
borderTopLeftRadius: '0px'
}}>
<Icon type="down" />
</Button>
</Popover>
</div>
);
}
}

HCS3DButton.PropTypes = {
size: PropTypes.string,
viewer: PropTypes.object
};
Loading
Loading