Skip to content

Commit de1e06a

Browse files
committed
Add URL image example
1 parent da10cc6 commit de1e06a

File tree

8 files changed

+191
-63
lines changed

8 files changed

+191
-63
lines changed

README.md

Lines changed: 61 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,9 @@ for use with `django-rest-framework` providing view mixins for endpoints to
1111
work with large images in Django -- specifically geared towards geospatial and
1212
medical image tile serving.
1313

14-
*DISCLAIMER:* this is a work in progress and is currently in an experimental phase.
15-
16-
| RGB Raster | Colormapped Raster Band |
17-
|---|---|
18-
| ![raster](https://raw.githubusercontent.com/ResonantGeoData/django-large-image/main/doc/raster.png) | ![raster-colormap](https://raw.githubusercontent.com/ResonantGeoData/django-large-image/main/doc/raster_colormap.png) |
14+
![admin-interface](https://raw.githubusercontent.com/ResonantGeoData/django-large-image/main/doc/admin.png)
1915

20-
| Swagger Documentation | Tiles Endpoint |
16+
| OpenAPI Documentation | Tiles Endpoint |
2117
|---|---|
2218
|![swagger-spec](https://raw.githubusercontent.com/ResonantGeoData/django-large-image/main/doc/swagger.png) | ![tiles-spec](https://raw.githubusercontent.com/ResonantGeoData/django-large-image/main/doc/tiles_endpoint.png)|
2319

@@ -90,11 +86,21 @@ pip install \
9086

9187
## Usage
9288

93-
Simply import and mixin the `LargeImageView` class to your existing
89+
Simply install the app and mixin the `LargeImageView` class to your existing
9490
`django-rest-framework` viewsets and specify the `FILE_FIELD_NAME` as the
9591
string name of the `FileField` in which your image data are saved.
9692

9793
```py
94+
# settings.py
95+
INSTALLED_APPS = [
96+
...,
97+
'django_large_image',
98+
]
99+
```
100+
101+
102+
```py
103+
# viewsets.py
98104
from django_large_image.rest import LargeImageView
99105

100106
class MyModelViewset(viewsets.GenericViewSet, LargeImageView):
@@ -110,8 +116,7 @@ To use the mixin classes provided here, create a model, serializer, and view in
110116
your Django project like so:
111117

112118
```py
113-
models.py
114-
---
119+
# models.py
115120
from django.db import models
116121
from rest_framework import serializers
117122

@@ -128,8 +133,7 @@ class ImageFileSerializer(serializers.ModelSerializer):
128133
```
129134

130135
```py
131-
admin.py
132-
---
136+
# admin.py
133137
from django.contrib import admin
134138
from example.core.models import ImageFile
135139

@@ -141,8 +145,7 @@ class ImageFileAdmin(admin.ModelAdmin):
141145

142146
Then create the viewset, mixing in the `django-large-image` view class:
143147
```py
144-
viewsets.py
145-
---
148+
# viewsets.py
146149
from example.core import models
147150
from rest_framework import mixins, viewsets
148151

@@ -164,8 +167,7 @@ class ImageFileDetailView(
164167
Then register the URLs:
165168

166169
```py
167-
urls.py
168-
---
170+
# urls.py
169171
from django.urls import path
170172
from example.core.viewsets import ImageFileDetailView
171173
from rest_framework.routers import SimpleRouter
@@ -174,16 +176,15 @@ router = SimpleRouter(trailing_slash=False)
174176
router.register(r'api/image-file', ImageFileDetailView, basename='image-file')
175177

176178
urlpatterns = [
177-
path('', include('django_large_image.urls')), # Some additional diagnostic URLs from django-large-image
179+
path('', include('django_large_image.urls')), # Additional diagnostic URLs from django-large-image
178180
] + router.urls
179181

180182
```
181183

182-
You can also opt into an admin widget for you model:
184+
You can also use an admin widget for your model:
183185

184186
```html
185-
templates/admin/myapp/imagefile/change_form.html
186-
---
187+
<!-- templates/admin/myapp/imagefile/change_form.html -->
187188
{% extends "admin/change_form.html" %}
188189

189190
{% block after_field_sets %}
@@ -197,62 +198,60 @@ templates/admin/myapp/imagefile/change_form.html
197198
{% endblock %}
198199
```
199200

200-
![admin-interface](https://raw.githubusercontent.com/ResonantGeoData/django-large-image/main/doc/admin.png)
201+
Please note the example Django project in the `project/` directory of this
202+
repository that shows how to use `django-large-image` in a `girder-4` project.
201203

202204

203-
Please note the example Django project in the `project/` directory of this
204-
repository that shows how to use `django-large-image`.
205+
### Customization
205206

206-
## Work Plan
207+
The `LargeImageView` is modularly designed and able to be subclassed for your
208+
project's needs. While the provided `LargeImageView` handles
209+
`FileFeild`-interfaces, you can easily extend it to handle any mechanism of
210+
data storage.
207211

208-
Our primary goal is to get through phases 1 and 2, focusing on tile serving of
209-
large geospatial images specifically in Cloud Optimized GeoTiff (COG) format.
212+
In the following example, I will show how to use GDAL compatible VSI paths
213+
from a model that stores `s3://` or `https://` URLs.
210214

211-
### Phase 1
215+
```py
216+
# model.py
217+
from django.db import models
218+
from rest_framework import serializers
212219

213-
- [x] Abstract API View classes that can be mixed-in downstream to expose all available endpoints
214-
- [x] endpoints for metadata (/tiles, /tiles/internal_metadata)
215-
- [x] endpoints for serving tiles (/tiles/zxy, /tiles/fzxy)
216-
- [x] cache management - tile sources should be cached so that we don't open a file for each tile
217-
- [x] endpoint for regions
218-
- [x] endpoint for thumbnails
219-
- [x] thumbnail caching
220-
- [x] endpoint for individual pixels
221-
- [x] endpoint for histograms
222-
- [x] some diagnostic and settings endpoints (list available sources, set whether to automatically use large_images and the size of small images that can be used)
223-
- [x] Support for django's FileFeild
224-
- [x] Support for S3FileField
225-
- [x] Ship an easily extensible SSR template for tile viewing with CesiumJS
226-
- [x] Support for using file URLs with GDAL's VSI
227-
- [x] Provide OpenAPI documentation in swagger
228220

229-
### Phase 2
221+
class URLImageFile(models.Model):
222+
name = models.TextField()
223+
url = models.TextField()
224+
230225

231-
- [ ] Handle band/component selection styling for tile serving and thumbnails
232-
- e.g., use channels 3,7,5 for Red Green Blue
233-
- endable linear/discrete color modes
234-
- [ ] Tie large-image's caching into Django's cache (might require upstream work in large-image)
235-
- [ ] Provide some sort of endpoint to check if an image is a valid COG
236-
- [ ] Create a secondary app with celery tasks for converting images to COG
237-
- [ ] Refactor/prototpye RGD's ChecksumFile model as a FieldFile subclass
238-
- [ ] Support GeoDjango's [`GDALRaster`](https://docs.djangoproject.com/en/4.0/ref/contrib/gis/gdal/#django.contrib.gis.gdal.GDALRaster)
226+
class URLImageFileSerializer(serializers.ModelSerializer):
227+
class Meta:
228+
model = URLImageFile
229+
fields = '__all__'
230+
```
239231

240-
### Phase 3 and onward
241232

242-
Incorporate more features from large-image.
233+
```py
234+
# viewsets.py
235+
from example.core import models
236+
from rest_framework import mixins, viewsets
243237

244-
Things that would require implementing tasks with celery:
238+
from django_large_image.rest import LargeImageViewMixin
239+
from django_large_image.utilities import make_vsi
245240

246-
- [ ] ability to convert images via large_image_converter
247-
- [ ] async endpoint for regions
248241

249-
Things I'm unsure about:
242+
class URLLargeImageViewMixin(LargeImageViewMixin):
243+
def get_path(self, request, pk):
244+
object = self.get_object()
245+
return make_vsi(object.url)
250246

251-
- [ ] endpoints for associated images
252-
- [ ] ability to precache thumbnails (the thumbnail jobs endpoints)
253-
- [ ] endpoints for serving tiles in deepzoom format
254247

255-
Things I think should be implemented downstream:
248+
class URLImageFileDetailView(
249+
mixins.ListModelMixin,
250+
viewsets.GenericViewSet,
251+
URLLargeImageViewMixin,
252+
):
253+
queryset = models.URLImageFile.objects.all()
254+
serializer_class = models.URLImageFileSerializer
255+
```
256256

257-
- endpoint or method to make / unmake a Django file field into a large_image item
258-
- fuse-like ability to access filefields as os-level files (until implemented, s3 files will need to be pulled locally to serve them, which is inefficient)
257+
Here is a good test image: https://oin-hotosm.s3.amazonaws.com/59c66c5223c8440011d7b1e4/0/7ad397c0-bba2-4f98-a08a-931ec3a6e943.tif

ROADMAP.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Roadmap
2+
3+
Our primary goal is to get through phases 1 and 2, focusing on tile serving of
4+
large geospatial images specifically in Cloud Optimized GeoTiff (COG) format.
5+
6+
### Phase 1
7+
8+
- [x] Abstract API View classes that can be mixed-in downstream to expose all available endpoints
9+
- [x] endpoints for metadata (/tiles, /tiles/internal_metadata)
10+
- [x] endpoints for serving tiles (/tiles/zxy, /tiles/fzxy)
11+
- [x] cache management - tile sources should be cached so that we don't open a file for each tile
12+
- [x] endpoint for regions
13+
- [x] endpoint for thumbnails
14+
- [x] thumbnail caching
15+
- [x] endpoint for individual pixels
16+
- [x] endpoint for histograms
17+
- [x] some diagnostic and settings endpoints (list available sources, set whether to automatically use large_images and the size of small images that can be used)
18+
- [x] Support for django's FileFeild
19+
- [x] Support for S3FileField
20+
- [x] Ship an easily extensible SSR template for tile viewing with CesiumJS
21+
- [x] Support for using file URLs with GDAL's VSI
22+
- [x] Provide OpenAPI documentation in swagger
23+
24+
### Phase 2
25+
26+
- [ ] Handle band/component selection styling for tile serving and thumbnails
27+
- e.g., use channels 3,7,5 for Red Green Blue
28+
- endable linear/discrete color modes
29+
- [ ] Tie large-image's caching into Django's cache (might require upstream work in large-image)
30+
- [ ] Provide some sort of endpoint to check if an image is a valid COG
31+
- [ ] Create a secondary app with celery tasks for converting images to COG
32+
- [ ] Refactor/prototpye RGD's ChecksumFile model as a FieldFile subclass
33+
- [ ] Support GeoDjango's [`GDALRaster`](https://docs.djangoproject.com/en/4.0/ref/contrib/gis/gdal/#django.contrib.gis.gdal.GDALRaster)
34+
35+
### Phase 3 and onward
36+
37+
Incorporate more features from large-image.
38+
39+
Things that would require implementing tasks with celery:
40+
41+
- [ ] ability to convert images via large_image_converter
42+
- [ ] async endpoint for regions
43+
44+
Things I'm unsure about:
45+
46+
- [ ] endpoints for associated images
47+
- [ ] ability to precache thumbnails (the thumbnail jobs endpoints)
48+
- [ ] endpoints for serving tiles in deepzoom format
49+
50+
Things I think should be implemented downstream:
51+
52+
- endpoint or method to make / unmake a Django file field into a large_image item
53+
- fuse-like ability to access filefields as os-level files (until implemented, s3 files will need to be pulled locally to serve them, which is inefficient)

project/example/core/admin.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from django.contrib import admin
2-
from example.core.models import ImageFile, S3ImageFile
2+
from example.core.models import ImageFile, S3ImageFile, URLImageFile
33

44

55
@admin.register(ImageFile)
@@ -12,3 +12,9 @@ class ImageFileAdmin(admin.ModelAdmin):
1212
class S3ImageFileAdmin(admin.ModelAdmin):
1313
list_display_links = ('pk', 'thumbnail')
1414
list_display = ('pk', 'thumbnail', 'metadata', 'internal_metadata')
15+
16+
17+
@admin.register(URLImageFile)
18+
class URLImageFileAdmin(admin.ModelAdmin):
19+
list_display_links = ('pk', 'thumbnail')
20+
list_display = ('pk', 'thumbnail', 'metadata', 'internal_metadata')
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Generated by Django 4.0.3 on 2022-04-02 00:20
2+
3+
from django.db import migrations, models
4+
import example.core.models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('core', '0004_alter_imagefile_id_alter_s3imagefile_id'),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='URLImageFile',
16+
fields=[
17+
(
18+
'id',
19+
models.BigAutoField(
20+
auto_created=True, primary_key=True, serialize=False, verbose_name='ID'
21+
),
22+
),
23+
('name', models.TextField()),
24+
('url', models.TextField()),
25+
],
26+
bases=(models.Model, example.core.models.Mixin),
27+
),
28+
]

project/example/core/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,16 @@ class S3ImageFileSerializer(serializers.ModelSerializer):
5555
class Meta:
5656
model = S3ImageFile
5757
fields = '__all__'
58+
59+
60+
class URLImageFile(models.Model, Mixin):
61+
name = models.TextField()
62+
url = models.TextField()
63+
64+
url_name = 'url-image-file'
65+
66+
67+
class URLImageFileSerializer(serializers.ModelSerializer):
68+
class Meta:
69+
model = URLImageFile
70+
fields = '__all__'
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{% extends "admin/change_form.html" %}
2+
3+
{% block after_field_sets %}
4+
5+
<script>
6+
var baseEndpoint = 'api/url-image-file';
7+
</script>
8+
9+
{% include 'admin/django_large_image/_include/geojs.html' %}
10+
11+
{% endblock %}

project/example/core/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
ImageFileDetailView,
55
S3ImageFileDetailView,
66
S3VSIImageFileDetailView,
7+
URLImageFileDetailView,
78
)
89
from rest_framework.routers import SimpleRouter
910

@@ -12,6 +13,7 @@
1213
router.register(r'api/vsi-image-file', ImageFileDetailView, basename='vsi-image-file')
1314
router.register(r'api/s3-image-file', S3ImageFileDetailView, basename='s3-image-file')
1415
router.register(r'api/s3-vsi-image-file', S3VSIImageFileDetailView, basename='s3-vsi-image-file')
16+
router.register(r'api/url-image-file', URLImageFileDetailView, basename='url-image-file')
1517

1618
urlpatterns = [
1719
path('image-file/<int:pk>/', views.ImageFileDetailView.as_view(), name='image-file-detail'),

project/example/core/viewsets.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from rest_framework import mixins, viewsets
33

44
from django_large_image.rest import LargeImageViewMixin, LargeImageVSIViewMixin
5+
from django_large_image.utilities import make_vsi
56

67

78
class ImageFileDetailView(
@@ -42,3 +43,18 @@ class S3VSIImageFileDetailView(
4243
queryset = models.S3ImageFile.objects.all()
4344
serializer_class = models.S3ImageFileSerializer
4445
FILE_FIELD_NAME = 'file'
46+
47+
48+
class S3URLLargeImageViewMixin(LargeImageViewMixin):
49+
def get_path(self, request, pk):
50+
object = self.get_object()
51+
return make_vsi(object.url)
52+
53+
54+
class URLImageFileDetailView(
55+
mixins.ListModelMixin,
56+
viewsets.GenericViewSet,
57+
S3URLLargeImageViewMixin,
58+
):
59+
queryset = models.URLImageFile.objects.all()
60+
serializer_class = models.URLImageFileSerializer

0 commit comments

Comments
 (0)