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

Mirroring support #439

Merged
merged 16 commits into from
Jun 6, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ You must define a palette and each row can display 4 colors::

$ omero config set omero.web.iviewer.show_palette_only true

When working with other images (coregistering MRIs for example), it is necessary to be able to mirror an image.
There is now experimental support for runtime image mirroring. To enable mirroring set enable_mirror to true.

$ omero config set omero.web.iviewer.enable__mirror true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a double underscore in enable__mirror which I think is why my config didn't work before.


Known issues
============

Expand Down
17 changes: 17 additions & 0 deletions css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -1527,3 +1527,20 @@ thumbnail-slider img {
max-width: 125px;
padding: 10px;
}

/* Mirror Buttons */
.ol-flip {
top: 3em;
left: .5em;
}

.glyphicon.ol-flip-button {
font-size: 15px;
background-color: #fff;
font-size: 13px;
top: 0px;
height: 30px;
min-width: 30px;
padding: 0px;
font-weight: bold;
}
8 changes: 7 additions & 1 deletion plugin/omero_iviewer/iviewer_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,14 @@
["SHOW_PALETTE_ONLY",
False,
bool,
("Disables spectrum color picker. Forces users to use preset options"
("Disables spectrum color picker. Forces users to use preset options."
"Must define a color palette for this setting to work.")],

"omero.web.iviewer.enable_mirror":
["ENABLE_MIRROR",
False,
bool,
("Enables buttons to mirror X or Y axis.")]
}

process_custom_settings(sys.modules[__name__], 'IVIEWER_SETTINGS_MAPPING')
Expand Down
2 changes: 2 additions & 0 deletions plugin/omero_iviewer/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
MAX_PROJECTION_BYTES = getattr(iviewer_settings, 'MAX_PROJECTION_BYTES')
ROI_COLOR_PALETTE = getattr(iviewer_settings, 'ROI_COLOR_PALETTE')
SHOW_PALETTE_ONLY = getattr(iviewer_settings, 'SHOW_PALETTE_ONLY')
ENABLE_MIRROR = getattr(iviewer_settings, 'ENABLE_MIRROR')

PROJECTIONS = {
'normal': -1,
Expand Down Expand Up @@ -108,6 +109,7 @@ def index(request, iid=None, conn=None, **kwargs):
params['MAX_PROJECTION_BYTES'] = max_bytes
params['ROI_COLOR_PALETTE'] = ROI_COLOR_PALETTE
params['SHOW_PALETTE_ONLY'] = SHOW_PALETTE_ONLY
params['ENABLE_MIRROR'] = ENABLE_MIRROR

return render(
request, 'omero_iviewer/index.html',
Expand Down
1 change: 1 addition & 0 deletions src/app/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ export default class Context {
}
}
this.show_palette_only = (this.initParams[REQUEST_PARAMS.SHOW_PALETTE_ONLY] != 'False') || false
this.enable_mirror = (this.initParams[REQUEST_PARAMS.ENABLE_MIRROR] != 'False') || false
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/index-dev.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@
'WEB_API_BASE': 'api/v0/',
'ROI_PAGE_SIZE': '500',
'MAX_PROJECTION_BYTES': "268435456", // 1024 * 1024 * 256
'IMAGES': "1", // "4420" // "73537",
// 'IMAGES': "1" // "4420" // "73537",
//'ROI_COLOR_PALETTE': '["rgb(0,255,0)","blue","blue","pink","blue"],["rgb(255,255,255)"]',
'ENABLE_MIRROR': 'True',
//'SHOW_PALETTE_ONLY': true,
// 'DATASET': "1",
'DATASET': "1",
//'WELL': "1"
};
// check if we need to log in
Expand Down
1 change: 1 addition & 0 deletions src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export const REQUEST_PARAMS = {
MAX_PROJECTION_BYTES: 'MAX_PROJECTION_BYTES',
ROI_COLOR_PALETTE: 'ROI_COLOR_PALETTE',
SHOW_PALETTE_ONLY: 'SHOW_PALETTE_ONLY',
ENABLE_MIRROR: 'ENABLE_MIRROR',
}

/**
Expand Down
7 changes: 5 additions & 2 deletions src/viewers/ol3-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
ENABLE_SHAPE_POPUP,
EventSubscriber
} from '../events/events';
import Mirror from './viewer/controls/Mirror';


/**
Expand Down Expand Up @@ -480,14 +481,16 @@ export default class Ol3Viewer extends EventSubscriber {
container: this.container
});
delete this.image_config.image_info.tmp_data;

// hide controls for mdi when more than 1 image configs
if (this.context.useMDI && this.context.image_configs.size > 1)
this.toggleControlsVisibility({
config_id: this.image_config.id, flag: false
});
// only the first request should be affected
// only the first request should be affected, besides mirror controls
let mirrorEnabled = this.context.getInitialRequestParam(REQUEST_PARAMS.ENABLE_MIRROR)
this.context.resetInitParams();
this.context.initParams[REQUEST_PARAMS.ENABLE_MIRROR] = mirrorEnabled
// use existing interpolation setting
this.viewer.enableSmoothing(this.context.interpolate);
// zoom to fit
Expand Down
15 changes: 14 additions & 1 deletion src/viewers/viewer/Viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import {AVAILABLE_VIEWER_INTERACTIONS,
AVAILABLE_VIEWER_CONTROLS,
WEBGATEWAY,
PLUGIN_PREFIX,
REQUEST_PARAMS,
DEFAULT_TILE_DIMS,
REGIONS_MODE,
REGIONS_STATE,
Expand All @@ -67,6 +66,8 @@ import {integrateStyleIntoJsonObject,
import OmeroImage from './source/Image';
import Regions from './source/Regions';
import Mask from './geom/Mask';
import Mirror from './controls/Mirror';
import { REQUEST_PARAMS } from '../../utils/constants';

/**
* @classdesc
Expand Down Expand Up @@ -580,6 +581,7 @@ class Viewer extends OlObject {
controls.push(defaultConts[contr]['ref']);
this.viewerState_[contr] = defaultConts[contr];
}


// finally construct the open layers map object
this.viewer_ = new OlMap({
Expand All @@ -599,6 +601,12 @@ class Viewer extends OlObject {
'collapsed': !source.use_tiled_retrieval_
};
this.addControl('birdseye', birdsEyeOptions);

// add mirror if requested
if(this.getInitialRequestParam(REQUEST_PARAMS.ENABLE_MIRROR) == 'True'){
this.addControl('mirror')
}

// tweak source element for fullscreen to include dim sliders (iviewer only)
var targetId = this.getTargetId();
var viewerFrame = targetId ? document.getElementById(targetId) : null;
Expand Down Expand Up @@ -670,6 +678,11 @@ class Viewer extends OlObject {
notifyAboutViewerInteraction(this);
}, this);
}

// add mirror if requested
if(this.getInitialRequestParam(REQUEST_PARAMS.ENABLE_MIRROR) == 'True'){
this.viewer_.addControl(new Mirror(this.viewer_))
}
}

/**
Expand Down
134 changes: 134 additions & 0 deletions src/viewers/viewer/controls/Mirror.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import {listen} from 'ol/events';
import EventType from 'ol/events/EventType';
import Control from 'ol/control/Control';
import {CLASS_UNSELECTABLE, CLASS_CONTROL } from 'ol/css';

export class Mirror extends Control {
/**
* @constructor
* @param {ol.Map=} map openlayers map.
* @param {ol.control.FlipOptions=} opt_options options. (className, target)
*/
constructor(opt_options) {
var options = opt_options ? opt_options : {};

var element = document.createElement('div');
super({
element: element,
target: options.target
});

/**
* @type {string}
* @private
*/
this.class_name_ =
options.className === 'string' ? options.className : 'ol-flip';

/**
* @type {MapBrowserPointerEvent}
* @private
*/
this.ref_ = null

/**
* @type {View}
*/
this.view = null

var cssClasses =
this.class_name_ + ' ' + CLASS_UNSELECTABLE + ' ' +
CLASS_CONTROL;

element.className = cssClasses;
var buttonGroup = document.createElement('div');
buttonGroup.className = "btn-group btn-group-sm ol-flip-buttons";
buttonGroup.appendChild(this.addFlipButton(false));
buttonGroup.appendChild(this.addFlipButton(true));
element.appendChild(buttonGroup);
}

/**
* Adds both, flip vertical and horizontal buttons
* @param {boolean} flip_vertical the vertical flip button is added if true, otherwise horizontal
* @private
*/
addFlipButton(flip_vertical) {
if (typeof flip_vertical !== 'boolean') flip_vertical = false;

var title = 'Flip ' + (flip_vertical ? 'vertical' : 'horizontal');
var element = document.createElement('button');
element.className =
this.class_name_ + (flip_vertical ? '-vertical glyphicon-resize-vertical' : '-horizontal glyphicon-resize-horizontal') +
" btn btn-default glyphicon ol-flip-button";
element.setAttribute('type', 'button');
element.title = title;

listen(element, EventType.CLICK, this.handleClick_, this);

return element;
}

init(){
this.view = this.getMap().getView()

this.view.flipX = false
this.view.flipY = false

this.view.constrainCenter_ = this.view.constrainCenter
this.view.constrainCenter = (center) => {
let curCenter = this.view.getCenter()
if (this.view.flipX) {
center[0] = curCenter[0]-(center[0]-curCenter[0])
}
if (this.view.flipY) center[1] = curCenter[1]-(center[1]-curCenter[1])
return this.view.constrainCenter_(center)
}

// override getEventPixel to account for mirroring
this.map_.getEventPixel = function (evt) {
const viewportPosition = this.viewport_.getBoundingClientRect();
const eventPosition =
//FIXME Are we really calling this with a TouchEvent anywhere?
'changedTouches' in evt
? /** @type {TouchEvent} */ (evt).changedTouches[0]
: /** @type {MouseEvent} */ (evt);

let x=eventPosition.clientX - viewportPosition.left
let y=eventPosition.clientY - viewportPosition.top

if (this.getView().flipX) x=viewportPosition.width-x
if (this.getView().flipY) y=viewportPosition.height-y

return [x,y]
}
}

handleClick_(event) {
if(this.view == null) this.init()
// 0 axis if vertical ( flip y over x axis )
// 1 axis if hortizontal ( flip x over y axis )
event.preventDefault();
var viewport = this.getMap().getViewport().children[0] // (mirror just tiles)
var axis = event.target.className.indexOf("ol-flip-vertical") !== -1 ? 0 : 1;

// set desired transform and record in view
let transform;
if (axis == 0) {
transform="scaleY(-1)"
this.view.flipY=!(this.view.flipY)

} else {
transform="scaleX(-1)"
this.view.flipX=!(this.view.flipX)
}

// if it is already mirrored remove mirror, otherwise add mirror
viewport.style.transform = viewport.style.transform.includes(transform) ?
viewport.style.transform.replace(transform, "") :
viewport.style.transform + transform
return true
}

}
export default Mirror
21 changes: 16 additions & 5 deletions src/viewers/viewer/controls/ShapeEditPopup.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import Overlay from 'ol/Overlay.js';
import Text from 'ol/style/Text';
import Style from 'ol/style/Style';
import {getTopLeft, getTopRight} from 'ol/extent';
import {getBottomLeft, getBottomRight, getTopLeft, getTopRight} from 'ol/extent';
import Line from '../geom/Line';
import {isArray,
sendEventNotification} from '../utils/Misc';
Expand Down Expand Up @@ -163,8 +163,11 @@ class ShapeEditPopup extends Overlay {
*/
updatePopupCoordinates(geom) {
let extent = geom.getExtent();
let x = (getTopLeft(extent)[0] + getTopRight(extent)[0]) / 2;
let y = getTopLeft(extent)[1];
let view = this.map.getView()

// gotta get opposite coord depending on mirror
let x = view.flipX ? (getBottomLeft(extent)[0] + getBottomRight(extent)[0]) / 2 : (getTopLeft(extent)[0] + getTopRight(extent)[0]) / 2;
let y = view.flipY ? getBottomLeft(extent)[1] : getTopLeft(extent)[1];

// If it's a Line, popup is on upper end of the line
if (geom instanceof Line) {
Expand Down Expand Up @@ -195,9 +198,17 @@ class ShapeEditPopup extends Overlay {
}
this.coordsInput.value = coordsText;
this.areaInput.value = areaText;

if (this.regions.viewer_.enable_shape_popup) {
this.setPosition([x, y]);
let viewportPosition = this.map.getViewport().getBoundingClientRect()

// unfortunately need to convert to pixel and back
let pixels = this.map.getPixelFromCoordinate([x,y])
if (view.flipX) pixels[0]=viewportPosition.width-pixels[0]
if (view.flipY) pixels[1]=viewportPosition.height-pixels[1]
let coord = this.map.getCoordinateFromPixel(pixels)

this.setPosition(coord);
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/viewers/viewer/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import ScaleBar from './controls/ScaleBar';
import IntensityDisplay from './controls/IntensityDisplay';
import RotateInteraction from './interaction/Rotate';
import BoxSelect from './interaction/BoxSelect';
import Mirror from './controls/Mirror';

/**
* Default lineCap setting for default stroke
Expand Down Expand Up @@ -315,6 +316,12 @@ export const AVAILABLE_VIEWER_CONTROLS = {
"options": {},
"defaults": true,
"enabled": false,
"links" : []},
"mirror" :
{"clazz" : Mirror,
"options": {},
"defaults": true,
"enabled" : false,
"links" : []}
};

Expand Down