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

New Frame selector Vue component #1086

Merged
merged 39 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5d0f3e5
Copy Mike's changes from frame-component-vue branch
annehaley Mar 20, 2023
efbc45d
Fix compilation
annehaley Mar 22, 2023
28d1b01
Reorganize inputs
annehaley Mar 23, 2023
05c25fd
Add color picker column
annehaley Mar 30, 2023
b0b7b6f
Adjust layer managemnet
annehaley Apr 4, 2023
201175c
comment out frame quad (temp)
annehaley Apr 4, 2023
e3683f8
Run tox -e formatclient.
manthey Apr 5, 2023
a563949
Resolve some issues in updating styles along with frames.
manthey Apr 5, 2023
e3cedec
Fix spacing
annehaley Apr 5, 2023
25752e5
Refactor URLSearchParams.
manthey Apr 5, 2023
93c9d9d
Merge branch 'master' into new-frame-selector
annehaley Apr 7, 2023
560ec8e
Colors list and table max height
annehaley Apr 7, 2023
e7c69a7
Fix colors linting
annehaley Apr 7, 2023
000796f
Merge branch 'master' into new-frame-selector
annehaley Apr 7, 2023
369a1d9
Use framedelta in style
annehaley Apr 7, 2023
e19b401
Change createVueModal to createVue
annehaley Apr 10, 2023
58bb30b
Merge branch 'master' into new-frame-selector
annehaley Apr 11, 2023
6ab53dd
Rewrite color assignment for composite channels
annehaley Apr 11, 2023
da894de
Fix nameless channels case
annehaley Apr 11, 2023
060eeab
Start with min and max undefined
annehaley Apr 11, 2023
81b50f0
Change modes options and start band compositing
annehaley Apr 11, 2023
1ba2522
Use band instead of framedelta for band compositing
annehaley Apr 12, 2023
6c864c8
Use layer name instead of 1-based index for band value
annehaley Apr 12, 2023
6d9f1fe
Remove frame indices prop from composite layers component
annehaley Apr 12, 2023
bf9766f
Put back enabled: true
annehaley Apr 12, 2023
e57846d
Make widget work when imageMetadata has no key `frames`
annehaley Apr 12, 2023
e02dc6b
Assign names to unnamed bands
annehaley Apr 12, 2023
1aabebe
Fix geospatial use case
annehaley Apr 12, 2023
ef21bad
Lint fixes
annehaley Apr 12, 2023
7979b43
Merge branch 'master' into new-frame-selector
annehaley Apr 12, 2023
21e920a
Fix color picker placement in scrollable tables
annehaley Apr 13, 2023
464146c
Add enable all / disable all checkbox in table header
annehaley Apr 13, 2023
81ab6ba
Visual improvements, remove alpha on color pickers
annehaley Apr 13, 2023
8390cb7
Merge branch 'master' into new-frame-selector
annehaley Apr 17, 2023
6fc14f6
No max height on layers table
annehaley Apr 17, 2023
e783421
Merge branch 'master' into new-frame-selector
annehaley Apr 19, 2023
1b15871
Fix adding channel axis value when in channel composite mode
annehaley Apr 19, 2023
43af239
Move keepLower to geospatial case
annehaley Apr 19, 2023
0101a3b
Refactor for geospatial case.
manthey Apr 19, 2023
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
6 changes: 5 additions & 1 deletion girder/girder_large_image/web_client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@
"d3": "^3.5.16",
"geojs": "^1.8.6",
"hammerjs": "^2.0.8",
"jsonlint-mod": "^1.7.6",
"js-yaml": "^4.1.0",
"jsonlint-mod": "^1.7.6",
"sinon": "^2.1.0",
"vue": "~2.6.14",
"vue-color": "^2.8.1",
"vue-loader": "~15.9.8",
"vue-template-compiler": "~2.6.14",
"webpack": "^2.7.0",
"yaml": "^2.1.1"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
each viewer in viewers
option(value=viewer.name) #{viewer.label}
.image-controls
#vue-container
span.image-controls-frame.hidden
label(for='image-frame') Frame:
input#image-frame-number.image-controls-number(type='number', min='0', value='0')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as viewers from './imageViewerWidget';

import imageViewerSelectWidget from '../templates/imageViewerSelectWidget.pug';
import '../stylesheets/imageViewerSelectWidget.styl';
import FrameSelector from '../vue/components/FrameSelector.vue';

wrap(ItemView, 'render', function (render) {
// ItemView is a special case in which rendering is done asynchronously,
Expand Down Expand Up @@ -65,6 +66,18 @@ var ImageViewerSelectWidget = View.extend({
return this;
},

_createVueModal(imageMetadata, frameUpdate) {
annehaley marked this conversation as resolved.
Show resolved Hide resolved
const el = this.$('#vue-container').get(0);
const vm = new FrameSelector({
el,
propsData: {
imageMetadata: imageMetadata,
frameUpdate: frameUpdate
}
});
this.vueApp = vm;
},

_selectViewer: function (viewerName) {
if (this.currentViewer && this.currentViewer.name === viewerName) {
return;
Expand Down Expand Up @@ -109,7 +122,7 @@ var ImageViewerSelectWidget = View.extend({
setFrames: function (metadata, frameUpdate) {
if (metadata.frames && metadata.frames.length > 1) {
this._frameUpdate = frameUpdate;
this.$('.image-controls-frame').removeClass('hidden');
// this.$('.image-controls-frame').removeClass('hidden');
var ctrl = this.$('#image-frame'),
ctrlnum = this.$('#image-frame-number');
ctrl.attr('max', metadata.frames.length - 1);
Expand All @@ -121,6 +134,9 @@ var ImageViewerSelectWidget = View.extend({
}
ctrlnum.val(frame);
frameUpdate(frame);

// Vue frame control
this._createVueModal(metadata, frameUpdate);
}
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,18 @@ var GeojsImageViewerWidget = ImageViewerWidget.extend({
params.layer.autoshareRenderer = false;
this._layer = this.viewer.createLayer('osm', params.layer);
if (this.metadata.frames && this.metadata.frames.length > 1) {
const baseUrl = this._getTileUrl('{z}', '{x}', '{y}');
const match = baseUrl.match(/[?&](_=[^&]*)/);
const updated = match && match[1] ? ('&' + match[1]) : '';
setFrameQuad(this.metadata, this._layer, {
// allow more and larger textures is slower, balancing
// performance and appearance
// maxTextures: 16,
// maxTotalTexturePixels: 256 * 1024 * 1024,
baseUrl: this._getTileUrl('{z}', '{x}', '{y}').split('/tiles/')[0] + '/tiles',
baseUrl: baseUrl.split('/tiles/')[0] + '/tiles',
restRequest: restRequest,
restUrl: 'item/' + this.itemId + '/tiles',
query: 'cache=true'
query: 'cache=true' + updated
});
this._layer.setFrameQuad(0);
}
Expand Down Expand Up @@ -144,69 +147,86 @@ var GeojsImageViewerWidget = ImageViewerWidget.extend({
_postRender: function () {
},

frameUpdate: function (frame) {
frameUpdate: function (frame, style) {
if (this._frame === undefined) {
// don't set up layers until the we access the first non-zero frame
if (frame === 0) {
if (frame === 0 && style === undefined) {
return;
}
this._frame = 0;
this._style = undefined;
this._baseurl = this._layer.url();
const quadLoaded = ((this._layer.setFrameQuad || {}).status || {}).loaded;
if (!quadLoaded) {
// use two layers to get smooth transitions until we load
// background quads.
this._layer2 = this.viewer.createLayer('osm', this._layer._options);
this._layer2.moveDown();
setFrameQuad((this._layer.setFrameQuad.status || {}).tileinfo, this._layer2, (this._layer.setFrameQuad.status || {}).options);
this._layer2.setFrameQuad(0);
}
// use two layers to get smooth transitions until we load
// background quads. Always cteate this, as styles will use
// this, even if pure frame do not.
this._layer2 = this.viewer.createLayer('osm', this._layer._options);
this._layer2.moveDown();
setFrameQuad((this._layer.setFrameQuad.status || {}).tileinfo, this._layer2, (this._layer.setFrameQuad.status || {}).options);
this._layer2.setFrameQuad(0);
}
frame = frame || 0;
this._nextframe = frame;
if (frame !== this._frame && !this._updating) {
this._nextstyle = style;
if ((frame !== this._frame || style !== this._style) && !this._updating) {
this._frame = frame;
this._style = style;
this.trigger('g:imageFrameChanging', this, frame);
const quadLoaded = ((this._layer.setFrameQuad || {}).status || {}).loaded;
if (quadLoaded) {
if (this._layer2) {
this.viewer.deleteLayer(this._layer2);
delete this._layer2;
}
if (quadLoaded && this._style === undefined) {
this._layer.url(this.getFrameAndUrl().url);
this._layer.setFrameQuad(frame);
this._layer.frame = frame;
this.trigger('g:imageFrameChanged', this, frame);
return;
}

if (this._layer.frame !== undefined) {
this._layer.setFrameQuad(undefined);
this._layer.frame = undefined;
}
this._updating = true;
this.viewer.onIdle(() => {
this._layer2.url(this.getFrameAndUrl().url);
this._layer2.setFrameQuad(frame);
this._layer2.frame = frame;
if (this._style === undefined) {
this._layer2.setFrameQuad(frame);
this._layer2.frame = frame;
} else {
this._layer2.setFrameQuad(undefined);
this._layer2.frame = undefined;
}
this.viewer.onIdle(() => {
this._layer.moveDown();
var ltemp = this._layer;
this._layer = this._layer2;
this._layer2 = ltemp;
this._updating = false;
this.trigger('g:imageFrameChanged', this, frame);
if (frame !== this._nextframe) {
this.frameUpdate(this._nextframe);
if (frame !== this._nextframe || style !== this._nextstyle) {
this.frameUpdate(this._nextframe, this._nextstyle);
}
});
});
}
},

getFrameAndUrl: function () {
const frame = this._frame || 0;
let frame = this._frame || 0;
let url = this._baseurl || this._layer.url();
// setting the frame to the first frame used in a style seems to
// resolve a caching issue, which is probably a bug in the styling
// functions. Until that is resolved, we do this.
if (this._style && this._style.bands && !this._style.bands.some((b) => b.frame === undefined) && this._style.bands.length) {
frame = this._style.bands[0].frame;
}
if (frame) {
url += (url.indexOf('?') >= 0 ? '&' : '?') + 'frame=' + frame;
}
if (this._style !== undefined) {
const encodedStyle = encodeURIComponent(JSON.stringify(this._style));
url += (url.indexOf('?') >= 0 ? '&' : '?') + 'style=' + encodedStyle;
}
return {
frame: frame,
style: this._style,
url: url
};
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ function setFrameQuad(tileinfo, layer, options) {
return status;
});
layer.setFrameQuad = function (frame) {
if (status.framesToIdx[frame] !== undefined && status.loaded) {
if (frame === undefined) {
layer.baseQuad = undefined;
} else if (status.framesToIdx[frame] !== undefined && status.loaded) {
layer.baseQuad = Object.assign({}, status.quads[status.framesToIdx[frame]]);
}
status.frame = frame;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<script>
import { Chrome } from 'vue-color';

const CHANNEL_COLORS = {
annehaley marked this conversation as resolved.
Show resolved Hide resolved
Red: '#FF0000',
Green: '#008000',
Orange: '#FFA500',
RoyalBlue: '#4169E1',
Purple: '#800080',
Yellow: '#FFFF00',
Magenta: '#FF00FF',
Teal: '#008080',
Maroon: '#800000',
Navy: '#000080',
Violet: '#EE82EE',
};


export default {
props: ['channels', 'channelMap', 'frameIndices'],
emits: ['updateActiveChannels'],
components: {
'color-picker': Chrome
},
data() {
return {
enabledChannels: this.channels,
colorPickerShown: undefined,
currentColorPickerRef: undefined,
}
},
methods: {
toggleColorPicker(channel) {
this.colorPickerShown = channel
if (this.colorPickerShown === undefined) {
document.removeEventListener('click', this.documentClick);
// Only update style when picker is closed
this.updateStyle()
}
else {
this.currentColorPickerRef = document.getElementById(channel+'_picker')
document.addEventListener('click', this.documentClick);
}
},
documentClick(e) {
const picker = this.currentColorPickerRef;
if (picker && picker !== e.target && !picker.contains(e.target)) {
this.toggleColorPicker(undefined);
}
},
updateChannelColor(channel, swatch) {
this.compositeChannelInfo[channel].falseColor = swatch.hex;
},
updateChannelMin(event, channel) {
const newVal = event.target.value;
const newMinVal = parseFloat(newVal);
this.compositeChannelInfo[channel].min = newMinVal;
this.updateStyle();
},
updateChannelMax(event, channel) {
const newVal = event.target.valueAsNumber;
const newMaxVal = parseFloat(newVal);
this.compositeChannelInfo[channel].max = newMaxVal;
this.updateStyle();
},
updateActiveChannels() {
this.channels.forEach((channel) => {
this.compositeChannelInfo[channel].enabled = this.enabledChannels.includes(channel);
})
this.updateStyle();
},
updateStyle() {
const activeChannels = Object.values(
this.compositeChannelInfo
).filter((channel) => channel.enabled);
this.$emit('updateActiveChannels', activeChannels);
},
},
computed: {
compositeChannelInfo() {
return Object.fromEntries(this.channels.map((channel, index) => {
return [channel, {
number: this.channelMap[channel],
enabled: true,
falseColor: Object.values(CHANNEL_COLORS)[index % Object.values(CHANNEL_COLORS).length],
min: 0,
max: 0,
}]
}))
}
},
mounted() {
this.updateStyle()
}
}
</script>

<template>
<div class="table-container">
<table id="composite-channel-table" class="table table-condensed">
<thead>
<tr>
<th class="channel-col">Channel</th>
<th class="enabled-col">Enabled?</th>
<th class="color-col">Color</th>
<th class="precision-col">Min</th>
<th class="precision-col">Max</th>
</tr>
</thead>
<tbody>
<tr
v-for="channel in channels.filter(c => compositeChannelInfo[c] !== undefined)"
:key="channel"
>
<td>{{ channel }}</td>
<td>
<input
type="checkbox"
:value="channel"
v-model="enabledChannels"
@change="updateActiveChannels"
>
</td>
<td :id="channel+'_picker'">
<span
class="current-color"
:style="{ 'background-color': compositeChannelInfo[channel].falseColor }"
@click="() => toggleColorPicker(channel)"
/>
<color-picker
class="picker-offset"
v-if="colorPickerShown === channel"
:value="compositeChannelInfo[channel].falseColor"
@input="(swatch) => {updateChannelColor(channel, swatch)}"
/>
</td>
<td>
<input
type="number"
step="0.01"
min="0"
max="1"
:value="compositeChannelInfo[channel].min"
@change.prevent="(event) => updateChannelMin(event, channel)"
>
</td>
<td>
<input
type="number"
step="0.01"
min="0"
max="1"
:value="compositeChannelInfo[channel].max"
@change.prevent="(event) => updateChannelMax(event, channel)"
>
</td>
<td></td>
</tr>
</tbody>
</table>
</div>
</template>

<style scoped>
.current-color {
display: inline-block;
width: 50px;
height: 20px;
background-color: #000;
cursor: pointer;
}
.picker-offset {
position: absolute;
z-index: 100;
margin-left: 50px;
}
.table-container {
max-height: 200px;
annehaley marked this conversation as resolved.
Show resolved Hide resolved
overflow: scroll;
}
.table-container input {
max-width: 70px;
}
</style>
Loading