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

Add Girder Large Image and Tiled Image Support #1255

Merged
merged 29 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8720a78
Adding in basic loading and running of geospatial images
BryonLewis Jun 1, 2022
2fc6075
Got the single frame non-geospatial stuff working
BryonLewis Jun 3, 2022
d6b5036
got projection advancement working
BryonLewis Jun 3, 2022
2f58a7e
updating geospatial to ignore it
BryonLewis Jun 9, 2022
2d71948
format python
BryonLewis Jun 9, 2022
b0bffce
Auto Conversion
BryonLewis Jun 10, 2022
71eab17
Merge branch 'main' into tiled-image-support
BryonLewis Oct 12, 2022
5eb3719
update broken pacakges
BryonLewis Oct 12, 2022
7c43eac
move urls into apispec
BryonLewis Oct 12, 2022
69a676b
add single frame views and fix large image caching
BryonLewis Oct 12, 2022
62090f5
disable pipelines for large-image
BryonLewis Oct 13, 2022
2be8868
update to latest large image
BryonLewis Oct 13, 2022
498d9dc
fixing non-geospatial display of multiple images
BryonLewis Nov 7, 2022
72132e5
fix image sizing requests
BryonLewis Nov 7, 2022
8aa2542
correct mypy no implicit optional
BryonLewis Nov 8, 2022
8bad201
update install process
BryonLewis Nov 28, 2022
005d65f
Merge branch 'main' into tiled-image-support
BryonLewis Dec 5, 2022
0bbc49b
updating extras
BryonLewis Dec 5, 2022
2cd1a58
Merge branch 'main' into tiled-image-support
BryonLewis Mar 28, 2023
5835578
testing
BryonLewis Mar 28, 2023
605560b
linting
BryonLewis Mar 28, 2023
c8dc721
Merge branch 'main' into tiled-image-support
BryonLewis Sep 11, 2023
cf828d0
update installation
BryonLewis Sep 11, 2023
665400d
1fps default for large image, display large image filename, play func…
BryonLewis Sep 11, 2023
5e37b57
update GDAL for CI
BryonLewis Sep 11, 2023
6b85fef
update CI
BryonLewis Sep 11, 2023
776ebe4
large-image as extra
BryonLewis Sep 11, 2023
033133a
Merge branch 'main' into tiled-image-support
BryonLewis Sep 14, 2023
3b86073
adding tooltip to tiled image import
BryonLewis Sep 14, 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
23 changes: 14 additions & 9 deletions .github/workflows/blank.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,28 @@ jobs:
run: yarn build:electron

test-server:
defaults:
run:
working-directory: server
name: Server Tests
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8]
steps:
- uses: actions/checkout@v2
- name: Update Package References
run: sudo apt-get update
- name: Install system dependencies
run: sudo apt-get install --no-install-recommends --yes
python3-cachecontrol
python3-dev
python3-gdal
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install tox
run: |
python -m pip install --upgrade pip;
pip install tox;
run: pip install tox
- name: Run tests
run: tox
run: tox -e testunit
working-directory: server

8 changes: 6 additions & 2 deletions client/dive-common/apispec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { provide } from '@vue/composition-api';
import { AnnotationId } from 'vue-media-annotator/BaseAnnotation';
import { AnnotationId, StringKeyObject } from 'vue-media-annotator/BaseAnnotation';
import { GroupData } from 'vue-media-annotator/Group';

import { use } from 'vue-media-annotator/provides';
Expand All @@ -8,7 +8,7 @@ import { Attribute } from 'vue-media-annotator/use/AttributeTypes';
import { CustomStyle } from 'vue-media-annotator/StyleManager';
import { AttributeTrackFilter } from 'vue-media-annotator/AttributeTrackFilterControls';

type DatasetType = 'image-sequence' | 'video' | 'multi';
type DatasetType = 'image-sequence' | 'video' | 'multi' | 'large-image';
type MultiTrackRecord = Record<string, TrackData>;
type MultiGroupRecord = Record<string, GroupData>;
type SubType = 'stereo' | 'multicam' | null; // Additional type info used for UI display enabled pipelines
Expand Down Expand Up @@ -161,6 +161,10 @@ interface Api {
// Non-Endpoint shared functions
openFromDisk(datasetType: DatasetType | 'calibration' | 'annotation' | 'text' | 'zip', directory?: boolean):
Promise<{canceled?: boolean; filePaths: string[]; fileList?: File[]; root?: string}>;
getTiles?(itemId: string, projection?: string): Promise<StringKeyObject>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getTileURL?(itemId: string, x: number, y: number, level: number, query: Record<string, any>):
string;
importAnnotationFile(id: string, path: string, file?: File,
additive?: boolean, additivePrepend?: string): Promise<boolean>;
}
Expand Down
2 changes: 1 addition & 1 deletion client/dive-common/components/ControlsContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export default defineComponent({
</template>
<template #middle>
<file-name-time-display
v-if="datasetType === 'image-sequence'"
v-if="datasetType === 'image-sequence' || datasetType === 'large-image'"
class="text-middle px-3"
display-type="filename"
/>
Expand Down
4 changes: 2 additions & 2 deletions client/dive-common/components/ImportButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default defineComponent({
<v-card outlined>
<v-list dense>
<v-list-item
v-if="openType === 'image-sequence'"
v-if="['image-sequence', 'large-image'].includes(openType)"
style="align-items':'center"
@click="$emit('open', openType)"
>
Expand All @@ -101,7 +101,7 @@ export default defineComponent({
</v-list-item-content>
</v-list-item>
<v-list-item
v-if="openType === 'image-sequence'"
v-if="['image-sequence', 'large-image'].includes(openType)"
style="align-items':'center"
@click="$emit('open', 'text')"
>
Expand Down
19 changes: 17 additions & 2 deletions client/dive-common/components/RunPipelineMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import {
Pipe,
useApi,
SubType,
DatasetType,
} from 'dive-common/apispec';
import JobLaunchDialog from 'dive-common/components/JobLaunchDialog.vue';
import { stereoPipelineMarker, multiCamPipelineMarkers } from 'dive-common/constants';
import { stereoPipelineMarker, multiCamPipelineMarkers, LargeImageType } from 'dive-common/constants';
import { useRequest } from 'dive-common/use';
import { usePrompt } from 'dive-common/vue-utilities/prompt-service';

Expand All @@ -31,6 +32,11 @@ export default defineComponent({
type: Object,
default: () => ({}),
},
/* Disable pipelines for large-image type */
typeList: {
type: Array as PropType<DatasetType[]>,
default: () => ([]),
},
/* Which pipelines to show based on dataset subtypes */
subTypeList: {
type: Array as PropType<SubType[]>,
Expand Down Expand Up @@ -63,6 +69,8 @@ export default defineComponent({
state: jobState,
} = useRequest();

const includesLargeImage = computed(() => props.typeList.includes(LargeImageType));

const successMessage = computed(() => (
`Started ${selectedPipe.value?.name} on ${props.selectedDatasetIds.length} dataset(s).`));

Expand Down Expand Up @@ -164,6 +172,7 @@ export default defineComponent({
jobState,
pipelines,
pipelinesNotRunnable,
includesLargeImage,
successMessage,
dismissLaunchDialog,
pipeTypeDisplay,
Expand All @@ -190,7 +199,7 @@ export default defineComponent({
<template #activator="{ on: tooltipOn }">
<v-btn
v-bind="buttonOptions"
:disabled="pipelinesNotRunnable"
:disabled="pipelinesNotRunnable || buttonOptions.disabled"
:color="pipelinesCurrentlyRunning? 'warning' : buttonOptions.color"
v-on="{ ...tooltipOn, ...menuOn }"
>
Expand Down Expand Up @@ -252,6 +261,12 @@ export default defineComponent({
This Dataset is in ReadOnly Mode. You cannot run pipelines on this dataset.
</v-card-text>
</v-card>
<v-card v-else-if="includesLargeImage">
<v-card-title> Large Image</v-card-title>
<v-card-text>
Pipelines are not supported yet for Large Images.
</v-card-text>
</v-card>
<v-card
v-else-if="pipelines"
outlined
Expand Down
16 changes: 13 additions & 3 deletions client/dive-common/components/Viewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { provideAnnotator } from 'vue-media-annotator/provides';
import {
ImageAnnotator,
VideoAnnotator,
LargeImageAnnotator,
LayerManager,
useMediaController,
} from 'vue-media-annotator/components';
Expand Down Expand Up @@ -63,6 +64,7 @@ export default defineComponent({
LayerManager,
VideoAnnotator,
ImageAnnotator,
LargeImageAnnotator,
ConfidenceFilter,
UserGuideButton,
EditorMenu,
Expand Down Expand Up @@ -104,7 +106,9 @@ export default defineComponent({
const datasetName = ref('');
const saveInProgress = ref(false);
const videoUrl: Ref<Record<string, string>> = ref({});
const { loadDetections, loadMetadata, saveMetadata } = useApi();
const {
loadDetections, loadMetadata, saveMetadata, getTiles, getTileURL,
} = useApi();
const progress = reactive({
// Loaded flag prevents annotator window from populating
// with stale data from props, for example if a persistent store
Expand Down Expand Up @@ -756,6 +760,9 @@ export default defineComponent({
readonlyState,
brightness,
intercept,
/* large image methods */
getTiles,
getTileURL,
/* methods */
handler: globalHandler,
save,
Expand Down Expand Up @@ -946,14 +953,17 @@ export default defineComponent({
@mouseup.right="changeCamera(camera, $event)"
>
<component
:is="datasetType === 'image-sequence' ? 'image-annotator' : 'video-annotator'"
:is="datasetType === 'image-sequence' ? 'image-annotator' :
datasetType === 'video' ? 'video-annotator' : 'large-image-annotator'"
v-if="(imageData[camera].length || videoUrl[camera]) && progress.loaded"
ref="subPlaybackComponent"
class="fill-height"
:class="{'selected-camera': selectedCamera === camera && camera !== 'singleCam'}"
v-bind="{
imageData: imageData[camera], videoUrl: videoUrl[camera],
updateTime, frameRate, originalFps, camera, brightness, intercept }"
updateTime, frameRate, originalFps, camera, brightness,
intercept, getTiles, getTileURL }"
@large-image-warning="$emit('large-image-warning', true)"
>
<LayerManager :camera="camera" />
</component>
Expand Down
12 changes: 12 additions & 0 deletions client/dive-common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { DatasetType } from './apispec';
const ImageSequenceType = 'image-sequence';
const VideoType = 'video';
const MultiType = 'multi';
const LargeImageType = 'large-image';

const MediaTypes: Record<DatasetType, string> = {
// friendly media type names
[ImageSequenceType]: 'image sequence',
[VideoType]: 'video',
[MultiType]: 'multi',
[LargeImageType]: 'tiled image',
};

const DefaultVideoFPS = 10;
Expand Down Expand Up @@ -67,6 +69,14 @@ const fileVideoTypes = [
'flv',
];

const largeImageTypes = [
'image/geotiff',
'image/tiff',
'image/x-tiff',
'image/nitf',
'image/ntf',
];

const websafeImageTypes = [
// 'image/apng',
// 'image/bmp',
Expand Down Expand Up @@ -122,6 +132,7 @@ export {
DefaultVideoFPS,
ImageSequenceType,
VideoType,
LargeImageType,
MediaTypes,
MultiType,
FPSOptions,
Expand All @@ -133,6 +144,7 @@ export {
websafeImageTypes,
websafeVideoTypes,
inputAnnotationTypes,
largeImageTypes,
inputAnnotationFileTypes,
listFileTypes,
zipFileTypes,
Expand Down
4 changes: 4 additions & 0 deletions client/platform/web-girder/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
loadDetections,
saveDetections,
unwrap,
getTiles,
getTileURL,
} from './api';
import { openFromDisk } from './utils';

Expand All @@ -46,6 +48,8 @@ export default defineComponent({
loadMetadata,
openFromDisk,
importAnnotationFile,
getTiles,
getTileURL,
});
},
});
Expand Down
1 change: 1 addition & 0 deletions client/platform/web-girder/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './configuration.service';
export * from './dataset.service';
export * from './girder.service';
export * from './rpc.service';
export * from './largeImage.service';

/**
* All API functions should return their raw AxiosResponse,
Expand Down
38 changes: 38 additions & 0 deletions client/platform/web-girder/api/largeImage.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import girderRest from 'platform/web-girder/plugins/girder';

async function getTilesMetadata(itemId: string) {
const { data } = await girderRest.get(`item/${itemId}/tiles/`);
return data;
}
async function getTiles(itemId: string, projection = '') {
let url = `item/${itemId}/tiles`;
if (projection !== '') {
url = `${url}?projection=${encodeURIComponent(projection)}`;
}
const { data } = await girderRest.get(url);
return data;
}
function getTileURL(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
itemId: string, x: number, y: number, level: number, query: Record<string, any>,
) {
let url = `${girderRest.apiRoot}/item/${itemId}/tiles/zxy/${level}/${x}/${y}`;
if (query) {
const params = Object.keys(query).map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(query[k])}`).join('&');
url += `?${params}`;
}
return url;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function getTileFrames(itemId: string, options: any) {
const { data } = await girderRest.get(`/item/${itemId}/tiles/tile_frames/quad_info`, options);
return data;
}

export {
getTilesMetadata,
getTiles,
getTileURL,
getTileFrames,
};
5 changes: 5 additions & 0 deletions client/platform/web-girder/api/rpc.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ function runTraining(
});
}

function convertLargeImage(folderId: string) {
return girderRest.post(`dive_rpc/convert_large_image/${folderId}`, null, {});
}

export {
convertLargeImage,
postProcess,
runPipeline,
runTraining,
Expand Down
2 changes: 1 addition & 1 deletion client/platform/web-girder/store/Jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export async function init(store: Store<RootState>) {
store.commit('Jobs/setJobState', { jobId: job._id, value: job.status });
if (typeof job.dataset_id === 'string') {
store.commit('Jobs/setDatasetStatus', { datasetId: job.dataset_id, status: job.status, jobId: job._id });
if (job.type === 'pipelines' && NonRunningStates.includes(job.status)) {
if (['pipelines', 'convert'].includes(job.type || '') && NonRunningStates.includes(job.status)) {
store.commit('Jobs/setCompleteJobsInfo', {
datasetId: job.dataset_id,
type: job.type,
Expand Down
3 changes: 2 additions & 1 deletion client/platform/web-girder/views/Export.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from 'platform/web-girder/api';
import { GirderMetadataStatic } from 'platform/web-girder/constants';
import {
ImageSequenceType, MultiType, VideoType,
ImageSequenceType, LargeImageType, MultiType, VideoType,
} from 'dive-common/constants';

export default defineComponent({
Expand Down Expand Up @@ -161,6 +161,7 @@ export default defineComponent({
return {
[ImageSequenceType]: 'Image Sequence',
[VideoType]: 'Video',
[LargeImageType]: 'Tiled Images',
}[type];
});

Expand Down
11 changes: 9 additions & 2 deletions client/platform/web-girder/views/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ export default defineComponent({
({ _modelType, meta }) => _modelType === 'folder' && meta && meta.annotate,
).map(({ _id }) => _id);
},
includesLargeImage() {
return (this.selected.filter(
({ meta }) => meta && meta.type === 'large-image',
)).length > 0;
},
locationInputs() {
return this.locationIsViameFolder ? [this.location._id] : this.selectedViameFolderIds;
},
Expand Down Expand Up @@ -157,11 +162,13 @@ export default defineComponent({
:dataset-id="locationInputs.length === 1 ? locationInputs[0] : null"
/>
<run-training-menu
v-bind="{ buttonOptions, menuOptions }"
v-bind="{ buttonOptions:
{ ...buttonOptions, disabled: includesLargeImage }, menuOptions }"
:selected-dataset-ids="locationInputs"
/>
<run-pipeline-menu
v-bind="{ buttonOptions, menuOptions }"
v-bind="{ buttonOptions:
{ ...buttonOptions, disabled: includesLargeImage }, menuOptions }"
:selected-dataset-ids="locationInputs"
:running-pipelines="runningPipelines"
/>
Expand Down
2 changes: 1 addition & 1 deletion client/platform/web-girder/views/RunTrainingMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export default defineComponent({
<template #activator="{ on: tooltipOn }">
<v-btn
v-bind="buttonOptions"
:disabled="trainingDisabled"
:disabled="trainingDisabled || buttonOptions.disabled"
v-on="{ ...tooltipOn, ...menuOn }"
>
<v-icon>
Expand Down
Loading
Loading