Passed
Push — master ( bc1242...f803eb )
by Marcin
02:33
created

spotifier.sort_nicely()   A

Complexity

Conditions 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
#!/usr/bin/env python
2
"""
3
install to work on psd files: psd-tools3
4
"""
5
import argparse
6
from PIL import Image, ImageChops, Image, ImageDraw, ImageFont, ImageStat
7
import logging
8
import os
9
import re
10
11 View Code Duplication
def trim(im):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
12
    """
13
    https://stackoverflow.com/questions/10615901/trim-whitespace-using-pil
14
    """
15
    bg = Image.new(im.mode, im.size, im.getpixel((1,1)))
16
    diff = ImageChops.difference(im, bg)
17
    diff = ImageChops.add(diff, diff, 0.5, -100)  # 1.0
18
    bbox = diff.getbbox()
19
    if bbox:
20
        return im.crop(bbox)
21
    else: return im
22
23 View Code Duplication
def get_parser():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
24
    parser = argparse.ArgumentParser(
25
        description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
26
27
    #parser.add_argument('-', "--", help="", default="")
28
29
    parser.add_argument("-v", "--verbose",
30
                        action="store_true", help="be verbose")
31
    parser.add_argument("-d", "--debug",
32
                        action="store_true", help="be even more verbose")
33
34
    parser.add_argument("--dont-align",
35
                        action="store_true", help="don't align dots")
36
    parser.add_argument("-x", default=165, type=int)
37
    parser.add_argument("-y", default=120, type=int)
38
    parser.add_argument("--trim-rms", default=50, type=int)
39
    parser.add_argument("--size", default=100, type=int)
40
    parser.add_argument("-a", "--dont-annotate", action="store_true")
41
    parser.add_argument("map", help='map')
42
    parser.add_argument("file", help="pre-processed image(s)", nargs='+')
43
    return parser
44
45
46
def get_rms(im):
47
    stat = ImageStat.Stat(im)
48
    #r,g,b = stat.mean
49
    ## print('bg sum', stat.sum[0])
50
    ## print('bg mean', stat.mean[0])
51
    ## print('bg rms', stat.rms[0])
52
    return stat.rms[0]
53
54
55
def sort_nicely(l):
56
    """ Sort the given list in the way that humans expect.
57
    http://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
58
    """
59
    def convert(text): return int(text) if text.isdigit() else text
60
    def alphanum_key(key): return [convert(c) for c in re.split('([0-9]+)', key)]
61
    l.sort(key=alphanum_key)
62
    return l
63
64
65
if __name__ == '__main__':
66
    parser = get_parser()
67
    args = parser.parse_args()
68
69
    if list != type(args.file):
70
        args.file = [args.file]
71
72
    args.file = sort_nicely(args.file)
73
    
74
    outputs = []
75
76
    for file in args.file:
77
        logging.basicConfig(
78
            level=logging.INFO,
79
            format="%(asctime)s %(message)s",
80
            handlers=[
81
                logging.FileHandler("spotifier.log"),
82
                logging.StreamHandler()
83
            ])
84
        logging.info('file: %s ' % (str(args)))
85
86
        args.trim = not args.dont_align
87
        if file.endswith('.psd'):
88
             try:
89
                 from psd_tools import PSDImage
90
             except:
91
                 print('pip install psd-tools3')
92
93
             psd = PSDImage.load(file)
94
             if 1 or args.verbose:
95
                 for l in psd.layers:
96
                     if l.name != 'Background':
97
                         i = l.as_PIL()
98
                         #i.save('tmp.png')
99
                     print(l)
100
             img = psd.as_PIL()
101
            ## from PIL import Image, ImageSequence
102
            ## im = Image.open(args.file)
103
            ## layers = [frame.copy() for frame in ImageSequence.Iterator(im)]
104
            ## print(layers)
105
            ## img = layers
106
        else:
107
            img = Image.open(file)
108
109
        # load map, into figure
110
        list_txt = '['
111
        names = []
112
        for l in open(args.map):
113
            if l.strip():
114
                name = ''
115
                if '#' in l:
116
                    l, name = l.split('#')
117
                names.append(name.strip())  # collect names
118
                list_txt += '[' + l + '],'
119
        list_txt += ']'
120
        figure = eval(list_txt)
121
        if args.verbose: print('Figure:', figure)
122
123
        # format of the plate
124
        PLATE_FORMAT = [
125
            [0, 0, 1, 1, 1, 1, 0, 0],
126
            [0, 1, 1, 1, 1, 1, 1, 0],
127
            [1, 1, 1, 1, 1, 1, 1, 1],
128
            [1, 1, 1, 1, 1, 1, 1, 1],
129
            [1, 1, 1, 1, 1, 1, 1, 1],
130
            [1, 1, 1, 1, 1, 1, 1, 1],
131
            [1, 1, 1, 1, 1, 1, 1, 1],
132
            [0, 1, 1, 1, 1, 1, 1, 0],
133
            [0, 0, 1, 1, 1, 1, 1, 0],
134
            ]
135
136
        # parameters
137
        size = args.size
138
        half = size / 2
139
140
        x0 = args.x
141
        y0 = args.y
142
143
        dx = size
144
        dy = size
145
146
        pix = []
147
148
        # de novo make pix
149
        i = 0
150
        for yi in range(1, 10):  # rows
151
            y = y0 + dy * (yi - 1)
152
153
            f, to = 0, 8
154
            if yi in [1]:
155
                f, to = 2, 6
156
            if yi in [2, 8]:
157
                f, to = 1, 7
158
            if yi in [9]:
159
                f, to = 2, 7
160
161
            for xi in range(f, to):
162
                x = x0 + xi * (dx - 1)
163
                pix.append([x, y])
164
                i += 1
165
                # print(i, yi, f, to, x, y)
166
            
167
        x_id = 0
168
        y_id = 0
169
170
        spots = []
171
        spot_id = 1
172
        for row in PLATE_FORMAT:
173
            for i in row:
174
                #x = x0 + (xshift * x_id)  # 150 * 3
175
                #y = y0 + (yshift * y_id) # 120 * 3
176
                #print(x, y)
177
                if i: #  and y_id == 1:
178
                    # print('index', spot_id - 1)
179
                    x, y = pix[spot_id - 1] # index for list
180
                    #x = x + x0
181
                    #y = y + y0
182
                    area = (x - half, y - half, x + half, y + half)
183
                    cropped_img = img.crop(area)
184
                    rms = get_rms(img)
185
                    if args.trim and rms > args.trim_rms:
186
                        cropped_img = trim(cropped_img)
187
                    if args.debug:
188
                        cropped_img.save('auto-' + str(y_id) + '-' + str(x_id) + '.png')
189
                    if args.verbose: print(spot_id, '----------------',)
190
                    spot_id += 1
191
                    spots.append(cropped_img)
192
                x_id += 1
193
            y_id += 1 # move row down
194
            x_id = 0
195
196
        extra = 0
197
        if args.dont_annotate:
198
            extra = 600
199
        fig = Image.new('RGB', (len(figure[0]) * 100 + extra, len(figure) * 100))
200
        draw = ImageDraw.Draw(fig)
201
        x = 0
202
        y = 0
203
204
        # LOAD FONT
205
        # font = ImageFont.truetype('Pillow/Tests/fonts/FreeMono.ttf', 40)
206
        # should work for OSX
207
        try:
208
            fnt = 'Helvetica.ttc'
209
            font = ImageFont.truetype(fnt, size=40)
210
        except OSError:
211
            font = ImageFont.load_default() # should work for everything else
212
        font_bar = ImageFont.truetype(fnt, size=100)
213
        #####################################################
214
215
        picked_wt = False
216
217
        # based on the collected spots
218
        # no build the figure
219
        for i, row in enumerate(figure):
220
            spots_text = ''
221
            row_fig = Image.new('RGB', (len(figure[0]) * 100, len(figure) * 100)) # for calculations, for each raw new one
222
            row_fig_y = 0
223
            for s in row:  # spot in row
224
                # for center something like this https://stackoverflow.com/questions/1970807/center-middle-align-text-with-pil
225
                # this is for spot or gray box
226
                if s == 0: # put a gray box here
227
                    img_box = Image.new('RGB', (100, 100))
228
                    draw_box = ImageDraw.Draw(img_box)
229
                    draw_box.rectangle((0,0,100,100), fill ="#808080") # , outline ="red")
230
                    fig.paste(img_box, (x, y)) # s - 1 # index from 0
231
                    row_fig.paste(img_box, (x, row_fig_y))
232
                else:
233
                    fig.paste(spots[s - 1], (x, y)) # s - 1 # index from 0
234
                    row_fig.paste(spots[s - 1], (x, row_fig_y))
235
236
                # this is extra | 
237
                # if s == -1:  # put a gray box here
238
                #    img_box = Image.new('RGB', (100, 100))
239
                #    draw_box = ImageDraw.Draw(img_box)
240
                #    # 5 is width of the bar ;-)
241
                #    draw_box.rectangle((0,0,5,100), fill ="#fff") # , outline ="red")
242
                #    fig.paste(img_box, (x, y)) # s - 1 # index from 0
243
                #    row_fig.paste(img_box, (x, row_fig_y))
244
                #    x -= 100
245
                    
246
                spots_text += ' ' + str(s)
247
                x += 100
248
            # run it for the whole row
249
            if not picked_wt:
250
                wt = get_rms(row_fig)
251
                picked_wt = True
252
            row_fig_rms = get_rms(row_fig)
253
            d = round(row_fig_rms - wt, 1)
0 ignored issues
show
introduced by
The variable wt does not seem to be defined for all execution paths.
Loading history...
254
            if args.verbose: print("%.2f %.2f ∆" % (round(wt, 2), row_fig_rms), d)
255
            #print(round(1, 2), )
256
            # str(x) + '-' + str(y)
257
            if args.dont_annotate:
258
                draw.text((x, y), '|', font=font_bar, fill = 'darkgray')
259
                txt = str(d) + ' #' + str(i + 1) + ' ' +  names[i] + ' ' + spots_text
260
                draw.text((x + 20, y + 10), txt, font = font, fill ="white", align="center")#, , align ="right")
261
            y += 100
262
            x = 0
263
264
        fig.show()
265
266
        map_name = os.path.splitext(os.path.basename(args.map))[0]
267
        outfn = os.path.splitext(file)[0] + '_spots_' + map_name + '.png'
268
        fig.save(outfn)
269
        outputs.append(outfn)
270
271
    if len(outputs) > 1:
272
        # append files to make the final figure
273
        width = Image.open(outputs[0]).width
274
        img = Image.new('RGB', (width, 100 * len(outputs)))
275
        x = 0
276
        y = 0
277
        for output in outputs:
278
            out_img = Image.open(output)            
279
            img.paste(out_img, (x, y))              
280
            y += 100
281
        draw = ImageDraw.Draw(img)
282
        draw.line([(0, 0), (3, 3)], fill='orange', width=10)
283
284
        map_path = os.path.splitext(args.map)[0]
285
        img.save(map_path + '_all.png')
286
        img.show()
287