From 5e7c12730363a0215feaa15f7a1e019a36e9fd3b Mon Sep 17 00:00:00 2001 From: Taka Date: Tue, 2 May 2017 06:25:37 +0000 Subject: [PATCH] Added the ability to change the resolution of extracted images. --- asl_cards/parse.py | 54 ++++++++++------- asl_cards/tests/_test_case_base.py | 5 +- asl_cards/tests/synthetic-data/1-card.doc | Bin 26624 -> 26112 bytes asl_cards/tests/synthetic-data/1-card.pdf | Bin 16133 -> 15736 bytes asl_cards/tests/synthetic-data/2-cards.doc | Bin 26624 -> 26624 bytes asl_cards/tests/synthetic-data/2-cards.pdf | Bin 16627 -> 16262 bytes asl_cards/tests/synthetic-data/3-cards.doc | Bin 27136 -> 27136 bytes asl_cards/tests/synthetic-data/3-cards.pdf | Bin 18060 -> 17652 bytes startup_widget.py | 16 ++++-- ui/startup_widget.ui | 64 ++++++++++++++++++++- 10 files changed, 108 insertions(+), 31 deletions(-) diff --git a/asl_cards/parse.py b/asl_cards/parse.py index fa4983e..e5bd965 100644 --- a/asl_cards/parse.py +++ b/asl_cards/parse.py @@ -2,6 +2,8 @@ import sys import os import re import itertools +import time +import datetime import tempfile import locale from collections import namedtuple @@ -84,7 +86,7 @@ class PdfParser: self.on_error = on_error # nb: for showing the user an error message self.cancelling = False - def parse( self , target , max_pages=-1 , images=True ) : + def parse( self , target , max_pages=-1 , image_res=None ) : """Extract the cards from a PDF file.""" # locate the files we're going to parse if os.path.isfile( target ) : @@ -97,10 +99,11 @@ class PdfParser: ] # parse each file cards = [] + start_time = time.time() for file_no,fname in enumerate(fnames) : if self.cancelling : raise AnalyzeCancelledException() try : - file_cards = self._do_parse_file( float(file_no)/len(fnames) , fname , max_pages , images ) + file_cards = self._do_parse_file( float(file_no)/len(fnames) , fname , max_pages , image_res ) except AnalyzeCancelledException as ex : raise except Exception as ex : @@ -118,9 +121,11 @@ class PdfParser: self._progress( 1.0 , "Done." ) # filter out placeholder cards cards = [ c for c in cards if c.nationality != "_unused_" and c.name != "_unused_" ] + elapsed_time = int( time.time() - start_time ) + #print( "Elapsed time: {}".format( datetime.timedelta( seconds=elapsed_time ) ) ) return cards - def _do_parse_file( self , pval , fname , max_pages , images ) : + def _do_parse_file( self , pval , fname , max_pages , image_res ) : cards = [] # check if we have an index for this file # NOTE: We originally tried to get the details of each card by parsing the PDF files but unfortunately, @@ -184,9 +189,9 @@ class PdfParser: if max_pages > 0 and 1+page_no >= max_pages : break # extract the card images - if images : + if image_res : self._progress( pval , "Extracting images from {}...".format( os.path.split(fname)[1] ) ) - card_images = self._extract_images( fname , max_pages ) + card_images = self._extract_images( fname , max_pages , image_res ) if len(cards) != len(card_images) : raise RuntimeError( "Card mismatch in {}: found {} cards, {} card images.".format( @@ -248,17 +253,16 @@ class PdfParser: page_pos = page_pos , ) - def _extract_images( self , fname , max_pages ) : + def _extract_images( self , fname , max_pages , image_res ) : """Extract card images from a file.""" # clean up any leftover extracted images from a previous run # NOTE: It's important we do this, otherwise we might think they're part of this run. for f in _find_extracted_image_files() : os.unlink( f ) # extract each page from the PDF as an image - resolution = 300 # pixels/inch args = [ "_ignored_" , "-dQUIET" , "-dSAFER" , "-dNOPAUSE" , - "-sDEVICE=png16m" , "-r"+str(resolution) , + "-sDEVICE=png16m" , "-r"+str(image_res) , "-sOutputFile="+_EXTRACTED_IMAGES_FILENAME_TEMPLATE ] if max_pages > 0 : @@ -277,7 +281,7 @@ class PdfParser: # extract the cards (by splitting the page in half) fname2 = list( os.path.split( fname ) ) fname2[1] = os.path.splitext( fname2[1] ) - ypos = img_height * 48 / 100 + ypos = img_height * 48/100 # nb: the cards are not perfectly aligned in the page buf1 , size1 = self._crop_image( img , (0,0,img_width,ypos) , os.path.join( fname2[0] , fname2[1][0]+"a"+fname2[1][1] ) @@ -286,8 +290,11 @@ class PdfParser: img , (0,ypos+1,img_width,img_height) , os.path.join( fname2[0] , fname2[1][0]+"b"+fname2[1][1] ) ) - # check if this is the last page, and it has just 1 card on it - if page_no == len(image_fnames)-1 and size1[1] < 1000 and size2[1] < 1000 : + if not buf1 and not buf2 : + continue # nb: blank page + # check if this is the last page, and it has just 1 card (centred) on it (e.g. ItalianOrdnance.pdf) + cutoff = img_height / 4 + if page_no == len(image_fnames)-1 and size1[1] < cutoff and size2[1] < cutoff : # yup - extract it buf , _ = self._crop_image( img , (0,0,img_width,img_height) , @@ -295,9 +302,11 @@ class PdfParser: ) card_images.append( buf ) else : - # nope - save the extracted cards - card_images.append( buf1 ) - card_images.append( buf2 ) + # nope - save the extracted card(s) + if buf1 : + card_images.append( buf1 ) + if buf2 : + card_images.append( buf2 ) # clean up os.unlink( fname ) return card_images @@ -309,16 +318,19 @@ class PdfParser: bgd_col = img.getpixel( (0,0) ) bgd_img = Image.new( img.mode , img.size , bgd_col ) diff = ImageChops.difference( rgn , bgd_img ) - #diff = ImageChops.add(diff, diff, 2.0, -100) + diff = ImageChops.add(diff, diff, 2.0, -100) bbox = diff.getbbox() if bbox : + # save the cropped image rgn = rgn.crop( bbox ) - # save the cropped image - rgn.save( fname ) - with open( fname , "rb" ) as fp : - buf = fp.read() - os.unlink( fname ) - return buf , rgn.size + rgn.save( fname ) + with open( fname , "rb" ) as fp : + buf = fp.read() + os.unlink( fname ) + return buf , rgn.size + else : + # nb: we get here if the entire region is blank (e.g. the bottom half of a single-card page) + return None , None def _progress( self , pval , msg ) : """Call the progress callback.""" diff --git a/asl_cards/tests/_test_case_base.py b/asl_cards/tests/_test_case_base.py index 12e499b..3ab625c 100755 --- a/asl_cards/tests/_test_case_base.py +++ b/asl_cards/tests/_test_case_base.py @@ -21,10 +21,7 @@ class TestCaseBase( unittest.TestCase ) : None , #progress = lambda _,msg: print( msg , file=sys.stderr , flush=True ) ) - cards = pdf_parser.parse( fname2 , images=False ) - if False : - for c in cards : - print(c) + cards = pdf_parser.parse( fname2 , image_res=None ) # check the results if len(cards) != len(expected_cards) : raise RuntimeError( "{}: got {} cards, expected {}.".format( fname , len(cards) , len(expected_cards) ) ) diff --git a/asl_cards/tests/synthetic-data/1-card.doc b/asl_cards/tests/synthetic-data/1-card.doc index 5b52de6e802f9231843372bfb5cc93a23176dcc8..a37b9a14ae2e9380bd0601c59906456fa24a52a1 100755 GIT binary patch delta 1980 zcmbVNTWC~Q6y4|EJDKq@NhZm}iZPDqL=#_0MDP`-@zD}Nf`Xxn50W@Oq6q$!7fWANgI0yg=($#MW}(Qve&r*h2b_7j z%lg5ciG8i5JR!0t4nJ_x9mpTz&1Knh`TS4lEtvia?C;@Kz6WkdVs)lm6WRy%A{N~qX>pAUb0ym8_j9;9`gQN^2W+uVre}u{LT)O?eg}F+ zx0F2vXKzWye-BDb>cgfA@IbbMI5rr&zX>0cI?peGY|=8^_%$*bHQ+3GXQS9z5*meV z7Lo^B{pN7iYGf0!(gCHbw2NjU2J;a5(C0Eawk=ZJ}IQB3x4bLN~HXq=_ zIhfwJ>%J7^Xe4^AzI{=)^zW|By!!H{ov39Gh@7X869X&3Lo5Tolew{Bc zrOBqEKIMnPa3pfhRv3f+8lvG=F^wReKjclUYE<(pF$%?ULUzb0ur23_B|rY3kPZGe ze9kMqb4_wxL*(>lGB$#lbjbhd#sAoT>P)nCVF|dyQp#+gAK<+V-lpHI*86qMP8Bby zhnz!x9nhaI^uflFqOf#Hr*s3ovR~4e0T%~T^==oO#Bhkd=p6yb_kazs#4OTPAOs8p zEdE`9h5Q>p+hq(W0VaUyKn0x50Ivb(*k1vTc1sgDd%gv{2zbPc z4{qhV8*4??~Zb%di^qZnf+xs^PyKQ)PNgjcG9@ro&WdXBqXT zy-PMzV94`;1TY_{0zLJkokJ(R6WZ#loVU zlakmFv7$Qk+E}TL2tvb3NQjV#NL4~o3F`Y#@2N2@lK#mz|M?&1KmX(0Gp&zAYp-Z4 zFIYe>1&J17DS)4YgM+M{4>+McH$=jiCX#ZxQ8XT2kK2SoQnp(&C$Gp$fnbMOZaYB)W{9QzsLxfZPXtSZ*~YWUX;i zs>y{ou`vC_bh$5AemC=NU4S|SKP72u{U7xDsq|01rD(N_`PX4q? z=jhb_WL=i#I ztXkH|Yk35hQ@s}#>6AxU-LppOp+UvX!a0yNO$pk>h2CSIV7Y%@XR2iJJ>Nv)H&x$lK!$JyfTUK zba_a93W&fn@bGSMj>vKbM6W!!^n-nqSGdw%_Mjn&SDiqY3)WhAqAYk>YOR9NJFJ;Z z;Ms7$P$^hs;!P(~ zTIX@a4iP@a-+KzFs&Um<%a#?rnHHU(I31!m2HTT}mfQT|XTzPNoCLTbtpGQ$4M2D4 z3V<=tEdY0cdH{ZPUITm$J_6%`0l*JfZpEN~E_I$yd>EqB2ju zj|RnJSy~yXs_pHDiK_Ugx_*rxubx`xcXtl2uU35FH4h4*)WEm{EO$8Vl?~al(HBir z_lPd%ZuQPUgp1(6MoyX&=43fhE}RqOrYDs0VzJY^t?Iru!a*SR%jeZV~yi Nsm>O~&eyteo?qLO8JYk9 diff --git a/asl_cards/tests/synthetic-data/1-card.pdf b/asl_cards/tests/synthetic-data/1-card.pdf index 02c8f3baeabd5c414da77b5a20bd8f92865f20c6..fbf2c61bdb10702489372ff862697449f770be20 100755 GIT binary patch delta 2380 zcmcguYg7|w8kV5o#v3|`kW6X;C2H%b z+@v_$YNgsP2N0tLS}0yuw_Vz*fh}mEr5-QG8y2BjBUI5sXArw-S3_FBBL<0;^99o<^&}9IroM} z440R#|90!aoZ6Y)U!JJ568*P#$u&ZabJ;PXV~wlg6xqh=!PW{F0)Fz-imqv_L~ zt_ymrbq@c{gYA4VXKqy5I!#v~x!UFQJujJhGtzA)w^?Sm3g08 zZ8L)#{R9nq*Jc|bW8cp9_DFu-`NrP*Zfi+b%F-HZOu&`$hhfhq1FYBT{^c99W1^ac zK0rp^-rJrn`8M>N0SUHX^ZFP06N|Jt$@v{eUQ_`Y(J6wwGT(hmlRQ)N_t+a#Rc`y? zF9vmOAMZF<{&2}Iq5sMCr+Y4_ZFk}eHy6xV@x$1T%RZ%K zv7L$_|KKe4q1yLcbkIjU%L{X7?>TGsm6JYWrUCnLT}J%Y`kf<|^CQ}$hl4Jc9SNwq z)MSZnYOcAyFyriBjJ{n3NA2obpJ3czR z=Wo22iDTK)Pm@>P`nY(TMAl?0bG(x<n=Qa;~?v_-j{X9Rug=3){V2Z=cHZ| zefWS^R@J*b5g$h9c)VXfK1=)K$d%x&7dwk_lios_aqX*Y+F}TYP7zOl@gKX8e{+#) zWAR;JYJe3Ms3c4zfYBi}TP9=k;^{}+L|OobxoQd!Eu_YTPq`qJr^wF*qA*r)EIgUa zTY-~Wy_VDh5dU%Y)VrD0rbw?AvV}~PUV17BK~x+PV{`lG<%B-u`74W4{(6x z_V`j^`rv*?xLWG?`ofDpA>Rx*ob{c>jThTPH{1@oli5&7I5s2&D93jtZCSDNdfUXV z4GlM2(ns#&FT|Y4m~Yz+}H+OEb5?Lj9u9Z!4p+ADkgs_fMPHx}5VF^|?k+LH8!nE_7krhu)lITPl0 zCE8?-XgJ@pCH1I=KaTn=1Fse0GMr=AHDm z3w=%7)9+>I6${|m0O#Oi1Ks~bDPSOxtDu_zncO`Pa&Wco;`0V)$cxsLPa62LWJL%DH6FD zARU~Gi1eaJ7|ewuAqW>ng7g)MArK51a0o#KoDja`USg3OAB5J!N_ zk{d3QO-a`4X57FA0SKllWPZy*P=wLy%3Od6U{4wlg2A-TJ>jGC zrtzWwQCnRTU_*ufRR_T+E!9u11)+?}KIsE76#Bgn5>1zf(Wn23TKXJAv~qx;?a@b& zX0358Zl?dH0K8OV5iKJyz*7?h3D9o<)BrEhWFUY?I?qd#1FNJ8kuY8)Krj@h@3?p* sPJp2z3{xl+0y(6VW3oTa$fAF6o@QG}t(lzcFeHHae0FH4B3a4)719Mcz5oCK delta 2665 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 delta 781 zcma)4OK1~e5T5yWv(3YFH%+n{T1yP6)hbjp*cv2BQIUcsmaHOpF;uiZ4pzK`638v* zIz4!-9%@k!F?*|r)N>C8PYNO;TD)0{LZw2SNgrnizWx4r{4?L~Y_)~e7S{U|I<#L6 zD+g&-@27ie!I+K$97Bq02}*16i)#@81pw>JV*kTQu6tqwJmrgLVgPCOZEhQ-2^FR# zEr&L3Y9n}rPUz=xjb`)$KUegcaew)J)@;&M&rtd!50sf>qK8>@iPg7-RJgI&9Q%FD zJAOSvC2t-p6fk=6C=D8uewi(1NBJ!e(vp$GY1%ZJSaDx>{ZOR*joE5!2A)G$Z07V zV&o}i^%7YrnzsfSWt!JR6g0hbAoL0MG9Uckr`MLOXXJq~I zhD5m9BR3dV7+w4gwxBb23OH)*W_O|soLXcQw=+5~S8^VDH5%4*09qN1HJXJuWt>AMzN#*p}aQuT)>tSrkIL{jhDOU sxP8*(xW-|TC@f=y6Ygww*u zErn4L0a>jaQ30zcf=8$a#boaHJ_8lDdDi8u>4!#%0(2lBr- z`02l~Y3`B@ovy3*Q^PbP1f!@!-vpPJU~^`R7)Sm#@7z z7!KNhHM5ZgN5q}{@N8uOe@nLVnip&Fddn3VOgmFCV#TMN-gWzQF61YhYJ+SODw_@AbI|}_)`%6e-T}F;$OSN}f)V`LBhqcTQ5k4}%k@o3+BD(3jtT{a$ zeQb8$&CNEe@slRgiF*By9k{f1%j$c>X^o1SVg1q5t)q8mnw~J?HRHW&oBnQC`z-k- zmcTr*>4}7U(`u(M7rskx`A^bHV>@O}yWZw-WL$-Q$(5T< zb=aP=i$31ChkZ!@YQ~vEQu$p*uI;mc@#few@Dl!>+1}3T_iqvD=3__Ni{_8q$**pU ziTU(TmG#kz5X+WO>z9|?Lal3D4fzMh2a>B|FV3r0l->W_sd`m?UWu@`;Mol;)o``p zeo#_NxQBaT@V0WrBXy-A#^Y?-FY|(#(L8VdZaIGpSgGN%+81N^@!CHWD-^mc-xVhMjOZ1q# zy8B}q91xO95j2T}#_=Utl?K-V)EG^OCjD@UPNB0ILi{RpG=m7CxwDizp}`4J;YrFQ zJPCl{DJGQH$w`b5&;-;edBvnZ2qG5n83srwkxT}hT^3&ytl28H&L1tyTaY;H5aB+= ze5ky0e9a~^2eI8Q>m6Upu!Lm?*yqRWoZIP={uj6~Vdz4!(Nupqtn(()qwY2hwYwD4 z6MEtj<3n|i4cBXb=2y$sZ(R7`$Ipc3*VOBIoFs z&!XAkS%A$9k^V!QZ81=hW!^~W)7&->SPy)i>|*ok(Jq&hcH099XMqe%PuX-kz_sec zVA&`0ojb1R&*hxTY4h689k026zWULMpqJ&_=0?52}&$?iS5{E2ZXT3EgZd>lM z1E-`PB-8IycWL%Y7NMSPMbJIz76Lw07wCza8MvuJ0nAf^EH0P=u^>n#V1eYrN)@OC zpfX7S^Lb1@p(q&k1VK*-@_;~+U?yc^IuXF6Lwr#cPQJLXh)L&( zmQt3|2{Dt7h+Ki}jCC5FXQndM%QYRxGd(#RqFKa10_4kqfOJfUzx`4`$OaKHhg9>F zYcb{e6qQy&6H{!kO1}ZqDs>V!cfwx0?Eevbc1GsEgD()X?5D{GaYO7joxI44{bz+r zt5ahd0Aj;zF2VEi_WL6$KvZ}Ii2nyiHO#E_7)}-kLPQ_mO<~fx5XJCC1Wf|`F_A<{ z#(+pwmPD*23${*8mP*W-B;OL`A+dScD>rI|OsF3cjw8OA z!grx!LM;sTQpAystD-B9shW>|bVX2b= zg@305@urapP-Loq2%@&}Z|{VJ)9E456g`N`hsk9Df<&v-)&OuK)f>sJ!UZ@H7={C+ zHV^@D0yU`^@OIyG0{wtkG2h1@@%4dyxsbPj>(BEQA~Mns;_vSx^z)W^^CkaXBcA#J X>F^{iK9N+2ha#vYjlqzG`P2RhmHUkX delta 2764 zcmb7G3s@6Z7AAz3V|5j@i(2D=hKDrFBa;UZB9VkQh%v${AckbXP)KYTK#G#J+ATf{ zJA##mqM{V2vK3uc*45Ts1hu%JqCi`*ifDaR1W}7(ZwRl#?zijxX6~H%@44rmd+s^s zo>*5k2kq8OVXi24B^AV|rF(d@W_w%lMQ{^l7+nP8drV~X;7t&SW5bh2=i-=fNZ=R@$HvP8 zb|E}|ql^JXO(q-JF+}N=nj}(hwDK}wU>Fl1f!cT-3>1Ooh+L|&;82~KM3$$JVLI&)0%H&o zN>WO-l2XFJCP2Wb7x3wDrig*lJDAR4vUQaIGD=B-O%JW(r_)lvH-_gRzTltKQYo-t z7#eaW7#qB4PDE3E&NMY z>8BV##i?S(aTz3&cnb1jgVq&SRiA`>NY?EC2*Y=+84y0X{ zP`MrFL(+dA(qA^~{p`o&!>qH01J80J48%US57G$C8J_a)4@ePUz7yv}=J zp7!UNuH0p0wZ-@-KlY^J7 zrz~!Mwyat7hYv0un<24$WA`ITc-jvrgtfWHuf&{=$&an*dVNfkyT~qLS=X8Fc1v`>z}F4bexRZ1Qg?dE8Uk_?+?j)SRrw*t~1A{g%8} zpR=<~nM!PV@vZ}Jx8%6+djW%u-!9t!Cg;X!%Qd&xM$PNy;FsTPPF*w0n{nhPt7(hx zT0#4o+g)!pGj-KRKRaHVC6ST1f{-QQp)e*E4@C`zj<6z&l_{h#+>ju7Xs~>Ro3AoS6Q|c8Modbk zsPvk(lui$0raYw3E!S#PAk<-uM~kdZnPV9J(#qM&G?{j?-WF!OF65(uJ{ZzNAu1O@ zMkU9N8e@ETR0s_!Hg=e(K5L!AVesg53${6q(t9mt89om@)KI9VWtA0=ELT5~zHgqL zBh$q$DsZoTF@0X^eDTBklt*lr2C00W9Jue)?u>%^9Y0@G1xHU^U)*K>KworuAUG|w zCW}*X<%2U>8>*s%7zuZ42F|ru^ZP9R;mn!eab4Vf?cb5^JI$zX_dt8=4ORE=&GpU7 zcg!fUvia)5ogq7XC&9Q3wCrjx}OPN8f%Dm+7{6*!HqV;=N-<|8x_Q&;? zWTK6)YUB@&B}TfsW|Q2%P?bIgi1oUo&UZ-!mb45pMyL$3v^* zc|wM3YN|%)|!-3ngzMU+3ZP^#AP0 z8KM8kXT62s2+tREm8XNP2+^zKJ(DzI6Cp{WXsuVT&0y7s!UWyzJv$a7xPtLDA4Ui# YmQRFGFs95JUnmh{ix&tZ_(Rppuj_QCJ^%m! diff --git a/asl_cards/tests/synthetic-data/3-cards.doc b/asl_cards/tests/synthetic-data/3-cards.doc index 28a2d6da28ae70b0252095a395a051df3ddc4c66..a19582de626ebcd2a975654e8947dd7bc223bc76 100755 GIT binary patch delta 1212 zcma)*OH33&5Qe*F#@S_Mmwn4StAGj~0E4e(B}Z!)HTkBxtE#K2yJx!Rc=sG1 z%ayn;@}yEp=b~517cJ7D+V5kmjEjK0O$6n@F)yYPW6Tdt@;|x|aWDLg4PfEX?40|RSLQpPr zaxNo)`Y1R3Mi5@r$$GRtgt@sbQ%{RZHDaR^BnkR(SKB4zf)elKOx`cn)hQm+CM=7R z;I<>$cblE>v7!Mm3`Rgr8e_M>J@5!jfH=^>#_EsNr3E~zT6y{a7a#2&-__ha##Db( zq;QL&>rcO8wm!$ac;m2(+e(hGXRP=}k@z!M1`-mv!7h>I%&|o=ss(jvqRV+mTys`{ zZ1KRIEj~c6I9$T(TG(BOkQ?AW7z2~wHCO{b!8+Igzd)kflyr8as~1_GPdt6xd$_1r z#|>^q6Ld<(-J&bn-X=0&OvgY4bY8sOaff101 zATP-vAWuBGR>A*5J`{pB3bT1=5;VZ*KykR@WpHblI;LzS7&1L9cXOjF>Wo=SBo4bV z#?;i3lbjG;+gTam>Y~K%ehrAuP}#w9>y=ElG=r2)l#lt|*7UU7qdVNtR-^n;ST*=0 zqoBg%>oP2cRr?Y;#rIEznG6eSVa==+bg)L&fDgKDJeia+AJD!D$_~WQFZQpExw5Ih|muB#|_Uy!{ VOAQoE;osCQol`l!@XPf*>Ti|q-^>62 delta 1152 zcma)+OGs2<6vxkZXS|NeI1gtgEt{;yd{C)mnU7=!av_=;JuF(88=(l27P&BmFk5P8 z5v>XXu^KAo27w0Bx`k3MDk55_Nt+Zb#>dR`KQnVnYSS5h_nhyX^PTTKgL7(dPCYNw zRGfEe6JsMXJxwQ;@AOH`@2$g+p)EJY`x9wLp1)}clIM+cDi$VW7Wjx7a5b!huh z*2^cgJf5@!m7g~(HN}VELmGwCwg1-V;6KQn*1c|-#a0P>c*T08ToOe74X6C(7wKKL zwgIz#e_kq(fQZ#Whpo=(x5`XElaAj)`D{4Ner9|gei^x0whCIdOvKC^!kR@!kW>hr zZd;Hb^Qbc8$s~M2>G7ohQMAovnL5n%dV99)porg#v)ZSj78HN9^h=a}O>N0*&BjkCZ_`N;P-X7RSr2BfhFU*kb zPVR#0$Dw&xcja0T;vC1CcM(N@0EGfBP!8I-z_}yqEUZqzT;;ycpPY7e)A;7}sRIn> zL+}htfjJNbYhWF0fK8B`7dl_(8RvGMbvN?^tBdUp8}H8hta2irrsA?V&MAk;EqjqX zK}>zWJNZLLMI2H z1!$2>Ap7;MsftPiwNzB!L2r8dD68ca$^PwXUExGsMo?y)d_+)UCW4MZMe{HPDt)Du zP)W*0GwFlYocM^ujpOz0N>6C?B@y6z4V5KVZMXAjc@|6QkpkrXaC*2HxIt>Ft?)*8 zxxuRK2P CO5i2{ diff --git a/asl_cards/tests/synthetic-data/3-cards.pdf b/asl_cards/tests/synthetic-data/3-cards.pdf index 65a3e16f2db44278e857f34ab82d642912b9a784..677bb4b4078d728f981bf12dcdc43bf2c78c2b45 100755 GIT binary patch delta 3453 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 0 592 - 418 + 471 @@ -64,7 +64,7 @@ 2 - 8 + 0 0 @@ -263,6 +263,65 @@ + + + + 0 + + + + + &Resolution: + + + cbo_resolution + + + + + + + + 80 + 0 + + + + + 80 + 16777215 + + + + + + + + + 8 + true + + + + (higher values are slower, but look better) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -654,6 +713,7 @@ le_cards_dir btn_cards_dir + cbo_resolution le_save_db_fname btn_save_db_fname btn_analyze