/
Display.py
471 lines (402 loc) · 18.1 KB
/
Display.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# -*- coding: utf-8 -*-
"""
The ProductDisplayType definition for SIDD 1.0.
"""
import logging
from typing import Union
from collections import OrderedDict
import numpy
from sarpy.compliance import int_func
from ..sidd2_elements.base import DEFAULT_STRICT
# noinspection PyProtectedMember
from ...complex.sicd_elements.base import Serializable, Arrayable, _SerializableDescriptor, \
_IntegerDescriptor, _FloatDescriptor, _StringDescriptor, _StringEnumDescriptor, \
_ParametersDescriptor, ParametersCollection, \
_create_new_node, _create_text_node, _get_node_value, \
_find_first_child
__classification__ = "UNCLASSIFIED"
__author__ = "Thomas McCullough"
class ColorDisplayRemapType(Serializable, Arrayable):
"""
LUT-base remap indicating that the color display is done through index-based color.
"""
__slots__ = ('_remap_lut', )
def __init__(self, RemapLUT=None, **kwargs):
"""
Parameters
----------
RemapLUT : numpy.ndarray|list|tuple
kwargs
"""
self._remap_lut = None
if '_xml_ns' in kwargs:
self._xml_ns = kwargs['_xml_ns']
if '_xml_ns_key' in kwargs:
self._xml_ns_key = kwargs['_xml_ns_key']
self.RemapLUT = RemapLUT
super(ColorDisplayRemapType, self).__init__(**kwargs)
@property
def RemapLUT(self):
"""
numpy.ndarray: the two dimensional (:math:`N \times 3`) look-up table, where the dtype must be
`uint8` or `uint16`. The first dimension should correspond to entries (i.e. size of the lookup table), and the
second dimension must have size 3 and corresponds to `RGB` bands.
"""
return self._remap_lut
@RemapLUT.setter
def RemapLUT(self, value):
if value is None:
self._remap_lut = None
return
if isinstance(value, (tuple, list)):
value = numpy.array(value, dtype=numpy.uint8)
if not isinstance(value, numpy.ndarray) or value.dtype.name not in ('uint8', 'uint16'):
raise ValueError(
'RemapLUT for class ColorDisplayRemapType must be a numpy.ndarray of dtype uint8 or uint16.')
if value.ndim != 2 and value.shape[1] != 3:
raise ValueError('RemapLUT for class ColorDisplayRemapType must be an N x 3 array.')
self._remap_lut = value
@property
def size(self):
"""
int: the size of the lookup table
"""
if self._remap_lut is None:
return 0
else:
return self._remap_lut.shape[0]
def __len__(self):
if self._remap_lut is None:
return 0
return self._remap_lut.shape[0]
def __getitem__(self, item):
return self._remap_lut[item]
@classmethod
def from_array(cls, array):
"""
Create from the lookup table array.
Parameters
----------
array: numpy.ndarray|list|tuple
Must be two-dimensional. If not a numpy.ndarray, this will be naively
interpreted as `uint8`.
Returns
-------
LUTInfoType
"""
return cls(RemapLUT=array)
def get_array(self, dtype=numpy.uint8):
"""
Gets **a copy** of the coefficent array of specified data type.
Parameters
----------
dtype : str|numpy.dtype|numpy.number
numpy data type of the return
Returns
-------
numpy.ndarray
the lookup table array
"""
return numpy.array(self._remap_lut, dtype=dtype)
@classmethod
def from_node(cls, node, xml_ns, ns_key=None, kwargs=None):
lut_key = cls._child_xml_ns_key.get('RemapLUT', ns_key)
lut_node = _find_first_child(node, 'RemapLUT', xml_ns, lut_key)
if lut_node is not None:
dim1 = int_func(lut_node.attrib['size'])
dim2 = 3
arr = numpy.zeros((dim1, dim2), dtype=numpy.uint16)
entries = _get_node_value(lut_node).split()
i = 0
for entry in entries:
if len(entry) == 0:
continue
sentry = entry.split(',')
if len(sentry) != 3:
logging.error('Parsing RemapLUT is likely compromised. Got entry {}, '
'which we are skipping.'.format(entry))
continue
arr[i, :] = [int(el) for el in entry]
i += 1
if numpy.max(arr) < 256:
arr = numpy.cast[numpy.uint8](arr)
return cls(RemapLUT=arr)
return cls()
def to_node(self, doc, tag, ns_key=None, parent=None, check_validity=False, strict=DEFAULT_STRICT, exclude=()):
if parent is None:
parent = doc.getroot()
if ns_key is None:
node = _create_new_node(doc, tag, parent=parent)
else:
node = _create_new_node(doc, '{}:{}'.format(ns_key, tag), parent=parent)
if 'RemapLUT' in self._child_xml_ns_key:
rtag = '{}:RemapLUT'.format(self._child_xml_ns_key['RemapLUT'])
elif ns_key is not None:
rtag = '{}:RemapLUT'.format(ns_key)
else:
rtag = 'RemapLUT'
if self._remap_lut is not None:
value = ' '.join('{0:d},{1:d},{2:d}'.format(*entry) for entry in self._remap_lut)
entry = _create_text_node(doc, rtag, value, parent=node)
entry.attrib['size'] = str(self.size)
return node
def to_dict(self, check_validity=False, strict=DEFAULT_STRICT, exclude=()):
out = OrderedDict()
if self.RemapLUT is not None:
out['RemapLUT'] = self.RemapLUT.tolist()
return out
class MonochromeDisplayRemapType(Serializable):
"""
This remap works by taking the input space and using the LUT to map it to a log space (for 8-bit only).
From the log space the C0 and Ch fields are applied to get to display-ready density space. The density
should then be rendered by the TTC and monitor comp. This means that the default DRA should not apply
anything besides the clip points. If a different contrast/brightness is applied it should be done through
modification of the clip points via DRA.
"""
_fields = ('RemapType', 'RemapLUT', 'RemapParameters')
_required = ('RemapType', )
_collections_tags = {'RemapParameters': {'array': False, 'child_tag': 'RemapParameter'}}
# Descriptor
RemapType = _StringDescriptor(
'RemapType', _required, strict=DEFAULT_STRICT,
docstring='') # type: str
RemapParameters = _ParametersDescriptor(
'RemapParameters', _collections_tags, _required, strict=DEFAULT_STRICT,
docstring='Textual remap parameter. Filled based upon remap type (for informational purposes only). '
'For example, if the data is linlog encoded a RemapParameter could be used to describe any '
'amplitude scaling that was performed prior to linlog encoding '
'the data.') # type: Union[None, ParametersCollection]
def __init__(self, RemapType=None, RemapLUT=None, RemapParameters=None, **kwargs):
"""
Parameters
----------
RemapType : str
RemapLUT : None|numpy.ndarray
RemapParameters : None|ParametersCollection|dict
kwargs
"""
self._remap_lut = None
if '_xml_ns' in kwargs:
self._xml_ns = kwargs['_xml_ns']
if '_xml_ns_key' in kwargs:
self._xml_ns_key = kwargs['_xml_ns_key']
self.RemapType = RemapType
self.RemapLUT = RemapLUT
self.RemapParameters = RemapParameters
super(MonochromeDisplayRemapType, self).__init__(**kwargs)
@property
def RemapLUT(self):
"""
numpy.ndarray: the one dimensional Lookup table for remap to log amplitude for display,
where the dtype must be `uint8`. Used during the "Product Generation Option" portion of the SIPS
display chain. Required for 8-bit data, and not to be used for 16-bit data.
"""
return self._remap_lut
@RemapLUT.setter
def RemapLUT(self, value):
if value is None:
self._remap_lut = None
return
if isinstance(value, (tuple, list)):
value = numpy.array(value, dtype=numpy.uint8)
if not isinstance(value, numpy.ndarray) or value.dtype.name != 'uint8':
raise ValueError(
'RemapLUT for class MonochromeDisplayRemapType must be a numpy.ndarray of dtype uint8.')
if value.ndim != 1:
raise ValueError('RemapLUT for class MonochromeDisplayRemapType must be a one-dimensional array.')
self._remap_lut = value
@classmethod
def from_node(cls, node, xml_ns, ns_key=None, kwargs=None):
if kwargs is None:
kwargs = {}
lut_key = cls._child_xml_ns_key.get('RemapLUT', ns_key)
lut_node = _find_first_child(node, 'RemapLUT', xml_ns, lut_key)
if lut_node is not None:
dim1 = int_func(lut_node.attrib['size'])
arr = numpy.zeros((dim1, ), dtype=numpy.uint8)
entries = _get_node_value(lut_node).split()
i = 0
for entry in entries:
if len(entry) == 0:
continue
arr[i] = int(entry)
i += 1
kwargs['RemapLUT'] = arr
return super(MonochromeDisplayRemapType, cls).from_node(node, xml_ns, ns_key=ns_key, **kwargs)
def to_node(self, doc, tag, ns_key=None, parent=None, check_validity=False, strict=DEFAULT_STRICT, exclude=()):
node = super(MonochromeDisplayRemapType, self).to_node(
doc, tag, ns_key=ns_key, parent=parent, check_validity=check_validity, strict=strict)
if 'RemapLUT' in self._child_xml_ns_key:
rtag = '{}:RemapLUT'.format(self._child_xml_ns_key['RemapLUT'])
elif ns_key is not None:
rtag = '{}:RemapLUT'.format(ns_key)
else:
rtag = 'RemapLUT'
if self._remap_lut is not None:
value = ' '.join('{0:d}'.format(entry) for entry in self._remap_lut)
entry = _create_text_node(doc, rtag, value, parent=node)
entry.attrib['size'] = str(self._remap_lut.size)
return node
def to_dict(self, check_validity=False, strict=DEFAULT_STRICT, exclude=()):
out = super(MonochromeDisplayRemapType, self).to_dict(
check_validity=check_validity, strict=strict, exclude=exclude)
if self.RemapLUT is not None:
out['RemapLUT'] = self.RemapLUT.tolist()
return out
class RemapChoiceType(Serializable):
"""
The remap choice type.
"""
_fields = ('ColorDisplayRemap', 'MonochromeDisplayRemap')
_required = ()
_choice = ({'required': False, 'collection': ('ColorDisplayRemap', 'MonochromeDisplayRemap')})
# Descriptor
ColorDisplayRemap = _SerializableDescriptor(
'ColorDisplayRemap', ColorDisplayRemapType, _required, strict=DEFAULT_STRICT,
docstring='Information for proper color display of the '
'data.') # type: Union[None, ColorDisplayRemapType]
MonochromeDisplayRemap = _SerializableDescriptor(
'MonochromeDisplayRemap', MonochromeDisplayRemapType, _required, strict=DEFAULT_STRICT,
docstring='Information for proper monochrome display of the '
'data.') # type: Union[None, MonochromeDisplayRemapType]
def __init__(self, ColorDisplayRemap=None, MonochromeDisplayRemap=None, **kwargs):
"""
Parameters
----------
ColorDisplayRemap : None|ColorDisplayRemapType|numpy.ndarray|list|type
MonochromeDisplayRemap : None|MonochromeDisplayRemapType
kwargs
"""
if '_xml_ns' in kwargs:
self._xml_ns = kwargs['_xml_ns']
if '_xml_ns_key' in kwargs:
self._xml_ns_key = kwargs['_xml_ns_key']
self.ColorDisplayRemap = ColorDisplayRemap
self.MonochromeDisplayRemap = MonochromeDisplayRemap
super(RemapChoiceType, self).__init__(**kwargs)
class MonitorCompensationAppliedType(Serializable):
"""
"""
_fields = ('Gamma', 'XMin')
_required = ('Gamma', 'XMin')
_numeric_format = {key: '0.16G' for key in _fields}
# Descriptor
Gamma = _FloatDescriptor(
'Gamma', _required, strict=DEFAULT_STRICT,
docstring='Gamma value for monitor compensation pre-applied to the image.') # type: float
XMin = _FloatDescriptor(
'XMin', _required, strict=DEFAULT_STRICT,
docstring='Xmin value for monitor compensation pre-applied to the image.') # type: float
def __init__(self, Gamma=None, XMin=None, **kwargs):
"""
Parameters
----------
Gamma : float
XMin : float
kwargs
"""
if '_xml_ns' in kwargs:
self._xml_ns = kwargs['_xml_ns']
if '_xml_ns_key' in kwargs:
self._xml_ns_key = kwargs['_xml_ns_key']
self.Gamma = Gamma
self.XMin = XMin
super(MonitorCompensationAppliedType, self).__init__(**kwargs)
class DRAHistogramOverridesType(Serializable):
"""
Dynamic range adjustment overide parameters.
"""
_fields = ('ClipMin', 'ClipMax')
_required = ('ClipMin', 'ClipMax')
# Descriptor
ClipMin = _IntegerDescriptor(
'ClipMin', _required, strict=DEFAULT_STRICT,
docstring='Suggested override for the lower end-point of the display histogram in the '
'ELT DRA application. Referred to as Pmin in SIPS documentation.') # type: int
ClipMax = _IntegerDescriptor(
'ClipMax', _required, strict=DEFAULT_STRICT,
docstring='Suggested override for the upper end-point of the display histogram in the '
'ELT DRA application. Referred to as Pmax in SIPS documentation.') # type: int
def __init__(self, ClipMin=None, ClipMax=None, **kwargs):
"""
Parameters
----------
ClipMin : int
ClipMax : int
kwargs
"""
if '_xml_ns' in kwargs:
self._xml_ns = kwargs['_xml_ns']
if '_xml_ns_key' in kwargs:
self._xml_ns_key = kwargs['_xml_ns_key']
self.ClipMin = ClipMin
self.ClipMax = ClipMax
super(DRAHistogramOverridesType, self).__init__(**kwargs)
class ProductDisplayType(Serializable):
"""
"""
_fields = (
'PixelType', 'RemapInformation', 'MagnificationMethod', 'DecimationMethod',
'DRAHistogramOverrides', 'MonitorCompensationApplied', 'DisplayExtensions')
_required = ('PixelType', )
_collections_tags = {
'DisplayExtensions': {'array': False, 'child_tag': 'DisplayExtension'}}
# Descriptors
PixelType = _StringEnumDescriptor(
'PixelType', ('MONO8I', 'MONO8LU', 'MONO16I', 'RGBL8U', 'RGB24I'),
_required, strict=DEFAULT_STRICT,
docstring='Enumeration of the pixel type. Definition in '
'Design and Exploitation document.') # type: str
RemapInformation = _SerializableDescriptor(
'RemapInformation', RemapChoiceType, _required, strict=DEFAULT_STRICT,
docstring='Information regarding the encoding of the pixel data. '
'Used for 8-bit pixel types.') # type: Union[None, RemapChoiceType]
MagnificationMethod = _StringEnumDescriptor(
'MagnificationMethod', ('NEAREST_NEIGHBOR', 'BILINEAR', 'LAGRANGE'),
_required, strict=DEFAULT_STRICT,
docstring='Recommended ELT magnification method for this data.') # type: Union[None, str]
DecimationMethod = _StringEnumDescriptor(
'DecimationMethod', ('NEAREST_NEIGHBOR', 'BILINEAR', 'BRIGHTEST_PIXEL', 'LAGRANGE'),
_required, strict=DEFAULT_STRICT,
docstring='Recommended ELT decimation method for this data. Also used as default for '
'reduced resolution dataset generation (if applicable).') # type: Union[None, str]
DRAHistogramOverrides = _SerializableDescriptor(
'DRAHistogramOverrides', DRAHistogramOverridesType, _required, strict=DEFAULT_STRICT,
docstring='Recommended ELT DRA overrides.') # type: Union[None, DRAHistogramOverridesType]
MonitorCompensationApplied = _SerializableDescriptor(
'MonitorCompensationApplied', MonitorCompensationAppliedType, _required, strict=DEFAULT_STRICT,
docstring='Describes monitor compensation that may have been applied to the product '
'during processing.') # type: Union[None, MonitorCompensationAppliedType]
DisplayExtensions = _ParametersDescriptor(
'DisplayExtensions', _collections_tags, _required, strict=DEFAULT_STRICT,
docstring='Optional extensible parameters used to support profile-specific needs related to '
'product display. Predefined filter types.') # type: Union[None, ParametersCollection]
def __init__(self, PixelType=None, RemapInformation=None, MagnificationMethod=None, DecimationMethod=None,
DRAHistogramOverrides=None, MonitorCompensationApplied=None, DisplayExtensions=None, **kwargs):
"""
Parameters
----------
PixelType : PixelTypeType
RemapInformation : None|RemapChoiceType
MagnificationMethod : None|str
DecimationMethod : None|str
DRAHistogramOverrides : None|DRAHistogramOverridesType
MonitorCompensationApplied : None|MonitorCompensationAppliedType
DisplayExtensions : None|ParametersCollection|dict
kwargs
"""
if '_xml_ns' in kwargs:
self._xml_ns = kwargs['_xml_ns']
if '_xml_ns_key' in kwargs:
self._xml_ns_key = kwargs['_xml_ns_key']
self.PixelType = PixelType
self.RemapInformation = RemapInformation
self.MagnificationMethod = MagnificationMethod
self.DecimationMethod = DecimationMethod
self.DRAHistogramOverrides = DRAHistogramOverrides
self.MonitorCompensationApplied = MonitorCompensationApplied
self.DisplayExtensions = DisplayExtensions
super(ProductDisplayType, self).__init__(**kwargs)
# @classmethod
# def from_node(cls, node, xml_ns, ns_key=None, kwargs=None):
# return super(ProductDisplayType, cls).from_node(node, xml_ns, ns_key=ns_key, kwargs=kwargs)