-
Notifications
You must be signed in to change notification settings - Fork 0
/
rcvs.py
2042 lines (1888 loc) · 110 KB
/
rcvs.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
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
title: rcvs.py
module: RCVS (Raster Computer Vision Simplification)
summary: Automatisation of geo-processing workflow over raster and vector data
description: Python basic tools for applying a geo-processing workflow over raster and
vector data:
- using the input/output utility functions of RIOS (Raster Input Output
Simplification) module, itself based on Gdal module,
- using external Computer Vision and Image Processing processing (CVIP)
algorithms provided (when installed independently) by modules like PIL,
OpenCV, skimage, matplotlib and/or scipy.ndimage.
This way, a 'simple' definition of processing workflow is possible
usage: see main()/test()
:CONTRIBUTORS: grazzja
:CONTACT: jacopo.grazzini@ec.europa.eu
:SINCE: Fri May 31 10:20:51 2013
:VERSION: 0.9
:REQUIRES: gdal, rios, numpy, scipy
Queue, multiprocessing
math, re, inspect, operator, itertools, collections
:OPTIONAL: cv2, skimage, PIL, matplotlib, vigra, mahotas
pathos
"""
# EUPL LICENSE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#==============================================================================
# PROGRAM METADATA
#==============================================================================
__contributor__ = ['grazzja']
__license__ = 'EUPL'
__date__ = 'Fri May 31 10:20:51 2013'
__version__ = '0.9'
__all__ = ['apply']
#==============================================================================
# IMPORT STATEMENTS
#==============================================================================
## import standard common modules
import os, sys, inspect, re
import warnings
import math
import itertools, collections
def raiseMessage(error, message): raise error, message
def raiseImportError(mod,web):
message = str('%s module is required' % mod)
if web not in ('',None): message += str('; visit %s for install' % web)
raise ImportError, message
def warnMessage(message): warnings.warn(message)
def warnImportError(mod,web):
message = str('missing %s module' % mod)
if web not in ('',None): message += str('; visit %s for install' % web)
warnMessage(message)
try:
numpy_mod, numpy_web = 'NumPy', 'http://www.numpy.org/'
import numpy as np
Array=np.ndarray
memArray=np.memmap
except ImportError:
raiseImportError(numpy_mod,numpy_web)
## import specific CVIP modules used herein
# last visit of websites: 15/01/14
## import GDAL module just to ensure it is available
try:
gdal_mod, gdal_web = 'GDAL', 'https://pypi.python.org/pypi/GDAL/'
import gdal
except ImportError:
try: from osgeo import gdal#analysis:ignore
except ImportError: raiseImportError(gdal_mod,gdal_web)
# define the RCVS_LIST_MODULES constant as the list of available(imported) CV modules
RCVS_LIST_MODULES = []
gdal__name__='gdal' # used instead of gdal.__name__ that may return 'osgeo.gdal'
RCVS_LIST_MODULES.append(gdal__name__) # RIOS format
## import RIOS module (submodules) used herein
try:
rios_mod, rios_web = 'RIOS', 'https://bitbucket.org/chchrsc/rios'
# import rios
from rios import imageio, imagereader, imagewriter, vectorreader
from rios import applier, rioserrors
# from rios import calcstats
except (ValueError, ImportError):
raiseImportError(rios_mod,rios_web)
try:
scipy_mod, scipy_web = 'SciPy', 'http://docs.scipy.org/doc/scipy/reference/ndimage.html'
import scipy
#from scipy import ndimage
RCVS_LIST_MODULES.append(scipy.__name__)
except ImportError:
warnImportError(scipy_mod, scipy_web)
class scipy: pass
#class ndimage: pass
try:
matplotlib_mod, matplotlib_web = 'matplotlib', 'http://matplotlib.org/'
import matplotlib
#from matplotlib import pyplot
RCVS_LIST_MODULES.append(matplotlib.__name__)
except ImportError:
warnImportError(matplotlib_mod,matplotlib_web)
class matplotlib: pass
#class pyplot: pass
try:
cv2_mod, cv2_web = 'OpenCV', 'http://opencv.org/' # last visited 15/01/14
import cv2
#from cv2 import cv as cv
RCVS_LIST_MODULES.append(cv2.__name__)
except ImportError:
warnImportError(cv2_mod,cv2_web)
class cv2: pass
#class cv(): iplimage = type('DUM_CV_IMAGE_TYPE',(object,),{})
try:
skimage_mod, skimage_web = 'scikits-image', 'http://scikits.appspot.com/scikits-image'
import skimage
#from skimage.io import _io as skim_io
RCVS_LIST_MODULES.append(skimage.__name__)
except ImportError:
warnImportError(skimage_mod,skimage_web)
class skimage: pass
#class skim_io(): Image = type('DUM_SKIM_IMAGE_TYPE',(object,),{})
try:
pil_mod, pil_web = 'PIL', 'http://www.pythonware.com/products/pil/, http://effbot.org/zone/pil-index.htm'
import PIL
#from PIL import Image as pil_im
RCVS_LIST_MODULES.append(PIL.__name__)
except ImportError:
# define dummy class
warnImportError(pil_mod,pil_web)
class PIL: pass
# define dummy type as an instance of a dummy class
#class pil_im(): Image = type('DUM_PIL_IMAGE_TYPE',(object,),{})
try:
vigra_mod, vigra_web = 'vigra', 'http://ukoethe.github.io/vigra/doc/vigranumpy/index.html'
import vigra
VigraArray = vigra.VigraArray
RCVS_LIST_MODULES.append(vigra.__name__)
except ImportError:
warnImportError(vigra_mod, vigra_web)
class vigra: pass
class VigraArray(): Image = type('DUM_VIGRA_IMAGE_TYPE',(object,),{})
#class pyplot: pass
try:
mahotas_mod, mahotas_web = 'mahotas', 'http://luispedro.org/software/mahotas' # last visited 15/02/14
import mahotas
#from cv2 import cv as cv
RCVS_LIST_MODULES.append(mahotas.__name__)
except ImportError:
warnImportError(mahotas_mod,mahotas_web)
class mahotas: pass
## check the availability and import other modules used in processing
# Python doesn't pickle method instance by default
# http://stackoverflow.com/questions/1816958/cant-pickle-type-instancemethod-when-using-pythons-multiprocessing-pool-ma
import copy_reg
import types
def _pickle_method(method): # identify the fields of the method
func_name = method.im_func.__name__
obj = method.im_self
cls = method.im_class
return _unpickle_method, (func_name, obj, cls)
def _unpickle_method(func_name, obj, cls):
for cls in cls.mro():
try: func = cls.__dict__[func_name]
except KeyError: pass
else: break
return func.__get__(obj, cls)
# http://matthewrocklin.com/blog/work/2013/12/05/Parallelism-and-Serialization/
# Note that functions (built-in and user-defined) that are mapped for multiprocessing
# using the original multiprocessing module are pickled by "fully qualified" name
# reference, not by value. This means that only the function name is pickled, along
# with the name of the module the function is defined in. Neither the function's code,
# nor any of its function attributes are pickled. Thus the defining module must be
# importable in the unpickling environment, and the module must contain the named
# object, otherwise an exception will be raised.
# Similarly, classes are pickled by named reference, so the same restrictions in the
# unpickling environment apply. Note that none of the class's code or data is pickled.
# Similarly, when class instances are pickled, their class's code and data are not
# pickled along with them. Only the instance data are pickled. This is done on purpose,
# so you can fix bugs in a class or add methods to the class and still load objects
# that were created with an earlier version of the class.
copy_reg.pickle(types.MethodType, _pickle_method, _unpickle_method)
import Queue
import multiprocessing
RCVS_CPU_NODES = multiprocessing.cpu_count()
pathos_mod, pathos_web = 'pathos', 'https://github.com/uqfoundation/pathos/'
#if re.search('win',sys.platform):
# warnImportError(pathos_mod, pathos_web) # platform.system()
#else:
try:
import pathos
except ImportError:
warnImportError(pathos_mod, pathos_web)
class pathos: pass
else:
from pathos.multiprocessing import ProcessingPool
pool = ProcessingPool(ncpus=1)
try:
pool.imap(lambda x: x**2, (0,), chunksize=1).next(timeout=0.1)
RCVS_LIST_MODULES.append(pathos.__name__)
except: # (multiprocessing.TimeoutError,cPickle.PicklingError):
warnMessage('pathos multiprocessing not available with the current system')
else:
warnMessage('pathos multiprocessing will be used')
# most of the multiprocessing pool operations performed herein are based on the
# module pathos, not the original multiprocessing module itself
#==============================================================================
# GLOBAL VARIABLES/METHODS
#==============================================================================
## global constants used herein
RCVS_KEY_IN = 'in'
RCVS_KEY_OUT = 'out'
RCVS_KEY_BLOCKBYTE = 'dtype' # reading option
RCVS_KEY_BLOCKFORMAT = 'fmtBlock'
RCVS_KEYDICT_BLOCKFORMAT = \
dict([(fmtDir,fmtDir+RCVS_KEY_BLOCKFORMAT[0].capitalize()\
+RCVS_KEY_BLOCKFORMAT[1::]) \
for fmtDir in (RCVS_KEY_IN,RCVS_KEY_OUT)])
RCVS_KEY_INAXIS = RCVS_KEYDICT_BLOCKFORMAT[RCVS_KEY_IN]
RCVS_KEY_OUTAXIS = RCVS_KEYDICT_BLOCKFORMAT[RCVS_KEY_OUT]
## additional keys used for defining the processing workflow
RCVS_KEY_MODULE = 'module'
RCVS_KEY_FUNCTION = 'call'
RCVS_KEY_ARGS = 'args'
RCVS_KEY_KWARGS = 'kwargs'
## final list of keys used for configuring the processing workflow
RCVS_KEYLIST_WORK = \
(RCVS_KEY_MODULE, RCVS_KEY_FUNCTION, \
RCVS_KEY_IN, RCVS_KEY_OUT, \
RCVS_KEY_ARGS, RCVS_KEY_KWARGS, \
RCVS_KEY_BLOCKFORMAT)
## keys used for defining the processing
RCVS_LIST_BLOCKPROCESS = ('single', 'serial', 'pool', 'pthread')
RCVS_KEY_BLOCKSINGLE = 'singleBlock'
RCVS_KEY_BLOCKPROCESS = 'procBlock'
RCVS_KEY_BLOCKNUM = 'numBlock'
RCVS_KEY_RETURNARGS = 'returnArgs'
RCVS_KEY_INFO = 'info'
RCVS_KEY_MARGIN = 'margin' # 'overlap'
## additional variables used
RCVS_NO_ARGUMENT = None
RCVS_DEF_ARGUMENT = set((None,())).difference(set((RCVS_NO_ARGUMENT,))).pop() # ()
# note that RCVS_DEF_ARGUMENT corresponds to a 'neutral' argument, ie. nothing
# has been passed for a given key, then it is left to the default value in the
# program
RCVS_EMPTY_ARGUMENT = () # ((),)
# a NO_ARGUMENT key will have its value set to EMPTY_ARGUMENT
RCVS_DEF_BLOCKRANGE = 5
RCVS_NONE2_LIST = (None,None)
#==============================================================================
# METHODS
#==============================================================================
## new functions/classes defined for cvapplier utility
#/****************************************************************************/
# Format
#/****************************************************************************/
class Format(object):
"""Class of utility methods for data format/structure manipulation.
Numpy does not provide any means to attach semantics to axes, but relies purely
on the convention that the most important axis is last, as in :literal:`array[y, x]`
or :literal:`array[z, y, x]` ("C-order").
However, there is no way to enforce this convention in a program, since arrays
can be transposed outside of the user's control (e.g. when saving data to a
file). Moreover, many imaging libraries use the opposite convention where the
:data:`x`-axis comes first, as in :literal:`array[x, y]` or
:literal:`array[x, y, z]`.
"""
#/************************************************************************/
# Data format (or shape): sets the axis arrangement (order) of the (numpy)
# Array
KEY_NOFORMAT = RCVS_NO_ARGUMENT # None
# ensure in particular that KEY_NOFORMAT!=RCVS_KEY_DEFARGUMENT
mod2format = {
gdal__name__: 'zyx', gdal.__name__: 'zyx',
PIL.__name__: 'yxz',
cv2.__name__: 'yx-z', # in cv2, RGB images are read as BGR
skimage.__name__: 'yxz',
matplotlib.__name__: 'yxz', #'-yxz'
scipy.__name__: 'yxz',
np.__name__: 'yxz',
vigra.__name__: 'xyz', # we refer here to the RGB format of vigra
mahotas.__name__: 'yxz',
KEY_NOFORMAT: '' # no change will be made
}
"""Define the format (shape) of the Arrays used for processing by the various
CVIP modules considered herein (say it otherwise, the axis arrangement of the
matrices used by those modules.
"""
DataBlockFormat = \
list(set(mod2format.keys()).union(set(mod2format.values())))
# list(set(mod2format.keys()).difference((None,)).union(set(mod2format.values())))
"""Define what a string used as a block format entry should be: both the name
of the module used and the description of the format itself are accepted.
"""
# see also RCVS_KEY_BLOCKFORMAT)
#/************************************************************************/
# Data type (byte format) dictionaries that can be used for various type
# conversions
# Python pack types names
PPTypes = ['B', 'b', 'H', 'h', 'I', 'i', 'f', 'd']
# http://www.python.org/doc//current/library/struct.html
# http://docs.python.org/2/library/array.html
# NumPy types names
NumPyTypes = [np.dtype(__n).name for __n in PPTypes+['l','L','c']]
# Python pack types <-> Numpy
ppt2npy = dict([(__n,np.dtype(__n).name) for __n in PPTypes])
# http://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html
# http://docs.scipy.org/doc/numpy/reference/arrays.scalars.html
# note regarding np.dtype:
# np.dtype('B') -> dtype('uint8')
# np.dtype('uint8') -> dtype('uint8')
# np.dtype(np.uint8) -> dtype('uint8')
npy2ppt = dict(zip(ppt2npy.values(),ppt2npy.keys()))
# GDAL Data Type names
# http://www.gdal.org/gdal_datamodel.html
__ngdtypes = 11 # currently 11...
GdalTypes = [__n for __n in [gdal.GetDataTypeName(__i) for __i in range(1,__ngdtypes+1)] if __n]
# note: there is no 'b' type ('int8') in Gdal!
# to retrieve thelist of GDT values, we can use the following:
# gdalGdalTypes = [eval('gdal.GDT_'+dtype) for dtype in GdalTypes]
# GDAL <-> Python pack types
gdt2ppt = dict(zip(GdalTypes,[__n for __n in PPTypes if __n!='b']))
ppt2gdt = dict(zip(gdt2ppt.values(),gdt2ppt.keys()))
ppt2gdt.update({'b': 'Byte'})
# GDAL <-> Numpy
gdt2npy = dict([(__i,ppt2npy[__n]) for (__i,__n) in gdt2ppt.items() \
if __n in ppt2npy.keys()])
# add the complex types
gdt2npy.update({'CInt16':'complex64', 'CFloat32':'complex64','CInt32':'complex64', 'CFloat64':'complex128'})
npy2gdt = dict(zip(gdt2npy.values(),gdt2npy.keys()))
# since several things map to complex64 (see gdt2npy update above), we ensure that only
# one match is selected (http://gdal.org/python/osgeo.gdal_array-pysrc.html)
npy2gdt.update({'complex64':'CFloat32'}) # it could be the case already, but make it safe
npy2gdt.update({'int8':'Byte', 'bool':'Byte'}) # ({'single':'Float32', 'float':'Float64'})
# see also imageio.dataTypeMapping
# OpenCV Data Type names
# obviously order matters, as the order in OCVTypes should correspond to
# that of PPTypes
OCVTypes = ['8U', '8S', '16U', '16S', '32S', 'i', '32F', '64F']
# note: there is no 'i' type ('int32') in OpenCV
try:
# OpenCV(cv2) <-> Python pack types
ocv2ppt = dict([(eval('cv2.IPL_DEPTH_'+__n),__i) for (__n,__i) in zip(OCVTypes,PPTypes) \
if __n!='i' and __i!='i'])
ppt2ocv = dict(zip(ocv2ppt.values(),ocv2ppt.keys()))
ppt2ocv.update({'i': cv2.IPL_DEPTH_32S})
# OpenCV(cv2) <-> Numpy
ocv2npy = dict([(__i,ppt2npy[__n]) for (__i,__n) in ocv2ppt.items() \
if __n in ppt2npy.keys()])
npy2ocv = dict(zip(ocv2npy.values(),ocv2npy.keys()))
npy2ocv.update({'int32': cv2.IPL_DEPTH_32S})
except:
pass
# PIL Data Type names
PILTypes = ['1', 'L', 'P', 'RGB', 'RGBA', 'CMYK', 'YCbCr', 'I', 'F']
# http://effbot.org/imagingbook/concepts.htm
__pilppt = ['B', 'B', 'B', 'B', 'B', 'B', 'B', 'i', 'f']
# PIL -> Python pack types
pil2ppt = dict(zip(PILTypes,__pilppt))
# PIL -> Numpy
pil2npy = dict(zip(PILTypes,[ppt2npy[__n] for __n in __pilppt]))
# Skimage Data Type names
# http://scikit-image.org/docs/dev/user_guide/data_types.html
SkimTypes = NumPyTypes
# ski2npy = dict(zip(SkimTypes,NumPyTypes)) # not used here
# npy2ski, ski2ppt, ppt2ski = ski2npy, npy2ppt, ppt2npy
# Pyplot Data Type names
# http://matplotlib.org/users/image_tutorial.html
PPlTypes = NumPyTypes
# ppl2npy = dict(zip(PPlTypes,NumPyTypes))
# npy2ppl, ppl2ppt, ppt2ppl = ppl2npy, npy2ppt, ppt2npy
# note that "Matplotlib plotting can handle float32 and uint8, but image reading/writing
# for any format other than PNG is limited to uint8 data." However, the type of the data
# used being a Numpy array, we keep it open for type conversions.
# obviously...
SciPyTypes = NumPyTypes
# define all possible (unique) strings for data type description
DataBlockTypes = list(set(PPTypes+GdalTypes+OCVTypes+PILTypes+NumPyTypes))
#/************************************************************************/
@classmethod
def isCVModule(cls, module):
"""Check that a module is one of the various loaded CVIP modules.
>>> resp = Format.isCVModule(module)
"""
if module is None: return False
elif inspect.ismodule(module): return cls.isCVModule(module.__name__)
elif not isinstance(module,str): raise IOError, 'unexpected argument'
return any([re.search(m,module) for m in RCVS_LIST_MODULES])
#/************************************************************************/
@classmethod
def whichCVModule(cls, module):
"""Retrieve the name of a CVIP module when loaded.
>>> name = Format.whichCVModule(module)
"""
if module is None: return None
elif inspect.ismodule(module): return cls.whichCVModule(module.__name__)
elif not isinstance(module,str): raise IOError, 'unexpected argument'
res = [re.search(m,module) for m in RCVS_LIST_MODULES]
if any(res):
return RCVS_LIST_MODULES[[i for (i,s) in enumerate(res) if s][0]]
else:
return None
#/************************************************************************/
@classmethod
def checkTypeBlock(cls, dType):
"""Check that a given type is accepted for processing.
>>> resp = Format.checkTypeBlock(dType)
"""
if not (dType is None or dType in cls.DataBlockTypes):
raise IOError, 'wrong block type: must be None or any string in %s' % cls.DataBlockTypes
else:
return dType
#/************************************************************************/
@classmethod
def toByte(cls, x, dType=None, oMod=None): # dType is a string defining the type of the data
"""Convert some data to the desired byte format, taking into account both
the type and the format.
>>> new = Format.toByte(x, dType=None, oMod=None)
Arguments
---------
x : scalar,np.ndarray,vigra.VigraArray,dict,list,tuple
container storing some data to convert.
dType : str,type
container providing the type to convert the data to.
oMod : str
name of the module to which the data will be sent.
Returns
-------
new : scalar,np.ndarray,vigra.VigraArray,dict,list,tuple
well formatted data representing the same data as those conveyed by
:data:`x`\ .
"""
try: np.ndarray([1,2,2]).astype('int8',copy=False)
except TypeError: asType = lambda x, dtype: x.astype(dtype)
else: asType = lambda x, dtype: x.astype(dtype,copy=False)
tobyteArray = {
False: lambda x, dtype: asType(x,dtype) if (dtype is not None and x.dtype!=dtype) \
else x,
True: lambda x, dtype: skimage.img_as_ubyte(x) if re.search('int8',dtype) \
else (skimage.img_as_int(x) if re.search('int16',dtype) \
else (skimage.img_as_uint(x) if re.search('uint16',dtype) \
else (skimage.img_as_float(x) if re.search('float',dtype) \
else (asType(x,dtype) if (dtype is not None and x.dtype!=dtype) \
else x))))
}
tobyteScalar = lambda x, dtype: int(x) if any([re.search(t,dtype) for t in ('int8','int16')]) \
else (long(x) if re.search('int32', dtype) \
else (float(x) if re.search('float', dtype) else x))
if oMod==cv2.__name__ and (dType is None or not any([re.search(t,dType) for t in ('int8','uint8')])):
dType = 'uint8'
elif oMod==vigra.__name__ and dType is None:
dType = 'float32' # unless noted, vigra functions expect/create numpy.float32 arrays
if dType is None:
return x
elif np.isscalar(x):
return tobyteScalar(x,dType)
if isinstance(x,(Array,VigraArray)):
return tobyteArray[oMod==skimage.__name__ ](x,dType)
elif isinstance(x,dict):
return dict(zip(x.keys(),map(cls.toByte, x.values(), [dType]*len(x), [oMod]*len(x))))
elif isinstance(x,(list,tuple)):
return map(cls.toByte, x, [dType]*len(x), [oMod]*len(x))
#/************************************************************************/
@classmethod
def checkFormatBlock(cls, formatBlock):
"""Check and retrieve a format for describing data.
>>> fmt, mod = Format.checkFormatBlock(fmt)
Arguments
---------
fmt : str, module
either the (name of a) module or a string describing the shape of the array
(i.e. the order of axis in the array) itself (nothing to do then).
Returns
-------
fmt : str
string describing the shape of an array for a given module (and not
anymore the name of the module itself).
mod : str
identified module name if any.
"""
# in case a module itself has been passed, retry
if inspect.ismodule(formatBlock): return cls.checkFormatBlock(formatBlock.__name__)
# first check that the string describing the format is acceptable: i
if formatBlock not in cls.DataBlockFormat:
raise IOError, 'wrong block format: must be any string in %s' % cls.DataBlockFormat
# ...then ensure it is a string describing the shape of the array (and not
# the name of a module)
elif formatBlock in cls.mod2format.keys(): # formatBlock is the name of a module
return cls.mod2format[formatBlock], formatBlock
# else return: it is already in cls.mod2format.values()
else:
return formatBlock, None # we have no clue about the module
#/************************************************************************/
# toAxis: method for rearranging the array data
@classmethod
def toAxis(cls, x, oAxis, iAxis='zyx'):
try: # keyword 'axis' new in version 1.7.0
np.squeeze(np.ndarray([1,2,2]), axis=(0,))
except TypeError:
toaxisSqueeze = lambda x, axis: x if (len(x.shape)!=3 or x.shape[axis]!=1) else np.squeeze(x)
else:
toaxisSqueeze = lambda x, axis: x if (len(x.shape)!=3 or x.shape[axis]!=1) else np.squeeze(x, axis=axis)
toaxisExpand = lambda x, axis: x if len(x.shape)==3 else np.expand_dims(x, axis=axis)
toaxisTranspose = lambda x, axes: x.transpose(axes) # np.transpose(x,axes=axes)
toaxisInvert = lambda x, lidx: x if lidx in (None,()) \
else x[tuple((slice(None,None,-1) if i in lidx else slice(None) for i in range(x.ndim)))]
#toaxisInvert = lambda x, idx: np.swapaxes(np.swapaxes(x, 0, idx)[::-1], 0, idx)
toaxis = { # vigra is assumed to be 'xyz'
'zyx': {'yxz': lambda x, lidx: toaxisTranspose(toaxisInvert(toaxisExpand(x,2),lidx),[2, 0, 1]),
'zyx': lambda x, lidx: toaxisInvert(x,lidx),
'vigra':lambda x, lidx: toaxisInvert(Array(x.asRGB(normalize=False).transposeToOrder('F')), [lidx[2]]+lidx[:2])
},
'yxz': {'zyx': lambda x, lidx: toaxisInvert(toaxisSqueeze(toaxisTranspose(x,[1, 2, 0]),2),lidx),
'yxz': lambda x, invert: toaxisInvert(x,lidx),
'vigra':lambda x, lidx: toaxisInvert(Array(x.transposeToOrder('C')), lidx)
},
'vigra':{'yxz': lambda x, lidx: VigraArray(toaxisInvert(toaxisExpand(x,2),lidx),order='C'),
'zyx': lambda x, lidx: VigraArray(toaxisInvert(toaxisExpand(x,2),lidx),order='F'),
'vigra':lambda x, lidx: x
}
# the configurations above are the only possible with the considered modules
}
# check if no change is requested
if oAxis=='' or iAxis=='': return x
# otherwise define the list of axis that need to be inverted: lidx will return
# the index of the axis in (y,x,z) order
lidx = [] # will stored the indices in (y,x,z) of the axis that change orientation
[lidx.append(i) for (i,straxis) in enumerate(['y','x','z']) \
if (re.search('-'+straxis,iAxis) is None)^(re.search('-'+straxis,oAxis) is None)]
# about (re.search('-'+straxis,iAxis) is None)^(re.search('-'+straxis,oAxis) is None): an axis
# is inverted if and only if there is a change in 'sign' in between iAxis and oAxis, eg.:
# - if iAxis='yx-z' and oAxis='zyx', then lidx=[2] as the z-axis needs to be inverted,
# - if iAxis='-yxz' and oAxis='yx-z', then lidx=[0,2] as both y- and z- need to be inverted,
# - if iAxis='-yxz' and oAxis='z-yx' (non existing example), then lidx=[] as there is no
# axis inversion needed.
# clean the axis definitions
iAxis, oAxis = iAxis.replace('-',''), oAxis.replace('-','')
# apply the matrix transformation
if isinstance(x,Array): return toaxis[oAxis][iAxis](x,lidx)
else: return x
#/************************************************************************/
@classmethod
def formatContainer(cls, blockContainer, **kwargs):
# define a utility function for manipulating the block container
defineNumpyType = lambda dtype: dtype if dtype in cls.NumPyTypes \
else (cls.ppt2npy[dType] if dtype in cls.PPTypes \
else (cls.gdt2npy[dtype] if dtype in cls.GdalTypes \
else (cls.ocv2npy[dtype] if dtype in cls.OCVTypes \
else (cls.pil2npy[dtype] if dtype in cls.PILTypes \
else None))))
# we assume here that non CV module use the same array format as Gdal ('zyx'); if this
# not the case, such module should be added to the RCVS_LIST_MODULES list and a specific
# format transformation should be implemented herein (through toAxis and toByte methods)
formatBlock = lambda block, oAxis, iAxis, dType, oMod: cls.toByte(cls.toAxis(block,oAxis,iAxis), dType, oMod)
# set/check the byte format of the data
dType = kwargs.pop(RCVS_KEY_BLOCKBYTE,None)
if not (dType is None or isinstance(dType,str)): raise IOError, 'unknown byte format %g' % dType
else: dType = defineNumpyType(dType)
# set/check the input/output format type (array arrangement) of the data
# and the module, when it exists
oMod = kwargs.pop(RCVS_KEY_MODULE,None)
iAxis, oAxis = [kwargs.pop(key,cls.mod2format[gdal__name__]) \
for key in (RCVS_KEY_INAXIS,RCVS_KEY_OUTAXIS)]
iAxis = iAxis if iAxis==vigra.__name__ else cls.checkFormatBlock(iAxis)[0]
oAxis = vigra.__name__ if (oMod==vigra.__name__ or oAxis==vigra.__name__) \
else cls.checkFormatBlock(oAxis)[0]
# define the complex formating function as a case handler
formatCase = lambda bC: [formatBlock(b, oAxis, iAxis, dType, oMod) for b in bC] if isinstance(bC,(list,tuple)) \
else (dict(zip(bC.keys(),[formatBlock(b, oAxis, iAxis, dType, oMod) for b in bC.values()])) if isinstance(bC,dict) \
else formatBlock(bC, oAxis, iAxis, dType, oMod))
# return the formatted block container
return formatCase(blockContainer)
#/************************************************************************/
# totype: method for converting the data to the desired type (structure)
@classmethod
def toType(cls, x, fmtOut=Array):
totypeList = lambda x: list(x) if isinstance(x,(tuple,Array)) \
else (x.values() if isinstance(x,dict) else x)
totypeTuple = lambda x: tuple(x) if isinstance(x,list) \
else (tuple(list(x)) if isinstance(x,Array) \
else (tuple(x.values()) if isinstance(x,dict) else x))
totypeArray = lambda x: Array(x) if isinstance(x,(list,tuple)) \
else (x.values()[0] if (isinstance(x,dict) and len(x)==1) \
else (x.values() if isinstance(x,dict) else x))
totypeDict = lambda x: dict(enumerate(x)) if isinstance(x,(list,tuple)) \
else (dict(enumerate((x,))) if isinstance(x,Array) else x)
return {list: totypeList,
tuple: totypeTuple,
Array: totypeArray,
dict: totypeDict,
None: lambda x:x
}[fmtOut](x)
# utility function for 'reducing' an iterable
@classmethod
def flattenIterable(cls, iterable):
#return reduce(lambda l, i: l + cls.flattenIterable(i) if isinstance(i, (list, tuple)) \
# else l + (i,), iterable, ())
if iterable is None: return ((),)
elif not isinstance(iterable, (list, tuple)): raise IOError
flatten = lambda iterable: reduce(lambda l, i: l + flatten(i) if isinstance(i, (list, tuple)) \
else l + (i,), iterable, ())
if isinstance(iterable,list): return list(flatten(iterable))
else: return flatten(iterable)
@classmethod
def definePicklable(cls, arg, unpick=False, block=False):
arg_cls_name = arg.__class__.__name__.lower()
if re.search('list',arg_cls_name):
pickle, unpickle = lambda arg, x: arg.append(x),lambda arg: arg.pop()
elif re.search('queue',arg_cls_name):
pickle, unpickle = lambda arg, x: arg.put(x), lambda arg: arg.get(block=block)
elif re.search('tuple',arg_cls_name): # tuples are immutable
pickle, unpickle = lambda arg, x: arg + (x,), lambda arg: arg
else:
raise IOError, 'unexpected picklable structure'
if unpick is False: return pickle
else: return unpickle
#/****************************************************************************/
# ImageReader overriding class
#/****************************************************************************/
class ImageReader(imagereader.ImageReader):
# variable set to True when one single block (ie. the whole image) is loaded
__singleBlock = False
# variable setting the array format (shape) of the blocks: any of the format
# used by the accepted CV modules
__formatBlock, __module = None, None
# define the type (byte) of the blocks ot read
__dtypeBlock = None
# variable for 'memorizing' the actual window size when it is decided to read
# one entire block
__windowxsize, __windowysize = None, None
#/************************************************************************/
def __init__(self, *args, **kwargs):
"""Retrieve specific keyword arguments not to be passed to the superclass.
>>> x = ImageReader(*args, **kwargs)
"""
self.__formatBlock, self.__module = \
Format.checkFormatBlock(kwargs.pop(RCVS_KEY_BLOCKFORMAT,None) \
or kwargs.pop(RCVS_KEY_OUTAXIS,gdal__name__))
self.__dtypeBlock = Format.checkTypeBlock(kwargs.pop(RCVS_KEY_BLOCKBYTE,None))
self.__singleBlock = kwargs.pop(RCVS_KEY_BLOCKSINGLE, False)
# run the super class method
imagereader.ImageReader.__init__(self, *args, **kwargs)
# store the desired window(x,y)size in case we modify it later when
# reading one block only
self.__windowxsize, self.__windowysize = self.windowxsize, self.windowysize
# at that stage, we may not know yet what will be exactly the size of the
# analyzing window as self.info has not been 'prepared' for sure; we force
# it however
if self.info is None: self.prepare(**kwargs)
#/************************************************************************/
def prepare(self, workingGrid=None, **kwargs):
"""
>>> x.prepare(workingGrid=None, **kwargs)
"""
# possibly create if not done already
if self.info is None:
imagereader.ImageReader.prepare(self, workingGrid=workingGrid)
# update the info field: in the case the whole image is to be read all
# at once (hence, one block only) reset the 'windowing' dimensions
if self.__singleBlock or kwargs.pop(RCVS_KEY_BLOCKSINGLE, False):
self.windowxsize, self.windowysize = self.info.xsize, self.info.ysize
self.info.windowxsize, self.info.windowysize = self.info.xsize, self.info.ysize
self.info.xtotalblocks, self.info.ytotalblocks = 1, 1
else: # reset if it has possibly been modified earlier
self.windowxsize, self.windowysize = self.__windowxsize, self.__windowysize
return
#/************************************************************************/
def readBlock(self, *nblock, **kwargs):
"""
>>> blockInfo, blockContainer = x.readBlock(*nblock, **kwargs)
"""
if nblock in ((),None): nblock=0
else: nblock=nblock[0]
# prepare and/or update
self.prepare(**kwargs)
# we still need to raise an OutsideImageBoundsError for the key counter
# (same condition as in imagereader.ImageReader.readBlock) when iterating
# over the reader
if nblock >= reduce(lambda x,y:x*y, self.info.getTotalBlocks()):
raise rioserrors.OutsideImageBoundsError()
# update the desired output format within kwargs, otherwise used the default
# one defined in the constructor
fmtOut, module = Format.checkFormatBlock(kwargs.pop(RCVS_KEY_BLOCKFORMAT,None) \
or kwargs.pop(RCVS_KEY_OUTAXIS,self.__formatBlock))
# perform the reading
if fmtOut==Format.mod2format[gdal__name__]:
# use the super class method (ibid with pool processing)
return imagereader.ImageReader.readBlock(self,nblock)
#return imagereader.ImageReader.readBlock(self,nblock)
else:
# the method otherwise is essentially the same as imagereader.ImageReader.readBlock,
# modulo the possible output data formatting
info, blockContainer = imagereader.ImageReader.readBlock(self,nblock)
dType = Format.checkTypeBlock(kwargs.pop(RCVS_KEY_BLOCKBYTE,self.__dtypeBlock))
module = module or self.__module # still possibly None
return info, Format.formatContainer(blockContainer,
**{RCVS_KEY_INAXIS: Format.mod2format[gdal__name__],
RCVS_KEY_OUTAXIS: fmtOut,
RCVS_KEY_BLOCKBYTE: dType,
RCVS_KEY_MODULE: module })
#/****************************************************************************/
# VectorReader overriding class
#/****************************************************************************/
class VectorReader(vectorreader.VectorReader):
__formatBlock, __module = None, None
__dtypeBlock = None
#/************************************************************************/
def __init__(self, arg, **kwargs):
"""Retrieve specific keyword arguments not to be passed to the superclass.
>>> x = VectorReader(*args, **kwargs)
"""
self.__formatBlock, self.__module = \
Format.checkFormatBlock(kwargs.pop(RCVS_KEY_BLOCKFORMAT,None) \
or kwargs.pop(RCVS_KEY_OUTAXIS,gdal__name__))
self.__dtypeBlock = Format.checkTypeBlock(kwargs.pop(RCVS_KEY_BLOCKBYTE,None))
vectorreader.VectorReader.__init__(self, arg, **kwargs)
#/************************************************************************/
def rasterize(self, info, **kwargs):
"""Retrieve specific keyword arguments not to be passed to the superclass.
>>> r = rasterize(info, **kwargs)
"""
fmtOut, module = Format.checkFormatBlock(kwargs.pop(RCVS_KEY_BLOCKFORMAT,None) \
or kwargs.pop(RCVS_KEY_OUTAXIS,self.__formatBlock))
dType = Format.checkTypeBlock(kwargs.pop(RCVS_KEY_BLOCKBYTE,self.__dtypeBlock))
module = module or self.__module
return Format.formatContainer(vectorreader.VectorReader.rasterize(self, info),
**{RCVS_KEY_INAXIS: Format.mod2format[gdal__name__],
RCVS_KEY_OUTAXIS: fmtOut,
RCVS_KEY_BLOCKBYTE: dType,
RCVS_KEY_MODULE: module})
#/****************************************************************************/
# ImageWriter overriding class
#/****************************************************************************/
class ImageWriter(imagewriter.ImageWriter):
# additional attributes w.r.t. original class for use in data format/shape
# conversion
__formatBlock = None
__dtypeBlock = None
__filename, __drivername, __creationoptions = None, None, None
__xsize, __ysize, __projection, __transform = None, None, None, None
__nbands, __gdaldatatype = None, None
#/************************************************************************/
def __init__(self, arg, **kwargs):
"""
>>> x = ImageWriter(arg, **kwargs)
"""
if arg is None:
raise IOError, 'missing output filename'
else:
self.__filename = arg
self.ds = None
self.blocknum = 0 # start writing at the first block
# retrieve info and firstblock (note that those keywords are defined in
# imagewriter.ImageWriter))
info, firstblock = kwargs.pop('info',None), kwargs.get('fisrtblock')
# retrieve and store the passed parameters
self.__drivername = kwargs.pop('drivername',imagewriter.DEFAULTDRIVERNAME)
self.__creationoptions = kwargs.pop('creationoptions',imagewriter.DEFAULTCREATIONOPTIONS) or []
# extract information from provided info if not None, possibly overwritting it
# (no error raising when 'conflictual' parameters are passed contrary to the
# original implementation)
self.__xsize, self.__ysize = kwargs.pop('xsize',None), kwargs.pop('ysize',None)
self.windowxsize, self.windowysize = kwargs.pop('windowxsize',None), kwargs.pop('windowysize',None)
if self.__xsize is None:
self.__xsize = info and info.getTotalSize()[0]
self.windowxsize = self.windowxsize or (info and info.getWindowSize()[0])
if self.__ysize is None:
self.__ysize = info and info.getTotalSize()[1]
self.windowysize = self.windowysize or (info and info.getWindowSize()[1])
self.__transform = kwargs.pop('transform',None) or (info and info.getTransform())
self.__projection = kwargs.pop('projection',None) or (info and info.getProjection())
self.overlap = kwargs.pop('overlap',None) or (info and info.getOverlapSize()) or 0 # shouldn't be None
self.xtotalblocks, self.ytotalblocks = (None,None) if info is None else info.getTotalBlocks()
self.__nbands, self.__gdaldatatype = kwargs.pop('nbands',None), kwargs.pop('gdaldatatype',None)
# format related information
self.__formatBlock, _ = Format.checkFormatBlock(kwargs.pop(RCVS_KEY_BLOCKFORMAT,None) \
or kwargs.pop(RCVS_KEY_INAXIS,gdal__name__))
self.__dtypeBlock = Format.checkTypeBlock(kwargs.pop(RCVS_KEY_BLOCKBYTE,None))
if firstblock is not None:
# we still check some consistency
if imagewriter.anynotNone([self.__nbands,self.__gdaldatatype]):
raise rioserrors.ParameterError('Must pass one either firstblock or nbands and gdaltype, not all of them')
if self.__formatBlock!=Format.mod2format[gdal__name__]:
# possibly transform the first block passed to the Writer instance
firstblock = Format.formatContainer(firstblock,
**{RCVS_KEY_INAXIS: self.__formatBlock,
RCVS_KEY_OUTAXIS: Format.mod2format[gdal__name__],
RCVS_KEY_BLOCKBYTE: self.__dtypeBlock,
RCVS_KEY_MODULE: gdal__name__})
# RIOS only works with 3-d image arrays, where the first dimension is
# the number of bands. Check that this is what the user gave us to write.
if len(firstblock.shape) != 3:
raise rioserrors.ArrayShapeError(
"Array dimension must be 3D instead of shape %s"%repr(firstblock.shape))
# prepare already the dataset in the case enough parameters have been set
self.prepare(firstblock=firstblock)
# if we have a first block then write it
if firstblock is not None: self.write(firstblock)
return
#/************************************************************************/
def prepare(self, firstblock=None):
"""
>>> x.prepare(firstblock=None)
"""
if firstblock is not None:
if self.windowxsize is None: self.windowxsize = firstblock.shape[-2] - 2*self.overlap
if self.windowysize is None: self.windowysize = firstblock.shape[-1] - 2*self.overlap
if self.__xsize<=0:
if self.xtotalblocks is None: raise rioserrors.ParameterError('Missing number of blocks')
self.__xsize = self.windowxsize * self.xtotalblocks
if self.__ysize<=0:
if self.ytotalblocks is None: raise rioserrors.ParameterError('Missing number of blocks')
self.__ysize = self.windowysize * self.ytotalblocks
(self.__nbands,y,x) = firstblock.shape # get the number of bands out of the block
self.__gdaldatatype = imageio.NumpyTypeToGDALType(firstblock.dtype) # and the datatype
# possibly update if it was not set before
if self.xtotalblocks is None: self.xtotalblocks = int(math.ceil(float(self.__xsize) / self.windowxsize))
if self.ytotalblocks is None: self.ytotalblocks = int(math.ceil(float(self.__ysize) / self.windowysize))
if not imagewriter.allnotNone((self.__nbands, self.__gdaldatatype)) \
or not imagewriter.allnotNone((self.__xsize, self.__ysize)) \
or self.__xsize<=0 or self.__ysize<=0:
return # do nothing, not enough parameters have been set
# else: create the output dataset
driver = gdal.GetDriverByName(self.__drivername)
# check that specified driver has gdal create method and go create
if driver is None: raise IOError, 'wrong driver definition'
else: metadata = driver.GetMetadata()
if not metadata.has_key(gdal.DCAP_CREATE) or metadata[gdal.DCAP_CREATE]=='NO':
raise TypeError, 'GDAL %s driver does not support Create(Copy) methods' % driver
elif not imagewriter.allnotNone((self.__xsize, self.__ysize)):
raise rioserrors.ParameterError('Must pass information about output image size')
if not isinstance(self.__filename,str): raise IOError, 'wrong filemane definition'
try:
self.ds = driver.Create(str(self.__filename), self.__xsize, self.__ysize,
self.__nbands, self.__gdaldatatype, self.__creationoptions)
except:
raise rioserrors.ImageOpenError('missing internal parameters')
if self.ds is None:
raise rioserrors.ImageOpenError('Unable to create output file %s' % self.__filename)
if self.__projection is not None:
self.ds.SetProjection(self.__projection)
if self.__transform is not None:
self.ds.SetGeoTransform(self.__transform)
return
#/************************************************************************/
def write(self, block, **kwargs):
"""
>>> x.write(block, **kwargs)
"""
# first shape the block container
fmtIn, _ = Format.checkFormatBlock(kwargs.pop(RCVS_KEY_BLOCKFORMAT,None) \
or kwargs.pop(RCVS_KEY_INAXIS,self.__formatBlock))
# we now adapt the write method to be able to perform multithread processing
blockNum = kwargs.pop(RCVS_KEY_BLOCKNUM, None) # tell us where to write
if blockNum is None:
blockNum = self.blocknum
elif not (isinstance(blockNum,int) and blockNum>=0):
raise IOError, 'wrong block index %s' % blockNum
dType = Format.checkTypeBlock(kwargs.pop(RCVS_KEY_BLOCKBYTE,self.__dtypeBlock))
if fmtIn!=Format.mod2format[gdal__name__] or dType is not None:
block = Format.formatContainer(block,
**{RCVS_KEY_INAXIS: fmtIn,
RCVS_KEY_OUTAXIS: Format.mod2format[gdal__name__],
RCVS_KEY_BLOCKBYTE: dType,
RCVS_KEY_MODULE: gdal__name__})
# possibly initialize the dataset with block considered as firstblock
if self.ds is None: self.prepare(firstblock=block)
# from here, it is copy/paste from write method
yblock, xblock = blockNum // self.xtotalblocks, blockNum % self.xtotalblocks
xcoord, ycoord = xblock * self.windowxsize, yblock * self.windowysize
self.writeAt(block, xcoord, ycoord)
# we keep the internal blocknum increment (in the case blockNum was None)
self.blocknum += 1 # use for serial writing
#/****************************************************************************/
# Job: class of methods for job definition and running
#/****************************************************************************/
class Job(collections.Callable):
#/************************************************************************/
def __getitem__(self, key):
if isinstance(key,str) and key in RCVS_KEYLIST_WORK:
try: return self.job[key]
except: warnMessage('key not set in designed job %s' % key)
else:
raiseMessage(IOError,'unexpected job key')
return
#def __repr__(self): return "<%s instance at %s>" % (self.__class__.__name__, id(self))
def __str__(self): return str(self.job)
# properties
@property
def function(self): return self.job[RCVS_KEY_FUNCTION]
@property
def module(self): return self.job[RCVS_KEY_MODULE]