girder-large-image 1.22.3.dev18__tar.gz → 1.22.3.dev26__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/PKG-INFO +1 -1
  2. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/rest/tiles.py +8 -0
  3. girder-large-image-1.22.3.dev26/girder_large_image/web_client/stylesheets/metadataWidget.styl +5 -0
  4. girder-large-image-1.22.3.dev26/girder_large_image/web_client/templates/metadataWidget.pug +22 -0
  5. girder-large-image-1.22.3.dev26/girder_large_image/web_client/templates/metadatumEditWidget.pug +30 -0
  6. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/configView.js +4 -4
  7. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/index.js +3 -1
  8. girder-large-image-1.22.3.dev26/girder_large_image/web_client/views/metadataWidget.js +672 -0
  9. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image.egg-info/PKG-INFO +1 -1
  10. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image.egg-info/SOURCES.txt +4 -0
  11. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image.egg-info/requires.txt +2 -2
  12. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/test_girder/test_tiles_rest.py +13 -0
  13. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/LICENSE +0 -0
  14. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/README.rst +0 -0
  15. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/__init__.py +0 -0
  16. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/constants.py +0 -0
  17. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/girder_tilesource.py +0 -0
  18. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/loadmodelcache.py +0 -0
  19. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/models/__init__.py +0 -0
  20. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/models/image_item.py +0 -0
  21. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/rest/__init__.py +0 -0
  22. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/rest/large_image_resource.py +0 -0
  23. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/eventStream.js +0 -0
  24. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/extra/.gitignore +0 -0
  25. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/index.js +0 -0
  26. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/main.js +0 -0
  27. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/package.json +0 -0
  28. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/routes.js +0 -0
  29. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/stylesheets/fileList.styl +0 -0
  30. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/stylesheets/imageViewerSelectWidget.styl +0 -0
  31. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/stylesheets/itemList.styl +0 -0
  32. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/stylesheets/itemView.styl +0 -0
  33. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/stylesheets/itemViewCodemirror.styl +0 -0
  34. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/stylesheets/largeImageConfig.styl +0 -0
  35. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/templates/imageViewerSelectWidget.pug +0 -0
  36. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/templates/itemList.pug +0 -0
  37. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/templates/itemView.pug +0 -0
  38. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/templates/itemViewCodemirror.pug +0 -0
  39. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/templates/largeImageConfig.pug +0 -0
  40. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/templates/largeImage_fileAction.pug +0 -0
  41. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/fileList.js +0 -0
  42. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/imageViewerSelectWidget.js +0 -0
  43. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/imageViewerWidget/base.js +0 -0
  44. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/imageViewerWidget/geojs.js +0 -0
  45. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/imageViewerWidget/index.js +0 -0
  46. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/imageViewerWidget/leaflet.js +0 -0
  47. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/imageViewerWidget/openlayers.js +0 -0
  48. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/imageViewerWidget/openseadragon.js +0 -0
  49. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/imageViewerWidget/setFrameQuad.js +0 -0
  50. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/imageViewerWidget/slideatlas.js +0 -0
  51. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/itemList.js +0 -0
  52. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/itemView.js +0 -0
  53. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/itemViewCodemirror.js +0 -0
  54. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/views/itemViewWidget.js +0 -0
  55. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/vue/colors.js +0 -0
  56. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/vue/components/CompositeLayers.vue +0 -0
  57. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/vue/components/DualInput.vue +0 -0
  58. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/vue/components/FrameSelector.vue +0 -0
  59. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image/web_client/webpack.helper.js +0 -0
  60. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image.egg-info/dependency_links.txt +0 -0
  61. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image.egg-info/entry_points.txt +0 -0
  62. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image.egg-info/not-zip-safe +0 -0
  63. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/girder_large_image.egg-info/top_level.txt +0 -0
  64. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/setup.cfg +0 -0
  65. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/setup.py +0 -0
  66. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/test_girder/__init__.py +0 -0
  67. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/test_girder/conftest.py +0 -0
  68. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/test_girder/girder_utilities.py +0 -0
  69. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/test_girder/test_large_image.py +0 -0
  70. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/test_girder/test_web_client.py +0 -0
  71. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/test_girder/web_client_specs/.eslintrc +0 -0
  72. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/test_girder/web_client_specs/imageViewerSpec.js +0 -0
  73. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/test_girder/web_client_specs/largeImageSpec.js +0 -0
  74. {girder-large-image-1.22.3.dev18 → girder-large-image-1.22.3.dev26}/test_girder/web_client_specs/otherFeatures.js +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: girder-large-image
3
- Version: 1.22.3.dev18
3
+ Version: 1.22.3.dev26
4
4
  Summary: A Girder plugin to work with large, multiresolution images.
5
5
  Home-page: https://github.com/girder/large_image
6
6
  Author: Kitware, Inc.
@@ -985,6 +985,10 @@ class TilesItemResource(ItemResource):
985
985
  .param('rangeMax', 'The maximum value in the histogram. Defaults to '
986
986
  'the maximum value in the image.',
987
987
  required=False, dataType='float')
988
+ .param('roundRange', 'If true and neither a minimum or maximum is '
989
+ 'specified for the range, round the bin edges and adjust the '
990
+ 'number of bins for integer data with smaller ranges.',
991
+ required=False, dataType='boolean', default=False)
988
992
  .param('density', 'If true, scale the results by the number of '
989
993
  'samples.', required=False, dataType='boolean', default=False)
990
994
  .errorResponse('ID was invalid.')
@@ -1020,12 +1024,16 @@ class TilesItemResource(ItemResource):
1020
1024
  ('bins', int),
1021
1025
  ('rangeMin', int),
1022
1026
  ('rangeMax', int),
1027
+ ('roundRange', bool),
1023
1028
  ('density', bool),
1024
1029
  ])
1025
1030
  _handleETag('getHistogram', item, params)
1026
1031
  histRange = None
1027
1032
  if 'rangeMin' in params or 'rangeMax' in params:
1028
1033
  histRange = [params.pop('rangeMin', 0), params.pop('rangeMax', 256)]
1034
+ if params.get('roundRange'):
1035
+ if params.pop('roundRange', False) and histRange is None:
1036
+ histRange = 'round'
1029
1037
  result = self.imageItemModel.histogram(item, range=histRange, **params)
1030
1038
  result = result['histogram']
1031
1039
  # Cast everything to lists and floats so json with encode properly
@@ -0,0 +1,5 @@
1
+ .g-widget-metadata-key-edit
2
+ font-weight bold
3
+
4
+ .g-widget-metadata-row.editing .g-widget-metadata-value-input.g-widget-metadata-lientry
5
+ height inherit
@@ -0,0 +1,22 @@
1
+ if (accessLevel >= AccessType.WRITE)
2
+ .btn-group.pull-right
3
+ button.g-widget-metadata-add-button.btn.btn-sm.btn-primary.btn-default.dropdown-toggle(data-toggle="dropdown", title="Add Metadata")
4
+ i.icon-plus
5
+ ul.dropdown-menu.pull-right(role="menu")
6
+ if limetadata
7
+ for entry in limetadata
8
+ if entry && entry.value
9
+ li.li-metadata-menuitem(role="presentation")
10
+ a.li-add-metadata(metadata-key=entry.value)
11
+ = entry.title || entry.value
12
+ li(role="presentation")
13
+ a.g-add-simple-metadata(role="menuitem")
14
+ | Simple
15
+ li(role="presentation")
16
+ a.g-add-json-metadata
17
+ | JSON
18
+
19
+ .g-widget-metadata-header
20
+ i.icon-tags
21
+ | #{title}
22
+ .g-widget-metadata-container
@@ -0,0 +1,30 @@
1
+ if !lientry
2
+ input.input-sm.form-control.g-widget-metadata-key-input(type="text", value=key, placeholder="Key")
3
+ else
4
+ span.g-widget-metadata-key-edit.g-widget-metadata-key-input(key=key)
5
+ = lientry.title || key
6
+
7
+ if !lientry
8
+ - var rows = value.length <= 40 ? 1 : (value.length <= 100 ? 3 : 5)
9
+ textarea.input-sm.form-control.g-widget-metadata-value-input(placeholder="Value", rows=rows)
10
+ = value
11
+ else
12
+ if lientry.enum
13
+ select.input-sm.form-control.g-widget-metadata-value-input.g-widget-metadata-lientry(title=lientry.description)
14
+ for enumval in lientry.enum
15
+ option(value=enumval, selected=enumval === value ? 'selected' : null)
16
+ = enumval
17
+ else
18
+ input.input-sm.form-control.g-widget-metadata-value-input.g-widget-metadata-lientry(placeholder=lientry.description || "Value", value=value, title=lientry.description)
19
+
20
+ button.btn.btn-sm.btn-warning.g-widget-metadata-cancel-button(title="Cancel")
21
+ i.icon-cancel
22
+ button.btn.btn-sm.btn-primary.g-widget-metadata-save-button(title="Accept")
23
+ i.icon-ok
24
+ if !newDatum
25
+ if !lientry
26
+ button.btn.btn-sm.btn-primary.g-widget-metadata-toggle-button(title="Convert to JSON")
27
+ i.icon-cog
28
+ if !lientry || !lientry.required
29
+ button.btn.btn-sm.btn-danger.g-widget-metadata-delete-button(title="Delete")
30
+ i.icon-trash
@@ -247,15 +247,15 @@ var ConfigView = View.extend({
247
247
  }
248
248
  if (ConfigView._lastliconfig === folderId && !reload) {
249
249
  if (callback) {
250
- callback(ConfigView._liconfig);
250
+ callback(ConfigView._liconfig || {});
251
251
  }
252
252
  return $.Deferred().resolve(ConfigView._liconfig);
253
253
  }
254
254
  if (ConfigView._liconfigSettingsRequest) {
255
255
  if (ConfigView._nextliconfig === folderId) {
256
256
  if (callback) {
257
- ConfigView._liconfigSettingsRequest.done(() => {
258
- callback(ConfigView._liconfig);
257
+ ConfigView._liconfigSettingsRequest.done((val) => {
258
+ callback(val || {});
259
259
  });
260
260
  }
261
261
  return ConfigView._liconfigSettingsRequest;
@@ -269,7 +269,7 @@ var ConfigView = View.extend({
269
269
  val = val || {};
270
270
  ConfigView._lastliconfig = folderId;
271
271
  ConfigView._liconfigSettingsRequest = null;
272
- ConfigView._liconfig = val || {};
272
+ ConfigView._liconfig = val;
273
273
  if (callback) {
274
274
  callback(ConfigView._liconfig);
275
275
  }
@@ -2,10 +2,12 @@ import ConfigView from './configView';
2
2
  import ImageViewerSelectWidget from './imageViewerSelectWidget';
3
3
  import * as imageViewerWidget from './imageViewerWidget';
4
4
  import ItemViewWidget from './itemViewWidget';
5
+ import * as MetadataWidget from './metadataWidget';
5
6
 
6
7
  export {
7
8
  ConfigView,
8
9
  ImageViewerSelectWidget,
9
10
  imageViewerWidget,
10
- ItemViewWidget
11
+ ItemViewWidget,
12
+ MetadataWidget
11
13
  };
@@ -0,0 +1,672 @@
1
+ import $ from 'jquery';
2
+ import {wrap} from '@girder/core/utilities/PluginUtils';
3
+
4
+ import _ from 'underscore';
5
+
6
+ import View from '@girder/core/views/View';
7
+ import {AccessType} from '@girder/core/constants';
8
+ import {confirm} from '@girder/core/dialog';
9
+ import events from '@girder/core/events';
10
+ import {localeSort} from '@girder/core/misc';
11
+
12
+ import JsonMetadatumEditWidgetTemplate from '@girder/core/templates/widgets/jsonMetadatumEditWidget.pug';
13
+
14
+ import MetadatumViewTemplate from '@girder/core/templates/widgets/metadatumView.pug';
15
+
16
+ import '@girder/core/stylesheets/widgets/metadataWidget.styl';
17
+
18
+ import JSONEditor from 'jsoneditor/dist/jsoneditor.js'; // can't 'jsoneditor'
19
+ import 'jsoneditor/dist/jsoneditor.css';
20
+
21
+ import 'bootstrap/js/dropdown';
22
+
23
+ import MetadataWidget from '@girder/core/views/widgets/MetadataWidget';
24
+
25
+ import '../stylesheets/metadataWidget.styl';
26
+ import MetadataWidgetTemplate from '../templates/metadataWidget.pug';
27
+ import MetadatumEditWidgetTemplate from '../templates/metadatumEditWidget.pug';
28
+ import largeImageConfig from './configView';
29
+
30
+ function getMetadataRecord(item, fieldName) {
31
+ if (item[fieldName]) {
32
+ return item[fieldName];
33
+ }
34
+ let meta = item.attributes;
35
+ fieldName.split('.').forEach((part) => {
36
+ if (!meta[part]) {
37
+ meta[part] = {};
38
+ }
39
+ meta = meta[part];
40
+ });
41
+ return meta;
42
+ }
43
+
44
+ function liMetadataKeyEntry(limetadata, key) {
45
+ if (!limetadata || !key) {
46
+ return;
47
+ }
48
+ let result;
49
+ limetadata.forEach((entry, idx) => {
50
+ if (entry.value === key) {
51
+ result = entry;
52
+ result.idx = idx;
53
+ }
54
+ });
55
+ return result;
56
+ }
57
+
58
+ var MetadatumWidget = View.extend({
59
+ className: 'g-widget-metadata-row',
60
+
61
+ events: {
62
+ 'click .g-widget-metadata-edit-button': 'editMetadata'
63
+ },
64
+
65
+ initialize: function (settings) {
66
+ if (!_.has(this.parentView.modes, settings.mode)) {
67
+ throw new Error('Unsupported metadatum mode ' + settings.mode + ' detected.');
68
+ }
69
+
70
+ this.mode = settings.mode;
71
+ this.key = settings.key;
72
+ this.value = settings.value;
73
+ this.accessLevel = settings.accessLevel;
74
+ this.parentView = settings.parentView;
75
+ this.fieldName = settings.fieldName;
76
+ this.apiPath = settings.apiPath;
77
+ this.noSave = settings.noSave;
78
+ this.limetadata = settings.limetadata;
79
+ this.onMetadataEdited = settings.onMetadataEdited;
80
+ this.onMetadataAdded = settings.onMetadataAdded;
81
+ },
82
+
83
+ _validate: function (from, to, value) {
84
+ var newMode = this.parentView.modes[to];
85
+
86
+ if (_.has(newMode, 'validation') &&
87
+ _.has(newMode.validation, 'from') &&
88
+ _.has(newMode.validation.from, from)) {
89
+ var validate = newMode.validation.from[from][0];
90
+ var msg = newMode.validation.from[from][1];
91
+
92
+ if (!validate(value)) {
93
+ events.trigger('g:alert', {
94
+ text: msg,
95
+ type: 'warning'
96
+ });
97
+ return false;
98
+ }
99
+ }
100
+
101
+ return true;
102
+ },
103
+
104
+ // @todo too much duplication with editMetadata
105
+ toggleEditor: function (event, newEditorMode, existingEditor, overrides) {
106
+ var fromEditorMode = (existingEditor instanceof JsonMetadatumEditWidget) ? 'json' : 'simple';
107
+ var newValue = (overrides || {}).value || existingEditor.$el.attr('g-value');
108
+ if (!this._validate(fromEditorMode, newEditorMode, newValue)) {
109
+ return false;
110
+ }
111
+
112
+ var row = existingEditor.$el;
113
+ existingEditor.destroy();
114
+ row.addClass('editing').empty();
115
+
116
+ var opts = _.extend({
117
+ el: row,
118
+ item: this.parentView.item,
119
+ key: row.attr('g-key'),
120
+ value: row.attr('g-value'),
121
+ accessLevel: this.accessLevel,
122
+ newDatum: false,
123
+ parentView: this,
124
+ fieldName: this.fieldName,
125
+ apiPath: this.apiPath,
126
+ noSave: this.noSave,
127
+ limetadata: this.limetadata,
128
+ onMetadataEdited: this.onMetadataEdited,
129
+ onMetadataAdded: this.onMetadataAdded
130
+ }, overrides || {});
131
+
132
+ this.parentView.modes[newEditorMode].editor(opts).render();
133
+ },
134
+
135
+ editMetadata: function (event) {
136
+ this.$el.addClass('editing');
137
+ this.$el.empty();
138
+
139
+ var opts = {
140
+ item: this.parentView.item,
141
+ key: this.$el.attr('g-key'),
142
+ value: this.$el.attr('g-value'),
143
+ accessLevel: this.accessLevel,
144
+ newDatum: false,
145
+ parentView: this,
146
+ fieldName: this.fieldName,
147
+ apiPath: this.apiPath,
148
+ noSave: this.noSave,
149
+ limetadata: this.limetadata,
150
+ onMetadataEdited: this.onMetadataEdited,
151
+ onMetadataAdded: this.onMetadataAdded
152
+ };
153
+
154
+ // If they're trying to open false, null, 6, etc which are not stored as strings
155
+ if (this.mode === 'json') {
156
+ try {
157
+ var jsonValue = JSON.parse(this.$el.attr('g-value'));
158
+
159
+ if (jsonValue !== undefined && !_.isObject(jsonValue)) {
160
+ opts.value = jsonValue;
161
+ }
162
+ } catch (e) {}
163
+ }
164
+
165
+ this.parentView.modes[this.mode].editor(opts)
166
+ .render()
167
+ .$el.appendTo(this.$el);
168
+ },
169
+
170
+ render: function () {
171
+ this.$el.attr({
172
+ 'g-key': this.key,
173
+ 'g-value': _.bind(this.parentView.modes[this.mode].displayValue, this)()
174
+ }).empty();
175
+ this.$el.removeClass('editing');
176
+ this.$el.html(this.parentView.modes[this.mode].template({
177
+ key: this.mode === 'key' && liMetadataKeyEntry(this.limetadata, this.key) ? liMetadataKeyEntry(this.limetadata, this.key).title || this.key : this.key,
178
+ value: _.bind(this.parentView.modes[this.mode].displayValue, this)(),
179
+ accessLevel: this.accessLevel,
180
+ AccessType
181
+ }));
182
+
183
+ return this;
184
+ }
185
+ });
186
+
187
+ var MetadatumEditWidget = View.extend({
188
+ events: {
189
+ 'click .g-widget-metadata-cancel-button': 'cancelEdit',
190
+ 'click .g-widget-metadata-save-button': 'save',
191
+ 'click .g-widget-metadata-delete-button': 'deleteMetadatum',
192
+ 'click .g-widget-metadata-toggle-button': function (event) {
193
+ var editorType;
194
+ // @todo modal
195
+ // in the future this event will have the new editorType (assuming a dropdown)
196
+ if (this instanceof JsonMetadatumEditWidget) {
197
+ editorType = 'simple';
198
+ } else {
199
+ editorType = 'json';
200
+ }
201
+
202
+ this.parentView.toggleEditor(event, editorType, this, {
203
+ // Save state before toggling editor
204
+ key: this.$el.find('.g-widget-metadata-key-input').val(),
205
+ value: this.getCurrentValue()
206
+ });
207
+ return false;
208
+ }
209
+ },
210
+
211
+ initialize: function (settings) {
212
+ this.item = settings.item;
213
+ this.key = settings.key || '';
214
+ this.fieldName = settings.fieldName || 'meta';
215
+ this.value = (settings.value !== undefined) ? settings.value : '';
216
+ this.accessLevel = settings.accessLevel;
217
+ this.newDatum = settings.newDatum;
218
+ this.fieldName = settings.fieldName;
219
+ this.apiPath = settings.apiPath;
220
+ this.noSave = settings.noSave;
221
+ this.limetadata = settings.limetadata;
222
+ this.onMetadataEdited = settings.onMetadataEdited;
223
+ this.onMetadataAdded = settings.onMetadataAdded;
224
+ },
225
+
226
+ editTemplate: MetadatumEditWidgetTemplate,
227
+
228
+ getCurrentValue: function () {
229
+ return this.$el.find('.g-widget-metadata-value-input').val();
230
+ },
231
+
232
+ deleteMetadatum: function (event) {
233
+ event.stopImmediatePropagation();
234
+ const target = $(event.currentTarget);
235
+ var metadataList = target.parent().parent();
236
+ if (this.noSave) {
237
+ delete getMetadataRecord(this.item, this.fieldName)[this.key];
238
+ metadataList.remove();
239
+ return;
240
+ }
241
+ var params = {
242
+ text: 'Are you sure you want to delete the metadatum <b>' +
243
+ _.escape(this.key) + '</b>?',
244
+ escapedHtml: true,
245
+ yesText: 'Delete',
246
+ confirmCallback: () => {
247
+ this.item.removeMetadata(this.key, function () {
248
+ metadataList.remove();
249
+ this.parentView.parentView.trigger('li-metadata-widget-update', {});
250
+ }, null, {
251
+ field: this.fieldName,
252
+ path: this.apiPath
253
+ });
254
+ }
255
+ };
256
+ confirm(params);
257
+ },
258
+
259
+ cancelEdit: function (event) {
260
+ event.stopImmediatePropagation();
261
+ const target = $(event.currentTarget);
262
+ var curRow = target.parent().parent();
263
+ if (this.newDatum) {
264
+ curRow.remove();
265
+ } else {
266
+ this.parentView.render();
267
+ }
268
+ },
269
+
270
+ save: function (event, value) {
271
+ event.stopImmediatePropagation();
272
+ const target = $(event.currentTarget);
273
+ var curRow = target.parent(),
274
+ tempKey = curRow.find('.g-widget-metadata-key-input').val().trim() || curRow.find('.g-widget-metadata-key-input').attr('key'),
275
+ keyMode = curRow.find('.g-widget-metadata-key-input').attr('key'),
276
+ tempValue = (value !== undefined) ? value : curRow.find('.g-widget-metadata-value-input').val();
277
+
278
+ if (this.newDatum && tempKey === '') {
279
+ events.trigger('g:alert', {
280
+ text: 'A key is required for all metadata.',
281
+ type: 'warning'
282
+ });
283
+ return false;
284
+ }
285
+ const lientry = keyMode ? liMetadataKeyEntry(this.limetadata, this.key) : undefined;
286
+ if (keyMode && lientry) {
287
+ tempValue = tempValue.trim();
288
+ }
289
+ if (lientry && lientry.regex && !(new RegExp(lientry.regex).exec(tempValue))) {
290
+ events.trigger('g:alert', {
291
+ text: 'The value does not match the required format.',
292
+ type: 'warning'
293
+ });
294
+ return false;
295
+ }
296
+ if (lientry && (lientry.type === 'number' || lientry.type === 'integer')) {
297
+ if (!Number.isFinite(parseFloat(tempValue)) || (lientry.type === 'integer' && !Number.isInteger(parseFloat(tempValue)))) {
298
+ events.trigger('g:alert', {
299
+ text: `The value must be a ${lientry.type}.`,
300
+ type: 'warning'
301
+ });
302
+ return false;
303
+ }
304
+ tempValue = parseFloat(tempValue);
305
+ if ((lientry.minimum !== undefined && tempValue < lientry.minimum) ||
306
+ (lientry.exclusiveMinimum !== undefined && tempValue <= lientry.exclusiveMinimum) ||
307
+ (lientry.maximum !== undefined && tempValue > lientry.maximum) ||
308
+ (lientry.exclusiveMaximum !== undefined && tempValue >= lientry.exclusiveMaximum)) {
309
+ events.trigger('g:alert', {
310
+ text: 'The value is outside of the allowed range.',
311
+ type: 'warning'
312
+ });
313
+ return false;
314
+ }
315
+ }
316
+
317
+ var saveCallback = () => {
318
+ this.key = tempKey;
319
+ this.value = tempValue;
320
+
321
+ this.parentView.key = this.key;
322
+ this.parentView.value = this.value;
323
+
324
+ if (keyMode) {
325
+ this.parentView.mode = 'key';
326
+ } else if (this instanceof JsonMetadatumEditWidget) {
327
+ this.parentView.mode = 'json';
328
+ } else {
329
+ this.parentView.mode = 'simple';
330
+ }
331
+ // event to re-render metadata panel header when metadata is edited
332
+ this.parentView.parentView.trigger('li-metadata-widget-update', {});
333
+ this.parentView.render();
334
+
335
+ this.newDatum = false;
336
+ };
337
+
338
+ var errorCallback = function (out) {
339
+ events.trigger('g:alert', {
340
+ text: out.message,
341
+ type: 'danger'
342
+ });
343
+ };
344
+
345
+ if (this.newDatum) {
346
+ if (this.onMetadataAdded) {
347
+ this.onMetadataAdded(tempKey, tempValue, saveCallback, errorCallback);
348
+ } else {
349
+ if (this.noSave) {
350
+ if (getMetadataRecord(this.item, this.fieldName)[tempKey] !== undefined) {
351
+ events.trigger('g:alert', {
352
+ text: tempKey + ' is already a metadata key',
353
+ type: 'warning'
354
+ });
355
+ return false;
356
+ }
357
+ getMetadataRecord(this.item, this.fieldName)[tempKey] = tempValue;
358
+ this.parentView.parentView.render();
359
+ }
360
+ this.item.addMetadata(tempKey, tempValue, saveCallback, errorCallback, {
361
+ field: this.fieldName,
362
+ path: this.apiPath
363
+ });
364
+ }
365
+ } else {
366
+ if (this.onMetadataEdited) {
367
+ this.onMetadataEdited(tempKey, this.key, tempValue, saveCallback, errorCallback);
368
+ } else {
369
+ if (this.noSave) {
370
+ tempKey = tempKey === '' ? this.key : tempKey;
371
+ if (tempKey !== this.key && getMetadataRecord(this.item, this.fieldName)[tempKey] !== undefined) {
372
+ events.trigger('g:alert', {
373
+ text: tempKey + ' is already a metadata key',
374
+ type: 'warning'
375
+ });
376
+ return false;
377
+ }
378
+ delete getMetadataRecord(this.item, this.fieldName)[this.key];
379
+ getMetadataRecord(this.item, this.fieldName)[tempKey] = tempValue;
380
+ this.parentView.parentView.render();
381
+ return;
382
+ }
383
+ this.item.editMetadata(tempKey, this.key, tempValue, saveCallback, errorCallback, {
384
+ field: this.fieldName,
385
+ path: this.apiPath
386
+ });
387
+ }
388
+ }
389
+ },
390
+
391
+ render: function () {
392
+ this.$el.html(this.editTemplate({
393
+ item: this.item,
394
+ lientry: liMetadataKeyEntry(this.limetadata, this.key),
395
+ key: this.key,
396
+ value: this.value,
397
+ accessLevel: this.accessLevel,
398
+ newDatum: this.newDatum,
399
+ AccessType
400
+ }));
401
+ this.$el.find('.g-widget-metadata-key-input').trigger('focus');
402
+
403
+ return this;
404
+ }
405
+ });
406
+
407
+ var JsonMetadatumEditWidget = MetadatumEditWidget.extend({
408
+ editTemplate: JsonMetadatumEditWidgetTemplate,
409
+
410
+ getCurrentValue: function () {
411
+ return this.editor.getText();
412
+ },
413
+
414
+ save: function (event) {
415
+ try {
416
+ return MetadatumEditWidget.prototype.save.call(
417
+ this, event, this.editor.get());
418
+ } catch (err) {
419
+ events.trigger('g:alert', {
420
+ text: 'The field contains invalid JSON and can not be saved.',
421
+ type: 'warning'
422
+ });
423
+ return false;
424
+ }
425
+ },
426
+
427
+ render: function () {
428
+ MetadatumEditWidget.prototype.render.apply(this, arguments);
429
+
430
+ const jsonEditorEl = this.$el.find('.g-json-editor');
431
+ this.editor = new JSONEditor(jsonEditorEl[0], {
432
+ mode: 'tree',
433
+ modes: ['code', 'tree'],
434
+ onError: () => {
435
+ events.trigger('g:alert', {
436
+ text: 'The field contains invalid JSON and can not be viewed in Tree Mode.',
437
+ type: 'warning'
438
+ });
439
+ }
440
+ });
441
+
442
+ if (this.value !== undefined) {
443
+ this.editor.setText(JSON.stringify(this.value));
444
+ this.editor.expandAll();
445
+ }
446
+
447
+ return this;
448
+ }
449
+ });
450
+
451
+ wrap(MetadataWidget, 'initialize', function (initialize, settings) {
452
+ try {
453
+ initialize.call(this, settings);
454
+ } catch (err) {
455
+ }
456
+ this.noSave = settings.noSave;
457
+ if (this.item && this.item.get('_modelType') === 'item') {
458
+ largeImageConfig.getConfigFile(this.item.get('folderId')).done((val) => {
459
+ this._limetadata = (val || {}).itemMetadata;
460
+ if (this._limetadata) {
461
+ this.render();
462
+ }
463
+ });
464
+ } else {
465
+ this._limetadata = null;
466
+ }
467
+ });
468
+
469
+ wrap(MetadataWidget, 'render', function (render) {
470
+ let metaDict;
471
+ if (this.item.get(this.fieldName)) {
472
+ metaDict = this.item.get(this.fieldName) || {};
473
+ } else if (this.item[this.fieldName]) {
474
+ metaDict = this.item[this.fieldName] || {};
475
+ } else {
476
+ const fieldParts = this.fieldName.split('.');
477
+ metaDict = this.item.get(fieldParts[0]) || {};
478
+ fieldParts.slice(1).forEach((part) => {
479
+ metaDict = metaDict[part] || {};
480
+ });
481
+ }
482
+ var metaKeys = Object.keys(metaDict);
483
+ metaKeys.sort(localeSort);
484
+ if (this._limetadata) {
485
+ const origOrder = metaKeys.slice();
486
+ metaKeys.sort((a, b) => {
487
+ const aentry = liMetadataKeyEntry(this._limetadata, a);
488
+ const bentry = liMetadataKeyEntry(this._limetadata, b);
489
+ if (aentry && !bentry) {
490
+ return -1;
491
+ }
492
+ if (bentry && !aentry) {
493
+ return 1;
494
+ }
495
+ if (aentry && bentry) {
496
+ return aentry.idx - bentry.idx;
497
+ }
498
+ return origOrder.indexOf(a) - origOrder.indexOf(b);
499
+ });
500
+ }
501
+ this._sortedMetaKeys = metaKeys;
502
+ this._renderedMetaDict = metaDict;
503
+ const contents = (this.MetadataWidgetTemplate || MetadataWidgetTemplate)({
504
+ item: this.item,
505
+ title: this.title,
506
+ accessLevel: this.accessLevel,
507
+ AccessType: AccessType,
508
+ limetadata: this._limetadata
509
+ });
510
+ this._renderHeader(contents);
511
+
512
+ // Append each metadatum
513
+ _.each(metaKeys, function (metaKey) {
514
+ this.$el.find('.g-widget-metadata-container').append(new MetadatumWidget({
515
+ mode: this.getModeFromValue(metaDict[metaKey], metaKey),
516
+ key: metaKey,
517
+ value: metaDict[metaKey],
518
+ accessLevel: this.accessLevel,
519
+ parentView: this,
520
+ fieldName: this.fieldName,
521
+ apiPath: this.apiPath,
522
+ limetadata: this._limetadata,
523
+ noSave: this.noSave,
524
+ onMetadataEdited: this.onMetadataEdited,
525
+ onMetadataAdded: this.onMetadataAdded
526
+ }).render().$el);
527
+ }, this);
528
+
529
+ return this;
530
+ });
531
+
532
+ wrap(MetadataWidget, 'setItem', function (setItem, item) {
533
+ if (item !== this.item) {
534
+ this._limetadata = null;
535
+ if (item && item.get('_modelType') === 'item') {
536
+ largeImageConfig.getConfigFile(item.get('folderId')).done((val) => {
537
+ this._limetadata = (val || {}).itemMetadata;
538
+ if (this._limetadata) {
539
+ this.render();
540
+ }
541
+ });
542
+ }
543
+ }
544
+ setItem.call(this, item);
545
+ this.item.on('g:changed', function () {
546
+ this.render();
547
+ }, this);
548
+ this.render();
549
+ return this;
550
+ });
551
+
552
+ MetadataWidget.prototype.modes.simple.editor = (args) => new MetadatumEditWidget(args);
553
+ MetadataWidget.prototype.modes.json.editor = (args) => {
554
+ if (args.value !== undefined) {
555
+ args.value = JSON.parse(args.value);
556
+ }
557
+ return new JsonMetadatumEditWidget(args);
558
+ };
559
+ MetadataWidget.prototype.modes.key = {
560
+ editor: function (args) {
561
+ return new MetadatumEditWidget(args);
562
+ },
563
+ displayValue: function () {
564
+ return this.value;
565
+ },
566
+ template: MetadatumViewTemplate
567
+ };
568
+
569
+ MetadataWidget.prototype.events['click .li-add-metadata'] = function (evt) {
570
+ this.addMetadataByKey(evt);
571
+ };
572
+
573
+ MetadataWidget.prototype.getModeFromValue = function (value, key) {
574
+ if (liMetadataKeyEntry(this._limetadata, key)) {
575
+ return 'key';
576
+ }
577
+ return _.isString(value) ? 'simple' : 'json';
578
+ };
579
+
580
+ MetadataWidget.prototype.addMetadata = function (evt, mode) {
581
+ var EditWidget = this.modes[mode].editor;
582
+ var value = (mode === 'json') ? '{}' : '';
583
+
584
+ var widget = new MetadatumWidget({
585
+ className: 'g-widget-metadata-row editing',
586
+ mode: mode,
587
+ key: '',
588
+ value: value,
589
+ item: this.item,
590
+ fieldName: this.fieldName,
591
+ noSave: this.noSave,
592
+ apiPath: this.apiPath,
593
+ accessLevel: this.accessLevel,
594
+ parentView: this,
595
+ onMetadataEdited: this.onMetadataEdited,
596
+ onMetadataAdded: this.onMetadataAdded
597
+ });
598
+ widget.$el.appendTo(this.$('.g-widget-metadata-container'));
599
+
600
+ new EditWidget({
601
+ item: this.item,
602
+ key: '',
603
+ value: value,
604
+ fieldName: this.fieldName,
605
+ noSave: this.noSave,
606
+ apiPath: this.apiPath,
607
+ accessLevel: this.accessLevel,
608
+ newDatum: true,
609
+ parentView: widget,
610
+ onMetadataEdited: this.onMetadataEdited,
611
+ onMetadataAdded: this.onMetadataAdded
612
+ })
613
+ .render()
614
+ .$el.appendTo(widget.$el);
615
+ };
616
+
617
+ MetadataWidget.prototype.addMetadataByKey = function (evt) {
618
+ const key = $(evt.target).attr('metadata-key');
619
+ // if this key already exists, just go to editing it
620
+ if (this.$el.find(`.g-widget-metadata-row[g-key="${key}"]`).length) {
621
+ this.$el.find(`.g-widget-metadata-row[g-key="${key}"] button.g-widget-metadata-edit-button`).click();
622
+ return false;
623
+ }
624
+ var EditWidget = this.modes.key.editor;
625
+ var lientry = liMetadataKeyEntry(this._limetadata, key) || {};
626
+ var value = lientry.default ? lientry.default : '';
627
+
628
+ var widget = new MetadatumWidget({
629
+ className: 'g-widget-metadata-row editing',
630
+ mode: 'key',
631
+ key: key,
632
+ value: value,
633
+ item: this.item,
634
+ fieldName: this.fieldName,
635
+ apiPath: this.apiPath,
636
+ accessLevel: this.accessLevel,
637
+ parentView: this,
638
+ limetadata: this._limetadata,
639
+ onMetadataEdited: this.onMetadataEdited,
640
+ onMetadataAdded: this.onMetadataAdded
641
+ });
642
+ widget.$el.appendTo(this.$('.g-widget-metadata-container'));
643
+
644
+ new EditWidget({
645
+ item: this.item,
646
+ key: key,
647
+ value: value,
648
+ fieldName: this.fieldName,
649
+ apiPath: this.apiPath,
650
+ accessLevel: this.accessLevel,
651
+ newDatum: true,
652
+ noSave: this.noSave,
653
+ parentView: widget,
654
+ limetadata: this._limetadata,
655
+ onMetadataEdited: this.onMetadataEdited,
656
+ onMetadataAdded: this.onMetadataAdded
657
+ })
658
+ .render()
659
+ .$el.appendTo(widget.$el);
660
+ };
661
+
662
+ MetadataWidget.prototype._renderHeader = function (contents) {
663
+ this.$el.html(contents);
664
+ };
665
+
666
+ export {
667
+ MetadataWidget,
668
+ MetadatumWidget,
669
+ MetadatumEditWidget,
670
+ JsonMetadatumEditWidget,
671
+ liMetadataKeyEntry
672
+ };
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: girder-large-image
3
- Version: 1.22.3.dev18
3
+ Version: 1.22.3.dev26
4
4
  Summary: A Girder plugin to work with large, multiresolution images.
5
5
  Home-page: https://github.com/girder/large_image
6
6
  Author: Kitware, Inc.
@@ -30,12 +30,15 @@ girder_large_image/web_client/stylesheets/itemList.styl
30
30
  girder_large_image/web_client/stylesheets/itemView.styl
31
31
  girder_large_image/web_client/stylesheets/itemViewCodemirror.styl
32
32
  girder_large_image/web_client/stylesheets/largeImageConfig.styl
33
+ girder_large_image/web_client/stylesheets/metadataWidget.styl
33
34
  girder_large_image/web_client/templates/imageViewerSelectWidget.pug
34
35
  girder_large_image/web_client/templates/itemList.pug
35
36
  girder_large_image/web_client/templates/itemView.pug
36
37
  girder_large_image/web_client/templates/itemViewCodemirror.pug
37
38
  girder_large_image/web_client/templates/largeImageConfig.pug
38
39
  girder_large_image/web_client/templates/largeImage_fileAction.pug
40
+ girder_large_image/web_client/templates/metadataWidget.pug
41
+ girder_large_image/web_client/templates/metadatumEditWidget.pug
39
42
  girder_large_image/web_client/views/configView.js
40
43
  girder_large_image/web_client/views/fileList.js
41
44
  girder_large_image/web_client/views/imageViewerSelectWidget.js
@@ -44,6 +47,7 @@ girder_large_image/web_client/views/itemList.js
44
47
  girder_large_image/web_client/views/itemView.js
45
48
  girder_large_image/web_client/views/itemViewCodemirror.js
46
49
  girder_large_image/web_client/views/itemViewWidget.js
50
+ girder_large_image/web_client/views/metadataWidget.js
47
51
  girder_large_image/web_client/views/imageViewerWidget/base.js
48
52
  girder_large_image/web_client/views/imageViewerWidget/geojs.js
49
53
  girder_large_image/web_client/views/imageViewerWidget/index.js
@@ -1,10 +1,10 @@
1
1
  girder>=3.1.18
2
2
  girder-jobs>=3.0.3
3
- large_image>=1.22.3.dev18
3
+ large_image>=1.22.3.dev26
4
4
 
5
5
  [:python_version < "3.8"]
6
6
  importlib-metadata<5
7
7
 
8
8
  [tasks]
9
- large-image-tasks[girder]>=1.22.3.dev18
9
+ large-image-tasks[girder]>=1.22.3.dev26
10
10
  girder-worker[girder]>=0.6.0
@@ -1218,6 +1218,19 @@ def testTilesHistogram(server, admin, fsAssetstore):
1218
1218
  assert len(resp.json) == 3
1219
1219
  assert len(resp.json[0]['hist']) == 256
1220
1220
 
1221
+ resp = server.request(
1222
+ path='/item/%s/tiles/histogram' % itemId,
1223
+ params={'width': 2048, 'height': 2048, 'resample': False,
1224
+ 'roundRange': False, 'bins': 512})
1225
+ assert len(resp.json) == 3
1226
+ assert len(resp.json[0]['hist']) == 512
1227
+ resp = server.request(
1228
+ path='/item/%s/tiles/histogram' % itemId,
1229
+ params={'width': 2048, 'height': 2048, 'resample': False,
1230
+ 'roundRange': True, 'bins': 512})
1231
+ assert len(resp.json) == 3
1232
+ assert len(resp.json[0]['hist']) == 256
1233
+
1221
1234
 
1222
1235
  @pytest.mark.usefixtures('unbindLargeImage')
1223
1236
  @pytest.mark.plugin('large_image')