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

Max Merge option in Frame Selector #1306

Merged
merged 10 commits into from
Oct 4, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
import {restRequest} from '@girder/core/rest';
import {Chrome} from 'vue-color';

import {CHANNEL_COLORS, OTHER_COLORS} from '../utils/colors';
import {OTHER_COLORS, getChannelColor} from '../utils/colors';
import HistogramEditor from './HistogramEditor.vue';

export default {
components: {
'color-picker': Chrome,
HistogramEditor
},
props: ['itemId', 'currentFrame', 'currentStyle', 'layers', 'layerMap', 'active'],
props: ['itemId', 'currentFrame', 'currentStyle', 'histogramParamStyle', 'layers', 'layerMap', 'active'],
emits: ['updateStyle'],
data() {
return {
Expand All @@ -20,17 +20,21 @@ export default {
compositeLayerInfo: {},
expandedRows: [],
autoRangeForAll: undefined,
histogramParams: {
showKeyboardShortcuts: false
};
},
computed: {
histogramParams() {
return {
frame: this.currentFrame,
width: 1024,
height: 1024,
bins: 512,
resample: false,
style: '{}',
style: this.histogramParamStyle,
roundRange: true
},
showKeyboardShortcuts: false
};
};
}
},
watch: {
active() {
Expand All @@ -44,6 +48,9 @@ export default {
if (this.currentStyle.preset) {
this.initializeStateFromStyle();
}
},
histogramParams() {
this.fetchCurrentFrameHistogram();
}
},
mounted() {
Expand Down Expand Up @@ -114,13 +121,11 @@ export default {
// Assign colors
this.layers.forEach((layerName) => {
if (!this.compositeLayerInfo[layerName].palette) {
// Search for case-insensitive regex match among known channel-colors
Object.entries(CHANNEL_COLORS).forEach(([channelPattern, color]) => {
if (layerName.match(new RegExp(channelPattern, 'i')) && !usedColors.includes(color)) {
this.compositeLayerInfo[layerName].palette = color;
usedColors.push(color);
}
});
const channelColor = getChannelColor(layerName);
if (channelColor) {
this.compositeLayerInfo[layerName].palette = channelColor;
usedColors.push(channelColor);
}
}
});
this.layers.forEach((layerName) => {
Expand Down
30 changes: 26 additions & 4 deletions girder/girder_large_image/web_client/vue/components/DualInput.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<script>
export default {
props: ['label', 'currentValue', 'valueMax', 'sliderLabels'],
emits: ['updateValue'],
props: ['label', 'currentValue', 'valueMax', 'sliderLabels', 'maxMerge'],
emits: ['updateValue', 'updateMaxMerge'],
data() {
return {
value: this.currentValue
value: this.currentValue,
merge: this.maxMerge
};
},
watch: {
Expand All @@ -13,6 +14,12 @@ export default {
},
value(v) {
this.$emit('updateValue', v);
},
maxMerge(v) {
this.merge = v;
},
merge(v) {
this.$emit('updateMaxMerge', v);
}
}
};
Expand All @@ -28,15 +35,17 @@ export default {
name="numberControl"
min="0"
:max="valueMax"
:disabled="merge"
>
</td>
<td style="width:90%">
<td style="width: 100%">
<input
v-model="value"
type="range"
name="sliderControl"
min="0"
:max="valueMax"
:disabled="merge"
>
<div class="bubble-wrap">
<output
Expand All @@ -53,12 +62,25 @@ export default {
/>
</div>
</td>
<td
v-show="merge !== undefined"
style="min-width: 150px; text-align: right;"
>
<input
:id="'maxMerge'+label"
v-model="merge"
type="checkbox"
style="margin: 0px 5px 0px 10px"
>
<label :for="'maxMerge'+label">Max Merge</label>
</td>
</tr>
</template>

<style scoped>
.dual-controls > * > * {
margin-right: 15px;
white-space: nowrap;
}
.dual-controls.tall {
height: 40px;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script>
import Vue from 'vue';

import {getChannelColor} from '../utils/colors';

import CompositeLayers from './CompositeLayers.vue';
import DualInput from './DualInput.vue';
import PresetsMenu from './PresetsMenu.vue';
Expand All @@ -18,7 +20,8 @@ export default Vue.extend({
indices: [],
indexInfo: {},
style: {},
modesShown: {1: true}
modesShown: {1: true},
histogramParamStyles: {}
};
},
computed: {
Expand Down Expand Up @@ -63,14 +66,39 @@ export default Vue.extend({
})
);
},
updateHistogramParamStyles() {
this.histogramParamStyles = {};
Array.from([2, 3]).forEach((modeID) => {
const mergedStyle = this.maxMergeStyle();
if (mergedStyle.bands.length) {
const simpleMergedStyleString = JSON.stringify({
dtype: 'source',
bands: mergedStyle.bands.map((b) => ({
framedelta: b.framedelta,
band: b.band
// including min, max, and palette gives strange results
}))
});
this.histogramParamStyles[modeID] = simpleMergedStyleString;
} else {
this.histogramParamStyles[modeID] = '{}';
}
});
},
updateStyle(idx, style) {
this.$set(this.style, idx, style);
this.updateHistogramParamStyles();
this.update();
},
updateAxisSlider(event) {
this.indexInfo[event.index].current = event.frame;
this.update();
},
updateMaxMergeAxis(event) {
this.indexInfo[event.index].maxMerge = event.maxMerge;
this.update();
this.updateHistogramParamStyles();
},
updateFrameSlider(frame) {
this.currentFrame = frame;
this.frameUpdate(frame, undefined);
Expand All @@ -80,14 +108,73 @@ export default Vue.extend({
this.indices.forEach((index) => {
if (this.sliderIndices.includes(index)) {
const info = this.indexInfo[index];
frame += info.current * info.stride;
if (!info.maxMerge) {
frame += info.current * info.stride;
}
}
});
this.currentFrame = frame;
const style = this.currentModeId > 1 ? Object.assign({}, this.style[this.currentModeId]) : undefined;
let style = this.currentModeId > 1 ? Object.assign({}, this.style[this.currentModeId]) : undefined;
if (style && style.preset) delete style.preset;
style = this.maxMergeStyle(style);
this.frameUpdate(frame, style);
},
maxMergeStyle(style) {
const bandsArray = (style ? style.bands : []) || [];
let newBandsArray = [];
let frameDeltas = [];
Object.entries(this.indexInfo).forEach(([indexName, {range, stride, maxMerge}]) => {
if (this.currentModeId === 2 && indexName === 'IndexC') {
// channel compositing is already in bandsArray
// skip permutations for this axis
} else if (maxMerge) {
const axisFrameDeltas = [...Array(range + 1).keys()].map((i) => i * stride);
if (frameDeltas.length) {
const newFrameDeltas = [];
frameDeltas.forEach((d) => {
axisFrameDeltas.forEach((a) => {
newFrameDeltas.push(d + a);
});
});
frameDeltas = newFrameDeltas;
} else {
frameDeltas = axisFrameDeltas;
}
}
});

if (frameDeltas.length) {
if (bandsArray.length) {
// some style already applied, add permutations
bandsArray.forEach((b) => {
frameDeltas.forEach((framedelta) => {
newBandsArray.push(
Object.assign({}, b, {
framedelta: b.framedelta ? b.framedelta + framedelta : framedelta
})
);
});
});
} else {
// no style applied yet, create new permutations list
const {bands} = this.metadata;
bands.forEach((b, i) => {
const bandPalette = getChannelColor(b);
frameDeltas.forEach((framedelta) => {
newBandsArray.push({
band: i + 1,
framedelta,
palette: bandPalette
});
});
});
}
} else {
// no max merge permutations to apply, keep old bandsArray
newBandsArray = bandsArray;
}
return {bands: newBandsArray};
},
fillMetadata() {
if (!this.metadata.frames) {
this.metadata.frames = [{
Expand Down Expand Up @@ -242,6 +329,8 @@ export default Vue.extend({
:value-max="indexInfo[index].range"
:label="index.replace('Index', '')"
:slider-labels="index === 'IndexC' ? imageMetadata.channels : []"
:max-merge="indexInfo[index].maxMerge || false"
@updateMaxMerge="(v) => updateMaxMergeAxis({index, maxMerge: v})"
@updateValue="(v) => updateAxisSlider({index, frame: v})"
/>
</table>
Expand All @@ -255,6 +344,7 @@ export default Vue.extend({
:item-id="itemId"
:current-frame="currentFrame"
:current-style="style[2]"
:histogram-param-style="histogramParamStyles[2]"
:layers="metadata.channels"
:layer-map="metadata.channelmap"
:active="currentModeId === 2"
Expand All @@ -267,6 +357,7 @@ export default Vue.extend({
:item-id="itemId"
:current-frame="currentFrame"
:current-style="style[3]"
:histogram-param-style="histogramParamStyles[3]"
:layers="metadata.bands"
:layer-map="undefined"
:active="currentModeId === 3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export default {
currentFrame() {
this.fetchHistogram();
},
currentFrameHistogram() {
this.fetchHistogram();
},
histogram() {
// allow rerender to occur first
Vue.nextTick().then(() => {
Expand Down Expand Up @@ -290,10 +293,11 @@ export default {
class="handles-svg"
>
<text
v-if="vRange[0] !== undefined"
x="5"
y="43"
class="small"
>{{ vRange[0] }}</text>
>{{ +vRange[0].toFixed(2) || 0 }}</text>
<rect
ref="minExclusionBox"
x="5"
Expand All @@ -314,10 +318,11 @@ export default {
y2="30"
/>
<text
v-if="vRange[1] !== undefined"
:x="xRange[1] && vRange[1] ? xRange[1] - (`${vRange[1]}`.length * 6): 0"
y="43"
class="small"
>{{ vRange[1] }}</text>
>{{ +vRange[1].toFixed(2) || 1 }}</text>
<rect
ref="maxExclusionBox"
x="5"
Expand Down
9 changes: 9 additions & 0 deletions girder/girder_large_image/web_client/vue/utils/colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ export const CHANNEL_COLORS = {
'^gr[ae]y(|scale)$': '#FFFFFF'
};

export function getChannelColor(name) {
// Search for case-insensitive regex match among known channel-colors
for (const [channelPattern, color] of Object.entries(CHANNEL_COLORS)) {
if (name.match(new RegExp(channelPattern, 'i'))) {
return color;
}
}
}

export const OTHER_COLORS = [
'#FF0000',
'#00FF00',
Expand Down