Browse Source

Commenting++

Yann Weber 3 years ago
parent
commit
3d0793d7f3
4 changed files with 160 additions and 32 deletions
  1. 27
    3
      python_rpnifs/__main__.py
  2. 92
    26
      python_rpnifs/expr.py
  3. 3
    1
      python_rpnifs/fractdim.py
  4. 38
    2
      python_rpnifs/ifs5.py

+ 27
- 3
python_rpnifs/__main__.py View File

@@ -9,6 +9,7 @@ except (ImportError, NameError) as e:
9 9
     raise e
10 10
 
11 11
 from PIL import Image
12
+from PIL.PngImagePlugin import PngImageFile, PngInfo
12 13
 import numpy as np
13 14
 from tqdm import tqdm
14 15
 
@@ -64,15 +65,38 @@ while True:
64 65
         im = Image.fromarray(ifs.get_image())
65 66
         im.save(outfile % i)
66 67
 
68
+        im_png = PngImageFile(outfile % i)
69
+        metadata = PngInfo()
70
+        metadata.add_text('Score', '%5.3f' % score)
71
+        metadata.add_text('Generation', '%04d' % generation)
72
+        metadata.add_text('Pool_position', '%02d' % i)
73
+        for key, text in ifs.expr_dict().items():
74
+            metadata.add_text(key, text)
75
+        im_png.save(outfile % i, pnginfo=metadata)
76
+
77
+    # Selecting best
67 78
     scores = sorted(scores, key=lambda v: 0 if v[1] == np.nan else v[1],
68 79
                     reverse=True)
69 80
     best = scores[:best_sz]
70 81
     for i, (ifs, score) in enumerate(best):
71 82
         print('%02d) %5.3f %s' % (i, score, ifs))
83
+
84
+    # Mutating best
72 85
     pool = [b for b, _ in best]
73 86
     for ifs, _ in best:
74 87
         for _ in range((pool_sz//best_sz)-1):
75
-            new = copy.copy(ifs)
76
-            new.mutation(n_mutation)
77
-            pool.append(new)
88
+            # needs >= IFS5
89
+            while True:
90
+                new = copy.copy(ifs)
91
+                new.mutation(n_mutation)
92
+                for cur in pool:
93
+                    if cur - new == 0:
94
+                        break
95
+                else:
96
+                    pool.append(new)
97
+                    break 
98
+            ###
99
+            #new = copy.copy(ifs)
100
+            #new.mutation(n_mutation)
101
+            #pool.append(new)
78 102
 

+ 92
- 26
python_rpnifs/expr.py View File

@@ -8,10 +8,30 @@ import numpy
8 8
 
9 9
 import pyrpn
10 10
 
11
+class RpnSym(object):
12
+    """ @brief Abstract class representing RpnExpr symbols """
13
+    
14
+    def __init__(self):
15
+        raise NotImplementedError('Abstract class')
16
+
17
+    def __sub__(self, b):
18
+        """ @return the difference between two symbols : 2 if types differs,
19
+            1 if same type but different value
20
+        """
21
+        if type(b) != type(self):
22
+            return 2
23
+        elif str(b) !=  str(self):
24
+            return 1
25
+        return 0
11 26
 
12
-class RpnOp(object):
27
+class RpnOp(RpnSym):
28
+    """ @brief an RPN operation symbol """
13 29
     
14 30
     def __init__(self, op=None):
31
+        """ @brief instanciate an RPN symbol
32
+            @param op if None a random op is choosen else a value from
33
+                pyrpn.get_ops().values() is expected
34
+        """
15 35
         if op is None:
16 36
             op = self.random()
17 37
         elif op not in pyrpn.get_ops().values():
@@ -24,25 +44,25 @@ class RpnOp(object):
24 44
     def __copy__(self):
25 45
         return type(self)(self.__op)
26 46
 
27
-    def __sub__(self, b):
28
-        if type(b) != type(self):
29
-            return 2
30
-        elif str(b) != str(self):
31
-            return 1
32
-        return 0
33
-
34 47
     def to_bytes(self):
48
+        """ @return a 9 bytes representation of the symbol """
35 49
         brepr = self.__op.encode('utf8')
36 50
         return bytes([1] + ([0]*(8-len(brepr)))) + brepr
37 51
 
38 52
     @staticmethod
39 53
     def random():
54
+        """ @return a random RpnOp instance """
40 55
         return random.choice(list(pyrpn.get_ops().values()))
41 56
 
42 57
 
43
-class RpnConst(object):
58
+class RpnConst(RpnSym):
59
+    """ @brief an RPN numeric constant symbol """
44 60
     
45 61
     def __init__(self, value=None):
62
+        """ @brief instanciate an RPN numeric constant symbol
63
+            @param value a 64bit unsigned integer (a random one is choosen
64
+                if None given)
65
+        """
46 66
         if value is None:
47 67
             value = random.randint(0, (1<<64)-1)
48 68
         elif value < 0 or value > (1<<64)-1:
@@ -55,20 +75,23 @@ class RpnConst(object):
55 75
     def __str__(self):
56 76
         return '0x%X' % self.__val
57 77
 
58
-    def __sub__(self, b):
59
-        if type(b) != type(self):
60
-            return 2
61
-        elif str(b) != str(self):
62
-            return 1
63
-        return 0
64
-
65 78
     def to_bytes(self):
79
+        """ @return a 9 bytes representation of the symbol """
66 80
         return bytes([0]) + self.__val.to_bytes(8, 'little')
67 81
 
68 82
 
69
-class RpnVar(object):
83
+class RpnVar(RpnSym):
84
+    """ @brief an RPN variable symbol
85
+        
86
+        Variables are indexed with integer, string representation are : A0, A1
87
+        etc.
88
+    """
70 89
 
71 90
     def __init__(self, varnum=None, nvar=1):
91
+        """ @brief instanciate an RPN variable symbol
92
+            @param varnum the variable number (random one choose if None given)
93
+            @param nvar the maximum variable number
94
+        """
72 95
         if varnum is None:
73 96
             if nvar == 1:
74 97
                 varnum = 0
@@ -81,6 +104,7 @@ class RpnVar(object):
81 104
     
82 105
     @property
83 106
     def nvar(self):
107
+        """ @return the maximum variable number """
84 108
         return self.__nvar
85 109
 
86 110
     def __copy__(self):
@@ -89,21 +113,26 @@ class RpnVar(object):
89 113
     def __str__(self):
90 114
         return 'A%d' % self.__val
91 115
 
92
-    def __sub__(self, b):
93
-        if type(b) != type(self):
94
-            return 2
95
-        elif str(b) != str(self):
96
-            return 1
97
-        return 0
98
-
99 116
     def to_bytes(self):
117
+        """ @return a 9 bytes representation of the symbol """
100 118
         brepr = str(self).encode('utf8')
101 119
         return bytes([1] + ([0]*(8-len(brepr)))) + brepr
102 120
 
103 121
 
104 122
 class RpnExpr(object):
123
+    """ @brief Wrapper on pyrpnifs.RPNExpr
124
+        
125
+        Allow using pyrpnifs.RPNExpr while keeping a high level representation
126
+        of the expression (using @ref RpnSym child classes).
127
+    """
105 128
 
106 129
     def __init__(self, sz=0, w_op=4, w_const=1, w_var=2, nvar=1):
130
+        """ @param sz The expression size for random generation
131
+            @param w_op RpnOp weight on random generation
132
+            @param w_const RpnConst weight on random generation
133
+            @param w_var RpnVar weight on random generation
134
+            @param n_var the variable count
135
+        """
107 136
         self.sz = 0
108 137
         self._weight = (w_op, w_const, w_var)
109 138
         choices = (RpnOp, RpnConst, lambda: RpnVar(nvar=nvar))
@@ -114,6 +143,9 @@ class RpnExpr(object):
114 143
             self.refresh_expr()
115 144
 
116 145
     def refresh_expr(self):
146
+        """ @brief Refresh the internal pyrpn.RPNExpr using the
147
+                @ref RpnSym representation
148
+        """
117 149
         self.pyrpn = pyrpn.RPNExpr(str(self), self.__nvar)
118 150
 
119 151
     def __str__(self):
@@ -130,16 +162,33 @@ class RpnExpr(object):
130 162
         return ret
131 163
 
132 164
     def to_bytes(self):
165
+        """ @return a bytes representation with 9 bytes for each symbols """
133 166
         return b''.join([elt.to_bytes() for elt in self.expr])
134 167
 
135 168
     def __sub__(self, b):
136 169
         return self.levenshtein(b)
137 170
 
138 171
     def eval(self, *args):
172
+        """ @brief Evaluate the expression with given arguments
173
+            @param args* an array of argument (with len  = nvar)
174
+        """
139 175
         return self.pyrpn.eval(*args)
140 176
 
141 177
     def mutation(self, n=1, min_len=3, w_add=1.25, w_del=1, w_mut=2,
142 178
                  w_mut_soft=4, w_add_elt=(1,1,1), w_mut_elt=(1,1,1)):
179
+        """ @brief Expression mutation
180
+            @param n mutation count
181
+            @param min_len the minimum length for an expression
182
+            @param w_add weight of adding a symbol
183
+            @param w_del weight of deleting a symbol
184
+            @param w_mut weight of mutating a symbol
185
+            @param w_mut_soft weight of mutationg a symbol without type change
186
+            @param w_add_elt weights for each types (op, const, var) when
187
+                adding a symbol
188
+            @param w_mut_elt weight for each types (op, const, var) when
189
+                mutating a symbol
190
+            @note The internal pyrpn.RPNExpr is refreshed after mutations
191
+        """
143 192
         for _ in range(n):
144 193
             if len(self.expr) <= min_len:
145 194
                 self.add(*w_add_elt)
@@ -156,6 +205,7 @@ class RpnExpr(object):
156 205
         pass
157 206
 
158 207
     def add(self, w_op=1, w_const=1, w_var=1):
208
+        """ @brief Mutate by adding a symbol """
159 209
         idx = random.randint(0,len(self.expr))
160 210
         new = random.choices((RpnOp, RpnConst, RpnVar), self._weight)[0]
161 211
         new = new(nvar=self.__nvar) if new == RpnVar else new()
@@ -163,11 +213,13 @@ class RpnExpr(object):
163 213
         pass
164 214
     
165 215
     def delete(self, *args):
216
+        """ @brief Mutate by deleting a symbol """
166 217
         idx = random.randint(0, len(self.expr)-1)
167 218
         self.expr.pop(idx)
168 219
         pass
169 220
 
170 221
     def mutate(self, w_op=1, w_const=1, w_var=1):
222
+        """ @brief Mutate changing a symbol into another """
171 223
         idx =  random.randint(0, len(self.expr)-1)
172 224
         new = random.choices((RpnOp, RpnConst, RpnVar), self._weight)[0]
173 225
         new = new(nvar=self.__nvar) if new == RpnVar else new()
@@ -175,6 +227,7 @@ class RpnExpr(object):
175 227
         pass
176 228
     
177 229
     def mutate_soft(self):
230
+        """ @brief Mutate changing a symbol into another of the same type """
178 231
         idx =  random.randint(0, len(self.expr)-1)
179 232
         cur = self.expr[idx]
180 233
         cls = type(cur)
@@ -185,8 +238,16 @@ class RpnExpr(object):
185 238
         self.expr[idx] =  new
186 239
         pass
187 240
 
188
-    def levenshtein(self, b, custom_weight=True):
189
-        a = self
241
+    def levenshtein(a, b, custom_weight=True):
242
+        """ @brief Calculate a levenshtein distance between two expression
243
+            @param a expression A
244
+            @param b expression B
245
+            @param custom_weight If True a different symbol of same type weight
246
+                1 and a different symbol of different type weight 2. Else no
247
+                special weight is applied.
248
+            @return an integer
249
+        """
250
+
190 251
         len_a = len(a.expr)
191 252
         len_b = len(b.expr)
192 253
 
@@ -209,6 +270,11 @@ class RpnExpr(object):
209 270
 
210 271
     @classmethod
211 272
     def from_string(cls, expr, nvar=1):
273
+        """ @brief Return an RpnExpr instance given a string representing
274
+            an expression
275
+            @param epxr the string representation
276
+            @param nvar the variable count
277
+        """
212 278
         res = RpnExpr(sz=0)
213 279
 
214 280
         for spl in expr.split(' '):

+ 3
- 1
python_rpnifs/fractdim.py View File

@@ -4,17 +4,19 @@ import scipy
4 4
 import scipy.misc
5 5
 
6 6
 def rgb2gray(rgb):
7
+    """ @return a grayscale version of an RGB "image" """
7 8
     r, g, b = rgb[:,:,0], rgb[:,:,1], rgb[:,:,2]
8 9
     gray = 0.2989 * r + 0.5870 * g + 0.1140 * b
9 10
     return gray
10 11
 
11 12
 def rgba2gray(rgba):
13
+    """ @return a grayscale version of an RGBA "image" """
12 14
     r, g, b, a = rgb[:,:,0], rgb[:,:,1], rgb[:,:,2], rgb[:,:,3]
13 15
     gray = (0.2989 * r + 0.5870 * g + 0.1140 * b) * (a/255)
14 16
     return gray
15 17
 
16 18
 def fractal_dimension(Z, threshold=None):
17
-    ''' @return MinkowskiBouligand dimension (computed) '''
19
+    ''' @return Minkowski-Bouligand dimension (computed) '''
18 20
     # Only for 2d image
19 21
     assert(len(Z.shape) == 2)
20 22
 

+ 38
- 2
python_rpnifs/ifs5.py View File

@@ -36,11 +36,25 @@ class IFS5(object):
36 36
             for j in range(self.width):
37 37
                 self._world[i][j] = [0,0,0,0]
38 38
     
39
-    def __str__(self):
39
+    def expr_dict(self):
40
+        el_lbl = 'XYrgba'
41
+        res = dict()
42
+        k_fmt = 'IFS:%02d:%c'
43
+        for i, expr in enumerate(self._expr):
44
+            for j, el in enumerate(expr):
45
+                res[k_fmt % (i, el_lbl[j])] = str(el)
46
+        return res
47
+                
48
+        
49
+
50
+    def _fel(self):
40 51
         el_lbl = 'XYrgba'
41 52
         fel = lambda el: ';'.join(['%s=(%s)' % (el_lbl[i], str(el[i]))
42 53
                                    for i in range(len(el))])
43
-        ret = ';'.join(['<%s>' % fel(el) for el in self._expr])
54
+        return [fel(el) for el in self._expr]
55
+
56
+    def __str__(self):
57
+        ret = ';'.join(['<%s>' % el for el in self._fel()])
44 58
         return ret
45 59
 
46 60
     def __copy__(self):
@@ -50,15 +64,32 @@ class IFS5(object):
50 64
         #ret._world = copy.deepcopy(self._world)
51 65
         return ret
52 66
 
67
+    def __sub__(self, b):
68
+        """ @return The sum of the levensthein distance between all expressions
69
+                of two ifs
70
+        """
71
+        return sum([sum([self._expr[i][j] - b._expr[i][j]
72
+                         for j in range(len(b._expr[i]))])
73
+                    for i in range(len(b._expr))])
74
+
53 75
     def get_image(self):
54 76
     	return self._world
55 77
  
56 78
     def mutation(self, n=1, rand=True):
79
+        """ @brief Apply mutation on expressions
80
+            @param n the mutation count
81
+            @param rand unused ?!
82
+        """
57 83
         n = 1 if n <= 1 else random.randint(1,n)
58 84
         for _ in range(n):
59 85
             random.choice(random.choice(self._expr)).mutation()
60 86
 
61 87
     def step(self):
88
+        """ @brief Calculate a step of IFS
89
+            
90
+            Choose randomly an expression and evaluate it, giving values for
91
+            each argument and storing values in "world"
92
+        """
62 93
         dr,dg,db,da = [int(e)
63 94
                        for e in self._world[self._position[0]][self._position[1]]]
64 95
 
@@ -86,6 +117,9 @@ class IFS5(object):
86 117
         self._world[self._position[0]][self._position[1]] = [r,g,b,a]
87 118
 
88 119
     def score(self):
120
+        """ @brief Calculate the world's score
121
+            @return a float, higher the better
122
+        """
89 123
         start = time.time()
90 124
         
91 125
         colcount = len(np.unique(np.reshape(self._world[:,:,:3], (-1,3)),
@@ -135,6 +169,8 @@ class IFS5(object):
135 169
             score -= sigma/100
136 170
         #colscore = abs(colcount-1024) / 1e5
137 171
         colscore = abs(colcount-2048) / 1e5
172
+        if colcount < 2048:
173
+            colscore *= 10
138 174
         score -= colscore
139 175
 
140 176
         printscore = lambda arr: '['+(', '.join(['%1.3f' % e for e in arr]))+']'

Loading…
Cancel
Save