Skip to content

Commit 768a367

Browse files
authored
Merge pull request #149 from DeepMicroscopy/ImageInfo
Added basic image information and possibly meta information to viewer. Checked on local installation.
2 parents 4d52b61 + 686646b commit 768a367

File tree

7 files changed

+179
-1
lines changed

7 files changed

+179
-1
lines changed

exact/exact/annotations/static/annotations/js/exact-image-viewer.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class EXACTViewer {
3737
this.filterImage = new OpenseadragonFilteringViewer(this.viewer);
3838
this.pluginHandler = new PluginHandler(this.imageId, gHeaders, this.viewer);
3939
this.screeningTool = new ScreeningTool(imageInformation, user_id, gHeaders, this.viewer);
40+
this.imageProperties = new ShowImageProperties(this.viewer, this.imageId);
4041

4142
console.log(`${this.constructor.name} loaded for id ${this.imageId}`);
4243

@@ -841,7 +842,7 @@ class EXACTViewerLocalAnnotations extends EXACTViewer {
841842
this.asthmaAnalysis = new AsthmaAnalysis(this.imageId, this.viewer, this.exact_sync);
842843

843844
this.showAnnotationProperties = new ShowAnnotationProperties(this.viewer, this.exact_sync);
844-
845+
845846
let team_id = parseInt($('#team_id').html());
846847
this.teamTool = new TeamTool(this.viewer, team_id);
847848
this.processingTool = new ProcessingTool(this.viewer, this.imageId);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
function include_server_subdir(url) {
2+
sub_dir = window.location.pathname.split("/annotations/")[0]
3+
if (sub_dir === "") { return url } else { return sub_dir + url }
4+
}
5+
6+
7+
8+
class ShowImageProperties{
9+
constructor(viewer, imageId) {
10+
11+
this.viewer = viewer;
12+
this.imageId = imageId;
13+
14+
this.updateImageInfo(viewer, imageId);
15+
}
16+
17+
updateImageInfo(viewer, imageId) {
18+
// get current environment
19+
let gCsrfToken = $('[name="csrfmiddlewaretoken"]').first().val();
20+
21+
let gHeaders = {
22+
"Content-Type": 'application/json',
23+
"X-CSRFTOKEN": gCsrfToken
24+
};
25+
26+
let url = include_server_subdir('/images/api/image/metadata/'+imageId+'/');
27+
$.ajax(url, {
28+
type: 'GET', headers: gHeaders, dataType: 'json',
29+
success: function (data) {
30+
31+
let table = ""
32+
for (let [key,value] of Object.entries(data.meta_data)) {
33+
table += "<tr><td>"+data.meta_data_dict[key]+"</td><td>"+value+'</td></tr>'
34+
}
35+
$("#image_info_table").html(table)
36+
// window.dispatchEvent(new CustomEvent("sync_ProcessingJobListLoaded", {"detail": context}));
37+
},
38+
error: function (request, status, error) {
39+
if (request.responseText !== undefined) {
40+
$.notify(request.responseText, { position: "bottom center", className: "error" });
41+
} else {
42+
$.notify(`Server ERR_CONNECTION_TIMED_OUT`, { position: "bottom center", className: "error" });
43+
}
44+
}
45+
});
46+
47+
48+
}
49+
}

exact/exact/annotations/templates/annotations/annotate.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<script type="text/javascript" src="{% static 'annotations/js/statistics-viewer.js' %}"></script>
4040
<script type="text/javascript" src="{% static 'annotations/js/plugin-handler.js' %}"></script>
4141
<script type="text/javascript" src="{% static 'annotations/js/show-annotation-properties.js' %}"></script>
42+
<script type="text/javascript" src="{% static 'annotations/js/show-image-properties.js' %}"></script>
4243
<script type="text/javascript" src="{% static 'annotations/js/exact-browser-sync.js' %}"></script>
4344
<script type="text/javascript" src="{% static 'annotations/js/exact-quad-tree.js' %}"></script>
4445
<script type="text/javascript" src="{% static 'annotations/js/exact-overlay-opacity.js' %}"></script>
@@ -176,6 +177,10 @@ <h5 id="active_image_name">{{ selected_image.name }}</h5>
176177
role="tab" aria-controls="home">Annotation</a>
177178
</li>
178179

180+
<li class="nav-item">
181+
<a class="nav-link" id="default_info_tab" data-toggle="tab" href="#nav-item_info"
182+
role="tab" aria-controls="home">Info</a>
183+
</li>
179184
{% if HasMediaFiles %}
180185

181186
<li class="nav-item">
@@ -299,7 +304,19 @@ <h5 id="active_image_name">{{ selected_image.name }}</h5>
299304
</div>
300305

301306
{% endif %}
307+
<div class="tab-pane fade" id="nav-item_info">
308+
<div id="ImageInformation">
309+
<table style="width: 100%; margin-top: 10px; text-align: center" id="image_info_table">
310+
<tr>
311+
<th></th>
312+
<th></th>
313+
<th>Remark</th>
314+
</tr>
315+
</table>
316+
</div>
302317

318+
319+
</div>
303320
<div class="tab-pane fade" id="nav-item_anno">
304321

305322
<div id="AnnotationInformation">

exact/exact/annotations/templates/annotations/annotate_v2.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
<script type="text/javascript" src="{% static 'annotations/js/statistics-viewer.js' %}"></script>
4040
<script type="text/javascript" src="{% static 'annotations/js/plugin-handler.js' %}"></script>
4141
<script type="text/javascript" src="{% static 'annotations/js/show-annotation-properties.js' %}"></script>
42+
<script type="text/javascript" src="{% static 'annotations/js/show-image-properties.js' %}"></script>
43+
4244
<script type="text/javascript" src="{% static 'annotations/js/exact-browser-sync.js' %}"></script>
4345
<script type="text/javascript" src="{% static 'annotations/js/exact-quad-tree.js' %}"></script>
4446
<script type="text/javascript" src="{% static 'annotations/js/annotations.js' %}"></script>
@@ -178,6 +180,12 @@ <h5 id="active_image_name">{{ selected_image.name }}</h5>
178180
role="tab" aria-controls="home">Annotation</a>
179181
</li>
180182

183+
<li class="nav-item">
184+
<a class="nav-link" id="default_info_tab" data-toggle="tab" href="#nav-item_info"
185+
role="tab" aria-controls="home">Info</a>
186+
</li>
187+
188+
181189
{% if HasMediaFiles %}
182190

183191
<li class="nav-item">
@@ -302,6 +310,20 @@ <h5 id="active_image_name">{{ selected_image.name }}</h5>
302310

303311
{% endif %}
304312

313+
<div class="tab-pane fade" id="nav-item_info">
314+
<div id="ImageInformation">
315+
<table style="width: 100%; margin-top: 10px; text-align: center" id="image_info_table">
316+
<tr>
317+
<th></th>
318+
<th></th>
319+
<th>Remark</th>
320+
</tr>
321+
</table>
322+
</div>
323+
324+
325+
</div>
326+
305327
<div class="tab-pane fade" id="nav-item_anno">
306328

307329
<div id="AnnotationInformation">

exact/exact/images/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
re_path(r'^image/(\d+)/(\d+)/(\d+)/tile_files/(\d+)/(\d+_\d+.(?:png|jpeg))$', views.view_image_tile, name='view_image_tile'),
2727
re_path(r'^api/image/verify/$', views.api_verify_image, name='verify_image'),
2828

29+
re_path(r'^api/image/metadata/(\d+)/$', views.image_metadata, name='metadata'),
2930
re_path(r'^api/image/plugins/$', views.image_plugins, name='plugins'),
3031
re_path(r'^image/centered_snapshot/(\d+)/(\d+)/(\d+)/(\d+)/(\d+)/', views.image_snapshots, name='centered_image_snapshot'),
3132

exact/exact/images/views.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,53 @@ def view_image(request, image_id, z_dimension:int=1, frame:int=1):
426426
cache.set(cache_key, value, None)
427427
return HttpResponse(value, content_type='application/xml')
428428

429+
@login_required
430+
@api_view(['GET'])
431+
def image_metadata(request, image_id) -> Response:
432+
image = get_object_or_404(Image, pk=image_id)
433+
if not image.image_set.has_perm('read', request.user):
434+
return Response({
435+
'detail': 'permission for reading this image set missing.',
436+
}, status=HTTP_403_FORBIDDEN)
437+
438+
meta_data = {}
439+
# descriptions in plain english
440+
meta_data_dict = {'level_count': 'Image levels',
441+
'image_size': 'Image size',
442+
'numberOfFrames': 'Number of frames',
443+
'frame_type': 'Frame type',
444+
'mpp_x': 'x Resolution (microns/px)',
445+
'mpp_y': 'y Resolution (microns/px)',
446+
}
447+
448+
try:
449+
slideobj = image_cache.get(image.path())
450+
451+
meta_data['level_count'] = slideobj.level_count
452+
meta_data['image_size'] = ' x '.join([str(x) for x in slideobj.dimensions])
453+
meta_data['numberOfFrames'] = slideobj.nFrames
454+
meta_data['mpp_x'] = slideobj.mpp_x
455+
meta_data['mpp_y'] = slideobj.mpp_y
456+
457+
if (slideobj.nFrames>1):
458+
meta_data['frame_type'] = slideobj.frame_type
459+
460+
meta_data.update(slideobj.meta_data)
461+
meta_data_dict.update(slideobj.meta_data_dict)
462+
463+
except Exception as e:
464+
logger.error(str(e))
465+
466+
return Response({
467+
'detail': 'Unable to retrieve basic metadata.',
468+
}, status=HTTP_403_FORBIDDEN)
469+
470+
return Response({
471+
'meta_data': meta_data,
472+
'meta_data_dict': meta_data_dict,
473+
}, status=HTTP_200_OK)
474+
475+
429476
@login_required
430477
@api_view(['GET'])
431478
def image_plugins(request) -> Response:

exact/util/slide_server.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,44 @@ def get_tile(self, level, address, frame=0):
5656
tile.info['icc_profile'] = profile
5757

5858
return tile
59+
60+
@property
61+
def dimensions(self):
62+
if hasattr(self._osr,'dimensions'):
63+
return self._osr.dimensions
64+
else:
65+
return [0]
66+
67+
@property
68+
def meta_data(self):
69+
if hasattr(self._osr,'meta_data'):
70+
return self._osr.meta_data
71+
else:
72+
return {}
73+
74+
@property
75+
def meta_data_dict(self):
76+
if hasattr(self._osr,'meta_data_dict'):
77+
return self._osr.meta_data_dict
78+
else:
79+
return {}
5980

81+
@property
82+
def nFrames(self):
83+
if hasattr(self._osr,'nFrames'):
84+
return self._osr.nFrames
85+
else:
86+
return None
87+
88+
@property
89+
def frame_type(self):
90+
if hasattr(self._osr,'frame_type'):
91+
return self._osr.frame_type
92+
else:
93+
return None
94+
95+
96+
6097
class OpenSlideWrapper(openslide.OpenSlide):
6198
"""
6299
Wraps an openslide.OpenSlide object. The rationale here is that OpenSlide.read_region does not support z Stacks / frames as arguments, hence we have to encapsulate it
@@ -523,8 +560,12 @@ def get(self, path):
523560
mpp_x = osr.properties[openslide.PROPERTY_NAME_MPP_X]
524561
mpp_y = osr.properties[openslide.PROPERTY_NAME_MPP_Y]
525562
slide.mpp = (float(mpp_x) + float(mpp_y)) / 2
563+
slide.mpp_x = mpp_x
564+
slide.mpp_y = mpp_y
526565
except (KeyError, ValueError):
527566
slide.mpp = 0
567+
slide.mpp_x = 0
568+
slide.mpp_y = 0
528569

529570
with self._lock:
530571
if path not in self._cache:

0 commit comments

Comments
 (0)