From fb8e023299393b67ca47e7e915ae7de64a971306 Mon Sep 17 00:00:00 2001 From: Alireza Davoudi Date: Sat, 19 May 2018 14:56:29 +0430 Subject: [PATCH 01/12] removed plt.hold --- invert_font_size.py | 2 +- poisson_reconstruct.py | 4 ++-- synthgen.py | 4 ++-- visualize_results.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/invert_font_size.py b/invert_font_size.py index 5697467..f60cff0 100644 --- a/invert_font_size.py +++ b/invert_font_size.py @@ -21,7 +21,7 @@ FS = FontState() #plt.figure() -#plt.hold(True) +##plt.hold(True) for i in xrange(len(FS.fonts)): print i font = freetype.Font(FS.fonts[i], size=12) diff --git a/poisson_reconstruct.py b/poisson_reconstruct.py index 7f90899..aadf729 100644 --- a/poisson_reconstruct.py +++ b/poisson_reconstruct.py @@ -212,7 +212,7 @@ def contiguous_regions(mask): with sns.axes_style("darkgrid"): plt.subplot(2,1,2) plt.plot(l_alpha,label='alpha') - plt.hold(True) + #plt.hold(True) plt.plot(l_poisson,label='poisson') plt.plot(l_actual,label='actual') plt.legend() @@ -227,7 +227,7 @@ def contiguous_regions(mask): with sns.axes_style("white"): plt.subplot(2,1,1) plt.imshow(im_alpha[:,:,::-1].astype('uint8')) - plt.hold(True) + #plt.hold(True) plt.plot([0,im_alpha_L.shape[0]-1],[i,i],'r') plt.axis('image') plt.show() diff --git a/synthgen.py b/synthgen.py index 2fda5e9..1873eaa 100644 --- a/synthgen.py +++ b/synthgen.py @@ -280,7 +280,7 @@ def get_text_placement_mask(xyz,mask,plane,pad=2,viz=False): plt.imshow(mask) plt.subplot(1,2,2) plt.imshow(~place_mask) - plt.hold(True) + #plt.hold(True) for i in range(len(pts_fp_i32)): plt.scatter(pts_fp_i32[i][:,0],pts_fp_i32[i][:,1], edgecolors='none',facecolor='g',alpha=0.5) @@ -350,7 +350,7 @@ def viz_textbb(fignum,text_im, bb_list,alpha=1.0): plt.close(fignum) plt.figure(fignum) plt.imshow(text_im) - plt.hold(True) + #plt.hold(True) H,W = text_im.shape[:2] for i in range(len(bb_list)): bbs = bb_list[i] diff --git a/visualize_results.py b/visualize_results.py index 7b578c6..00f3f1f 100644 --- a/visualize_results.py +++ b/visualize_results.py @@ -24,7 +24,7 @@ def viz_textbb(text_im, charBB_list, wordBB, alpha=1.0): plt.close(1) plt.figure(1) plt.imshow(text_im) - plt.hold(True) + #plt.hold(True) H,W = text_im.shape[:2] # plot the character-BB: From f3c4c25b58fc4f6f96a6912badd1889263a17d95 Mon Sep 17 00:00:00 2001 From: Alireza Davoudi Date: Sat, 19 May 2018 14:58:53 +0430 Subject: [PATCH 02/12] fixed print and xrange --- invert_font_size.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invert_font_size.py b/invert_font_size.py index f60cff0..7b2461e 100644 --- a/invert_font_size.py +++ b/invert_font_size.py @@ -22,8 +22,8 @@ FS = FontState() #plt.figure() ##plt.hold(True) -for i in xrange(len(FS.fonts)): - print i +for i in range(len(FS.fonts)): + print(i) font = freetype.Font(FS.fonts[i], size=12) h = [] for y in ys: From 892ac0c17865f1c48e66e84f43d7af5279431dab Mon Sep 17 00:00:00 2001 From: Alireza Davoudi Date: Sat, 19 May 2018 14:59:29 +0430 Subject: [PATCH 03/12] fixed print and xrange --- prep_scripts/floodFill.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/prep_scripts/floodFill.py b/prep_scripts/floodFill.py index 1f61ef1..8440d6a 100644 --- a/prep_scripts/floodFill.py +++ b/prep_scripts/floodFill.py @@ -17,7 +17,7 @@ def get_seed(sx,sy,ucm): n = sx.size - for i in xrange(n): + for i in range(n): if ucm[sx[i]+1,sy[i]+1] == 0: return (sy[i],sx[i]) @@ -41,7 +41,7 @@ def get_mask(ucm,viz=False): sx,sy = np.where(mask==0) seed = get_seed(sx,sy,ucm) i += 1 - print " > terminated in %d steps"%i + print(" > terminated in %d steps"%i) if viz: plt.imshow(mask) @@ -81,7 +81,7 @@ def get_imname(self,i): return "".join(map(chr, self.ucm_h5[self.ucm_h5['names'][0,self.i]][:])) def __stop__(self): - print "DONE" + print("DONE") self.ucm_h5.close() raise StopIteration @@ -101,14 +101,14 @@ def get_valid_name(self): def next(self): imname = self.get_valid_name() - print "%d of %d"%(self.i+1,self.N) + print ("%d of %d"%(self.i+1,self.N)) ucm = self.ucm_h5[self.ucm_h5['ucms'][0,self.i]][:] ucm = ucm.copy() self.i += 1 return ((ucm>self.th).astype('uint8'),imname) ucm_iter = ucm_iterable(db_path,th) - print "cpu count: ", mp.cpu_count() + print ("cpu count: ", mp.cpu_count()) parpool = mp.Pool(4) ucm_result = parpool.imap_unordered(get_mask_parallel, ucm_iter, chunksize=1) @@ -116,16 +116,16 @@ def next(self): if res is None: continue ((mask,area,label),imname) = res - print "got back : ", imname + print ("got back : ", imname) mask = mask.astype('uint16') mask_dset = dbo_mask.create_dataset(imname, data=mask) mask_dset.attrs['area'] = area mask_dset.attrs['label'] = label # close the h5 files: - print "closing DB" + print ("closing DB") dbo.close() - print ">>>> DONE" + print (">>>> DONE") base_dir = '/home/' # directory containing the ucm.mat, i.e., output of run_ucm.m From f15aa78f455dd1ab1121f811f9064b1ce0babf5a Mon Sep 17 00:00:00 2001 From: Alireza Davoudi Date: Sat, 19 May 2018 18:01:26 +0430 Subject: [PATCH 04/12] Added support for persian and arabic --- fribidi.py | 999 ++++++++++++++++++++++++++++++++++++++++++++ invert_font_size.py | 18 +- synthgen.py | 58 +-- text_utils.py | 107 +++-- 4 files changed, 1100 insertions(+), 82 deletions(-) create mode 100644 fribidi.py diff --git a/fribidi.py b/fribidi.py new file mode 100644 index 0000000..5778090 --- /dev/null +++ b/fribidi.py @@ -0,0 +1,999 @@ +#!/usr/bin/env python +# coding=UTF-8 + +""" +an implementation of Unicode Bidirectional Algorithm, using GNU FriBidi + +This is a python wrap of GNU FriBidi C library. +http://fribidi.org/ + +GNU FriBidi is an implementation of Unicode Bidirectional Algorithm (bidi). +http://unicode.org/reports/tr9/ + +""" + + +import ctypes + + +# Load FriBidi + +_libfribidi = ctypes.CDLL("libfribidi.so") + +try: + _libfribidi.fribidi_shape() +except AttributeError: + libfribidi_version = '0.10' + libfribidi_version_major = 0 + libfribidi_version_minor = 10 +else: + libfribidi_version = '0.19' + libfribidi_version_major = 0 + libfribidi_version_minor = 19 + + +# Versions + +VERSION = '0.10' +"Version of the python wrapper." + + +# Memory allocation functions + +def _malloc_int_array(n): + + """ + Return a pointer to allocated C int array of length `n'. + """ + + t = ctypes.c_int * n + return t() + + +def _malloc_int8_array(n): + + """ + Return a pointer to allocated C int array of length `n'. + """ + + t = ctypes.c_int8 * n + return t() + + +def _malloc_int32_array(n): + + """ + Return a pointer to allocated C int32 array of length `n'. + """ + + t = ctypes.c_uint32 * n + return t() + + +def _malloc_int32_array_from_list(a, n=None): + + """ + Return a pointer to allocated C int32 array of length `n', initialized with `a' + + If `n' is not set, the length of `a' will be considered. + """ + + if n is None: + n = len(a) + + # Memory allocations + + m = _malloc_int32_array(n) + + for i in range(n): + m[i] = a[i] + + return m + + +def _malloc_char_array(n): + + """ + Return a pointer to allocated UTF8 (C char) array of length `n'. + """ + + t = ctypes.c_char * n + return t() + + +def _malloc_char_array_from_string(s): + + """ + Return a pointer to allocated UTF8 (C char) array, initialized with `s'. + """ + + return ctypes.c_char_p(s) + + +# Unicode type convertors + +def _pyunicode_to_utf32_p(a_pyunicode): + """Return UTF32 (C int32) array from Py_Unicode. + """ + + a_len = len(a_pyunicode) + + utf8_pystr = a_pyunicode.encode('UTF-8') + utf8_len = len(utf8_pystr) + utf8_p = _malloc_char_array_from_string(utf8_pystr) + + utf32_p = _malloc_int32_array(a_len+1) + _libfribidi.fribidi_utf8_to_unicode(utf8_p, utf8_len, utf32_p) + + return utf32_p + + +def _utf32_p_to_pyunicode(a_utf32_p): + """Return Py_Unicode from UTF32 (C int32) array. + """ + + utf32_len = ctypes.sizeof(a_utf32_p) // ctypes.sizeof(ctypes.c_uint32) + + utf8_len = 6*utf32_len+1 + utf8_p = _malloc_char_array(utf8_len) + + _libfribidi.fribidi_unicode_to_utf8(a_utf32_p, utf32_len, utf8_p) + + return utf8_p.value.decode('UTF-8') + + +# ######################################################################## +# FriBidi API, Bidi, Types (fribidi-bidi-types.h) + +# Character and Paragraph Masks and Types + +class Mask: + """ + TODO. + """ + + # Mask values + + RTL = 0x00000001 # Is right to left + ARABIC = 0x00000002 # Is arabic + + # Each character can be only one of the three following: + STRONG = 0x00000010 # Is strong + WEAK = 0x00000020 # Is weak + NEUTRAL = 0x00000040 # Is neutral + SENTINEL = 0x00000080 # Is sentinel + # Sentinels are not valid chars, just identify the start/end of strings. + + # Each charcter can be only one of the five following: + LETTER = 0x00000100 # Is letter: L, R, AL + NUMBER = 0x00000200 # Is number: EN, AN + NUMSEPTER = 0x00000400 # Is number separator or terminator: ES, ET, CS + SPACE = 0x00000800 # Is space: BN, BS, SS, WS + EXPLICIT = 0x00001000 # Is expilict mark: LRE, RLE, LRO, RLO, PDF + + # Can be set only if Mask.SPACE is also set. + SEPARATOR = 0x00002000 # Is test separator: BS, SS + + OVERRIDE = 0x00004000 # Is explicit override: LRO, RLO + + # The following must be to make types pairwise different, some of them can + # be removed but are here because of efficiency (make queries faster). + ES = 0x00010000 + ET = 0x00020000 + CS = 0x00040000 + + NSM = 0x00080000 + BN = 0x00100000 + + BS = 0x00200000 + SS = 0x00400000 + WS = 0x00800000 + + # We reserve a single bit for user's private use: we will never use it. + PRIVATE = 0x01000000 + + +class _Type: + """ + TODO. + """ + + # Strong types + + LTR = Mask.STRONG + Mask.LETTER # Left-To-Right letter + RTL = Mask.STRONG + Mask.LETTER + Mask.RTL # Right-To-Left letter + AL = Mask.STRONG + Mask.LETTER + Mask.RTL + Mask.ARABIC # Arabic Letter + LRE = Mask.STRONG + Mask.EXPLICIT # Left-to-Right Embedding + RLE = Mask.STRONG + Mask.EXPLICIT + Mask.RTL # Right-to-Left Embedding + LRO = Mask.STRONG + Mask.EXPLICIT + Mask.OVERRIDE # Left-to-Right Override + RLO = Mask.STRONG + Mask.EXPLICIT + Mask.RTL + Mask.OVERRIDE # Right-to-Left Override + + # Weak types + + PDF = Mask.WEAK + Mask.EXPLICIT # Pop Directional Override + EN = Mask.WEAK + Mask.NUMBER # European Numeral + AN = Mask.WEAK + Mask.NUMBER + Mask.ARABIC # Arabic Numeral + ES = Mask.WEAK + Mask.NUMSEPTER + Mask.ES # European number Separator + ET = Mask.WEAK + Mask.NUMSEPTER + Mask.ET # European number Terminator + CS = Mask.WEAK + Mask.NUMSEPTER + Mask.CS # Common Separator + NSM = Mask.WEAK + Mask.NSM # Non Spacing Mark + BN = Mask.WEAK + Mask.SPACE + Mask.BN # Boundary Neutral + + # Neutral types + + BS = Mask.NEUTRAL + Mask.SPACE + Mask.SEPARATOR + Mask.BS # Block Separator + SS = Mask.NEUTRAL + Mask.SPACE + Mask.SEPARATOR + Mask.SS # Segment Separator + WS = Mask.NEUTRAL + Mask.SPACE + Mask.WS # WhiteSpace + ON = Mask.NEUTRAL # Other Neutral + + # Paragraph-only types + + WLTR = Mask.WEAK # Weak Left-To-Right + WRTL = Mask.WEAK | Mask.RTL # Weak Right-To-Left + + SENTINEL = Mask.SENTINEL # start or end of text (run list) SENTINEL + # Only used internally + + PRIVATE = Mask.PRIVATE # Private types for applications + # More private types can be obtained by summing up from this one + + +class CharType: + """ + Class of character (direction) types. + + Strong types: + + LTR Left-To-Right letter + RTL Right-To-Left letter + AL Arabic Letter + LRE Left-to-Right Embedding + RLE Right-to-Left Embedding + LRO Left-to-Right Override + RLO Right-to-Left Override + + Weak types: + + PDF Pop Directional Override + EN European Numeral + AN Arabic Numeral + ES European number Separator + ET European number Terminator + CS Common Separator + NSM Non Spacing Mark + BN Boundary Neutral + + Neutral types: + + BS Block Separator + SS Segment Separator + WS WhiteSpace + ON Other Neutral + + """ + + LTR = _Type.LTR + RTL = _Type.RTL + AL = _Type.AL + EN = _Type.EN + AN = _Type.AN + ES = _Type.ES + ET = _Type.ET + CS = _Type.CS + NSM = _Type.NSM + BN = _Type.BN + BS = _Type.BS + SS = _Type.SS + WS = _Type.WS + ON = _Type.ON + LRE = _Type.LRE + RLE = _Type.RLE + LRO = _Type.LRO + RLO = _Type.RLO + PDF = _Type.PDF + + +class ParType: + + """ + Class of paragraph (direction) types: + + LTR Left-to-Right paragraph + RTL Right-to-Left paragraph + ON (Other) Neutral paragraph + WLTR Weak Left-to-Right paragraph + WRTL Weak Right-to-Left paragraph + + """ + + LTR = _Type.LTR + RTL = _Type.RTL + ON = _Type.ON + WLTR = _Type.WLTR + WRTL = _Type.WRTL + + +def level_is_rtl(lev): + + """ + Return True if `lev' is a Right-to-Left level, False otherwise. + + """ + + return lev & 1 + + +def level_to_dir(lev): + + """ + Return the bidi type corresponding to the direction of the level number. + + Return ParType.LTR for evens, and ParType.RTL for odds. + + """ + + return ParType.RTL if level_is_rtl(lev) else ParType.LTR + + +def dir_is_rtl(dir): + + """ + Return True if `dir' is a Right-to-Left, False otherwise. + + """ + + return dir & Mask.RTL + + +def dir_to_level(dir): + + """ + Return the minimum level of the direction. + + Return 0 for LTR and 1 for RTL. + + """ + + return 1 if dir_is_rtl(dir) else 0 + +# TODO: More dir functions (probabely should put into Type class) +# fribidi-bidi-types.h: 283-334 + + +# Functions + +def get_bidi_types(unicode_text, text_length=None): + + """ + Return characters bidi types. + + This function returns the bidi type of a character as defined in Table 3.7 + Bidirectional Character Types of the Unicode Bidirectional Algorithm + available at + http://www.unicode.org/reports/tr9/#Bidirectional_Character_Types, using + data provided in file UnicodeData.txt of the Unicode Character Database + available at http://www.unicode.org/Public/UNIDATA/UnicodeData.txt . + + """ + # TOD: There are a few macros defined in fribidi-bidi-types.h for querying a bidi type. + + + if not isinstance(unicode_text, str): + unicode_text = unicode(unicode_text) + + if not text_length: + text_length = len(unicode_text) + + # Memory allocations + + input_utf32_p = _pyunicode_to_utf32_p(unicode_text) + + output_chartype_p = _malloc_int32_array(text_length) + + # Calling the API + + """ + FRIBIDI_ENTRY void fribidi_get_bidi_types ( + const FriBidiChar *str, /* input string */ + const FriBidiStrIndex len, /* input string length */ + FriBidiCharType *btypes /* output bidi types */ + ); + """ + + if libfribidi_version_minor <= 10: + _libfribidi.fribidi_get_types( + input_utf32_p, # input string + text_length, # input string length + output_chartype_p # output bidi types + ) + + else: + _libfribidi.fribidi_get_bidi_types( + input_utf32_p, # input string + text_length, # input string length + output_chartype_p # output bidi types + ) + + # Pythonizing the output + + return [i for i in output_chartype_p] + + +# TODO: fribidi_get_bidi_type_name + + +# ######################################################################## +# FriBidi API, Bidi (fribidi-bidi.h) + + +def get_par_direction(bidi_types_list, text_length=None): + + """ + Return base paragraph direction + + No weak paragraph direction is returned, only LTR, RTL, or ON. + + Input: List of bidi types as returned by get_bidi_types() + + This function finds the base direction of a single paragraph, + as defined by rule P2 of the Unicode Bidirectional Algorithm available at + http://www.unicode.org/reports/tr9/#P2. + + You typically do not need this function as get_par_embedding_levels() knows + how to compute base direction itself, but you may need this to implement a + more sophisticated paragraph direction handling. + + Note that you can pass more than a paragraph to this function and the + direction of the first non-neutral paragraph is returned, which is a very + good heuristic to set direction of the neutral paragraphs at the beginning + of text. For other neutral paragraphs, you better use the direction of the + previous paragraph. + + """ + + if not text_length: + text_length = len(bidi_types_list) + + # Memory allocations + + input_bidi_types_p = _malloc_int32_array_from_list(bidi_types_list, text_length) + + # Calling the API + + ''' + FRIBIDI_ENTRY FriBidiParType fribidi_get_par_direction ( + const FriBidiCharType *bidi_types, /* input list of bidi types as returned by fribidi_get_bidi_types() */ + const FriBidiStrIndex len /* input string length */ + ); + ''' + + par_type = _libfribidi.fribidi_get_par_direction( + input_bidi_types_p, # input bidi types + text_length # input string length + ) + + # Pythonizing the output + + return int(par_type) + + +def get_par_embedding_levels(bidi_types_list, text_length=None, + base_direction=None): + + """ + Return the list of embedding levels of characters in the paragraph. + + A tuple of list of embedding levels, resolved paragraph direction, and + maximum embedding level will be returned. + + Input: List of bidi types as returned by get_bidi_types() + + This function finds the bidi embedding levels of a single paragraph, + as defined by the Unicode Bidirectional Algorithm available at + http://www.unicode.org/reports/tr9/. This function implements rules P2 to + I1 inclusive, and parts 1 to 3 of L1, except for rule X9 which is + implemented in remove_bidi_marks(). Part 4 of L1 is implemented in + reorder_line(). + + """ + + # TODO: There are a few macros defined in fribidi-bidi-types.h to work with this embedding levels. + + if not text_length: + text_length = len(bidi_types_list) + + if base_direction is None: + base_direction=ParType.LTR + + # Memory allocations + + input_bidi_types_p = _malloc_int32_array_from_list(bidi_types_list, text_length) + pbase_dir_p = ctypes.pointer(ctypes.c_int32(base_direction)) + + emb_p = _malloc_int8_array(text_length) + + # Calling the API + + """ + FRIBIDI_ENTRY FriBidiLevel fribidi_get_par_embedding_levels ( + const FriBidiCharType *bidi_types, /* input list of bidi types as returned by fribidi_get_bidi_types() */ + const FriBidiStrIndex len, /* input string length of the paragraph */ + FriBidiParType *pbase_dir, /* requested and resolved paragraph base direction */ + FriBidiLevel *embedding_levels /* output list of embedding levels */ + ) FRIBIDI_GNUC_WARN_UNUSED; + """ + + success = _libfribidi.fribidi_get_par_embedding_levels( + input_bidi_types_p, # input list of bidi types as returned by get_bidi_types() + text_length, # input string length of the paragraph + pbase_dir_p, # requested and resolved paragraph base direction + emb_p # output list of embedding levels + ) + + if not success: + raise Exception('fribidi_get_par_embedding_levels failed') + + # Pythonizing the output + + max_levels = success - 1 + + output_levels_list = [i for i in emb_p] + + return [output_levels_list, pbase_dir_p[0], max_levels] + + +def reorder_line(bidi_types_list, text_length=None, line_offset=0, + base_direction=None, with_max_levels=False): + + """ + Return visual reordered of a line of logical string + """ + + # TODO + + """ + This function reorders the characters in a line of text from logical to + final visual order. This function implements part 4 of rule L1, and rules + L2 and L3 of the Unicode Bidirectional Algorithm available at + http://www.unicode.org/reports/tr9/#Reordering_Resolved_Levels. + + As a side effect it also sets position maps if not NULL. + + You should provide the resolved paragraph direction and embedding levels as + set by fribidi_get_par_embedding_levels(). Also note that the embedding + levels may change a bit. To be exact, the embedding level of any sequence + of white space at the end of line is reset to the paragraph embedding level + (That is part 4 of rule L1). + + Note that the bidi types and embedding levels are not reordered. You can + reorder these (or any other) arrays using the map later. The user is + responsible to initialize map to something sensible, like an identity + mapping, or pass NULL if no map is needed. + + There is an optional part to this function, which is whether non-spacing + marks for right-to-left parts of the text should be reordered to come after + their base characters in the visual string or not. Most rendering engines + expect this behavior, but console-based systems for example do not like it. + This is controlled by the FRIBIDI_FLAG_REORDER_NSM flag. The flag is on + in FRIBIDI_FLAGS_DEFAULT. + + Returns: Maximum level found in this line plus one, or zero if any error + occured (memory allocation failure most probably). + + """ + + if not text_length: + text_length = len(bidi_types_list) + + if base_direction is None: + base_direction=ParType.LTR + + # Memory allocations + + input_bidi_types_p = _malloc_int32_array_from_list(bidi_types_list, text_length) + pbase_dir_p = ctypes.pointer(ctypes.c_int32(base_direction)) + + emb_p = _malloc_int8_array(text_length) + + # Calling the API + + """ + FRIBIDI_ENTRY FriBidiLevel fribidi_reorder_line ( + FriBidiFlags flags, /* reorder flags */ + const FriBidiCharType *bidi_types, /* input list of bidi types as returned by fribidi_get_bidi_types() */ + const FriBidiStrIndex len, /* input length of the line */ + const FriBidiStrIndex off, /* input offset of the beginning of the line in the paragraph */ + const FriBidiParType base_dir, /* resolved paragraph base direction */ + FriBidiLevel *embedding_levels, /* input list of embedding levels, as returned by fribidi_get_par_embedding_levels */ + FriBidiChar *visual_str, /* visual string to reorder */ + FriBidiStrIndex *map /* a map of string indices which is reordered to reflect where each glyph ends up. */ + ) FRIBIDI_GNUC_WARN_UNUSED; + """ + + success = _libfribidi.fribidi_get_par_embedding_levels( + input_flags, # reorder flags + input_bidi_types_p, # input list of bidi types as returned by get_bidi_types() + text_length, # input string length of the paragraph + line_offset, # input offset of the beginning of the line in the paragraph + pbase_dir_p, # requested and resolved paragraph base direction + emb_p, # output list of embedding levels + embedding_levels_p # input list of embedding levels, as returned by get_par_embedding_levels() + ) + + if not success: + raise Exception('fribidi_get_par_embedding_levels failed') + + # Pythonizing the output + + max_levels = success - 1 + + output_levels_list = [i for i in emb_p] + + if with_max_levels: + return [output_levels_list, max_levels] + else: + return output_levels_list + + + +# ######################################################################## +# FriBidi API, Misc + +def log2vis(unicode_text, text_length=None, base_direction=None, + with_l2v_position=False, with_v2l_position=False, + with_embedding_level=False): + """ + Return a unicode text contaning the visual order of characters in the text. + + If paragraph direction is not set (`base_direction'), it will be assumed to + to be letf-to-right (LTR). + + If any of with_l2v_position, with_v2l_position, and with_embedding_level + are True, the return value will be a tuple including logical-to-visual + position, visual-to-logical positions, or embedding-level lists + respectively. + + """ + + if not isinstance(unicode_text, str): + unicode_text = unicode(unicode_text) + + if not text_length: + text_length = len(unicode_text) + + if base_direction is None: + base_direction=ParType.LTR + + + # Memory allocations + + input_utf32_p = _pyunicode_to_utf32_p(unicode_text) + pbase_dir_p = ctypes.pointer(ctypes.c_int32(base_direction)) + + output_utf32_p = _malloc_int32_array(text_length+1) + + l2v_p = _malloc_int_array(text_length) if with_l2v_position else None + v2l_p = _malloc_int_array(text_length) if with_v2l_position else None + emb_p = _malloc_int8_array(text_length) if with_embedding_level else None + + # Calling the API + + """ + FRIBIDI_API fribidi_boolean fribidi_log2vis ( + + /* input */ + FriBidiChar *str, + FriBidiStrIndex len, + FriBidiCharType *pbase_dirs, + + /* output */ + FriBidiChar *visual_str, + FriBidiStrIndex *position_L_to_V_list, + FriBidiStrIndex *position_V_to_L_list, + FriBidiLevel *embedding_level_list + ); + """ + + successed = _libfribidi.fribidi_log2vis( + # input + input_utf32_p, + text_length, + pbase_dir_p, + + # output + output_utf32_p, + l2v_p, + v2l_p, + emb_p + ) + + if not successed: + raise Exception('fribidi_log2vis failed') + + # Pythonizing the output + + output_u = _utf32_p_to_pyunicode(output_utf32_p) + + if with_l2v_position or with_v2l_position or with_embedding_level: + res = (output_u, ) + if with_l2v_position: res += ([i for i in l2v_p], ) + if with_v2l_position: res += ([i for i in v2l_p], ) + if with_embedding_level: res += ([i for i in emb_p], ) + + else: + res = output_u + + return res + + +def log2vis_get_embedding_levels(unicode_text, base_direction=None): + + """ + Return an array containing the embedding-level of characters in the text. + + """ + + if not isinstance(unicode_text, str): + unicode_text = unicode(unicode_text) + + if base_direction is None: + base_direction=ParType.LTR + + text_len = len(unicode_text) + + # Memory allocations + + input_utf32_p = _pyunicode_to_utf32_p(unicode_text) + pbase_dir_p = ctypes.pointer(ctypes.c_int32(base_direction)) + + emb_p = _malloc_int8_array(text_len) + + # Calling the API + + """ + FRIBIDI_API fribidi_boolean fribidi_log2vis_get_embedding_levels ( + + /* input */ + FriBidiChar *str, + FriBidiStrIndex len, + FriBidiCharType *pbase_dir, + + /* output */ + FriBidiLevel *embedding_level_list + ); + """ + + successed = _libfribidi.fribidi_log2vis_get_embedding_levels( + # input + input_utf32_p, + text_len, + pbase_dir_p, + + # output + emb_p + ) + + if not successed: + raise Exception('fribidi_log2vis_get_embedding_levels failed') + + # Pythonizing the output + + res = [i for i in emb_p] + + return res + + +def remove_bidi_marks(unicode_text, text_length=None, + with_position_to=False, with_position_from=False, + with_embedding_level=False): + + """ + Return the text with all Bidirectional Marks removed. + + If with_position_to, with_position_from, or with_embedding_level are True, + the return value will be a tuple including positions from input text to + output text, positions from output text to input text, or embedding-level + lists respectively. + + Note: Seems the optional parameters of fribidi_remove_bidi_marks() doesn't + work or crash. Use them at your own risk. + + """ + + if not isinstance(unicode_text, str): + unicode_text = unicode(unicode_text) + + if not text_length: + text_length = len(unicode_text) + + # Memory allocations + + input_utf32_p = _pyunicode_to_utf32_p(unicode_text) + + pto_p = _malloc_int_array(text_length) if with_position_to else None + pfr_p = _malloc_int_array(text_length) if with_position_from else None + emb_p = _malloc_int8_array(text_length) if with_embedding_level else None + + # Calling the API + + """ + FRIBIDI_API FriBidiStrIndex fribidi_remove_bidi_marks ( + + /* input & output */ + FriBidiChar *str, + + /* input */ + FriBidiStrIndex length, + + /* output */ + FriBidiStrIndex *position_to_this_list, + FriBidiStrIndex *position_from_this_list, + FriBidiLevel *embedding_level_list + ); + """ + + new_length = _libfribidi.fribidi_remove_bidi_marks( + # input & output + input_utf32_p, + + # input + text_length, + + # output + pto_p, + pfr_p, + emb_p + ) + + + # Pythonizing the output + + output_u = _utf32_p_to_pyunicode(input_utf32_p) + + if with_position_to or with_position_from or with_embedding_level: + res = (output_u, ) + if with_position_to: res += ([i for i in pto_p], ) + if with_position_from: res += ([i for i in pfr_p], ) + if with_embedding_level: res += ([i for i in emb_p], ) + + else: + res = output_u + + return res + + +def get_mirror_chars(unicode_text): + + """ + Return TODO + + TODO. + + * fribidi_get_mirror_char() returns the mirrored character, if input + * character has a mirror, or the input itself. + * if mirrored_ch is NULL, just returns if character has a mirror or not. + + """ + + if not isinstance(unicode_text, str): + unicode_text = unicode(unicode_text) + + res = u'' + + for unicode_char in unicode_text: + text_length = len(unicode_text) + + # Memory allocations + + input_utf32_p = _pyunicode_to_utf32_p(unicode_char) + + output_utf32_p = _malloc_int32_array(text_length+1) + + # Calling the API + + """ + FRIBIDI_API fribidi_boolean fribidi_get_mirror_char ( + + /* Input */ + FriBidiChar ch, + + /* Output */ + FriBidiChar *mirrored_ch + ); + """ + + _libfribidi.fribidi_get_mirror_char( + # input + input_utf32_p[0], + # output + output_utf32_p + ) + + # Pythonizing the output + + res += _utf32_p_to_pyunicode(output_utf32_p) + + return res + + +def get_mirror_prop(unicode_text): + + """ + Return TODO + + TODO. + + * fribidi_get_mirror_char() returns the mirrored character, if input + * character has a mirror, or the input itself. + * if mirrored_ch is NULL, just returns if character has a mirror or not. + + """ + + if not isinstance(unicode_text, str): + unicode_text = unicode(unicode_text) + + res = [] + + for unicode_char in unicode_text: + + # Memory allocations + + input_utf32_p = _pyunicode_to_utf32_p(unicode_char) + + # Calling the API + + """ + FRIBIDI_API fribidi_boolean fribidi_get_mirror_char ( + + /* Input */ + FriBidiChar ch, + + /* Output */ + FriBidiChar *mirrored_ch + ); + """ + + is_mirror = _libfribidi.fribidi_get_mirror_char( + # input + input_utf32_p[0], + # output + None + ) + + # Pythonizing the output + + res.append(is_mirror) + + return res + + +def get_version_info(): + + """ + Return TODO + + TODO. + + """ + + # TODO + + return str(_libfribidi.fribidi_version_info) + + +# Main + +def _main(): + + """ + Print visual representation of command-line parameters (as a whole). + + """ + + import sys + text = ' '.join(sys.argv[1:]) + print(log2vis(text)) + + +if __name__=='__main__': + _main() diff --git a/invert_font_size.py b/invert_font_size.py index 7b2461e..6407185 100644 --- a/invert_font_size.py +++ b/invert_font_size.py @@ -4,10 +4,10 @@ import pygame from pygame import freetype -from text_utils import FontState +import os import numpy as np import matplotlib.pyplot as plt -import cPickle as cp +import pickle as cp pygame.init() @@ -19,21 +19,21 @@ xs = [] models = {} #linear model -FS = FontState() -#plt.figure() +FONT_LIST = './data/fonts/fontlist.txt' +fonts = [os.path.join('./data/fonts',f.strip()) for f in open(FONT_LIST)] ##plt.hold(True) -for i in range(len(FS.fonts)): - print(i) - font = freetype.Font(FS.fonts[i], size=12) +for i in range(len(fonts)): + print(fonts[i]) + font = freetype.Font(fonts[i], size=12) h = [] for y in ys: - h.append(font.get_sized_glyph_height(y)) + h.append(font.get_sized_glyph_height(int(y))) h = np.array(h) m,_,_,_ = np.linalg.lstsq(A,h) models[font.name] = m xs.append(h) -with open('font_px2pt.cp','w') as f: +with open('./data/models/font_px2pt.cp','wb') as f: cp.dump(models,f) #plt.plot(xs,ys[i]) #plt.show() diff --git a/synthgen.py b/synthgen.py index 1873eaa..872e208 100644 --- a/synthgen.py +++ b/synthgen.py @@ -443,6 +443,7 @@ def bb_filter(self,bb0,bb,text): bb : 2x4xn matrix of BB after perspective text: string of text -- for excluding symbols/punctuations. """ + return True h0 = np.linalg.norm(bb0[:,3,:] - bb0[:,0,:], axis=0) w0 = np.linalg.norm(bb0[:,1,:] - bb0[:,0,:], axis=0) hw0 = np.c_[h0,w0] @@ -471,6 +472,7 @@ def bb_filter(self,bb0,bb,text): def get_min_h(selg, bb, text): + return True # find min-height: h = np.linalg.norm(bb[:,3,:] - bb[:,0,:], axis=0) # remove newlines and spaces: @@ -549,34 +551,34 @@ def char2wordBB(self, charBB, text): output : 2x4xm matrix of BB coordinates, where, m == number of words. """ - wrds = text.split() - bb_idx = np.r_[0, np.cumsum([len(w) for w in wrds])] - wordBB = np.zeros((2,4,len(wrds)), 'float32') + # wrds = text.split() + # bb_idx = np.r_[0, np.cumsum([len(w) for w in wrds])] + # wordBB = np.zeros((2,4,len(wrds)), 'float32') - for i in range(len(wrds)): - cc = charBB[:,:,bb_idx[i]:bb_idx[i+1]] - - # fit a rotated-rectangle: - # change shape from 2x4xn_i -> (4*n_i)x2 - cc = np.squeeze(np.concatenate(np.dsplit(cc,cc.shape[-1]),axis=1)).T.astype('float32') - rect = cv2.minAreaRect(cc.copy()) - box = np.array(cv2.boxPoints(rect)) - - # find the permutation of box-coordinates which - # are "aligned" appropriately with the character-bb. - # (exhaustive search over all possible assignments): - cc_tblr = np.c_[cc[0,:], - cc[-3,:], - cc[-2,:], - cc[3,:]].T - perm4 = np.array(list(itertools.permutations(np.arange(4)))) - dists = [] - for pidx in range(perm4.shape[0]): - d = np.sum(np.linalg.norm(box[perm4[pidx],:]-cc_tblr,axis=1)) - dists.append(d) - wordBB[:,:,i] = box[perm4[np.argmin(dists)],:].T - - return wordBB + # for i in range(len(wrds)): + # cc = charBB[:,:,bb_idx[i]:bb_idx[i+1]] + + # # fit a rotated-rectangle: + # # change shape from 2x4xn_i -> (4*n_i)x2 + # cc = np.squeeze(np.concatenate(np.dsplit(cc,cc.shape[-1]),axis=1)).T.astype('float32') + # rect = cv2.minAreaRect(cc.copy()) + # box = np.array(cv2.boxPoints(rect)) + + # # find the permutation of box-coordinates which + # # are "aligned" appropriately with the character-bb. + # # (exhaustive search over all possible assignments): + # cc_tblr = np.c_[cc[0,:], + # cc[-3,:], + # cc[-2,:], + # cc[3,:]].T + # perm4 = np.array(list(itertools.permutations(np.arange(4)))) + # dists = [] + # for pidx in range(perm4.shape[0]): + # d = np.sum(np.linalg.norm(box[perm4[pidx],:]-cc_tblr,axis=1)) + # dists.append(d) + # wordBB[:,:,i] = box[perm4[np.argmin(dists)],:].T + + return charBB def render_text(self,rgb,depth,seg,area,label,ninstance=1,viz=False): @@ -689,5 +691,5 @@ def render_text(self,rgb,depth,seg,area,label,ninstance=1,viz=False): viz_masks(2,img,seg,depth,regions['label']) # viz_regions(rgb.copy(),xyz,seg,regions['coeff'],regions['label']) if i < ninstance-1: - raw_input(colorize(Color.BLUE,'continue?',True)) + input(colorize(Color.BLUE,'continue?',True)) return res diff --git a/text_utils.py b/text_utils.py index 1814eda..d72e52c 100644 --- a/text_utils.py +++ b/text_utils.py @@ -16,6 +16,8 @@ import math from common import * import pickle +import codecs +import fribidi def sample_weighted(p_dict): ps = list(p_dict.keys()) @@ -137,17 +139,20 @@ def render_multiline(self,font,text): for l in lines: x = 0 # carriage-return y += line_spacing # line-feed - - for ch in l: # render each character - if ch.isspace(): # just shift - x += space.width - else: + words = l.split(' ') + for word in words: + word = fribidi.log2vis(word, None, fribidi.ParType.RTL) + print('---->> ', word) + # for ch in l: # render each character + # if ch.isspace(): # just shift + # x += space.width + # else: # render the character - ch_bounds = font.render_to(surf, (x,y), ch) - ch_bounds.x = x + ch_bounds.x - ch_bounds.y = y - ch_bounds.y - x += ch_bounds.width - bbs.append(np.array(ch_bounds)) + ch_bounds = font.render_to(surf, (x,y), word) + ch_bounds.x = x + ch_bounds.x + ch_bounds.y = y - ch_bounds.y + x += ch_bounds.width + space.width + bbs.append(np.array(ch_bounds)) # get the union of characters for cropping: r0 = pygame.Rect(bbs[0]) @@ -167,13 +172,17 @@ def render_curved(self, font, word_text): """ use curved baseline for rendering word """ + # word_text = word_text[::-1] wl = len(word_text) isword = len(word_text.split())==1 + print(isword) # do curved iff, the length of the word <= 10 if not isword or wl > 10 or np.random.rand() > self.p_curved: return self.render_multiline(font, word_text) + word_text = fribidi.log2vis(word_text, None, fribidi.ParType.RTL) + print('<<<<<<<', word_text) # create the surface: lspace = font.get_sized_height() + 1 lbound = font.get_rect(word_text) @@ -186,14 +195,14 @@ def render_curved(self, font, word_text): curve = [BS['curve'](i-mid_idx) for i in range(wl)] curve[mid_idx] = -np.sum(curve) / (wl-1) rots = [-int(math.degrees(math.atan(BS['diff'](i-mid_idx)/(font.size/2)))) for i in range(wl)] - + bbs = [] # place middle char - rect = font.get_rect(word_text[mid_idx]) + rect = font.get_rect(word_text) rect.centerx = surf.get_rect().centerx rect.centery = surf.get_rect().centery + rect.height rect.centery += curve[mid_idx] - ch_bounds = font.render_to(surf, rect, word_text[mid_idx], rotation=rots[mid_idx]) + ch_bounds = font.render_to(surf, rect, word_text, rotation=rots[mid_idx]) ch_bounds.x = rect.x + ch_bounds.x ch_bounds.y = rect.y - ch_bounds.y mid_ch_bb = np.array(ch_bounds) @@ -201,36 +210,40 @@ def render_curved(self, font, word_text): # render chars to the left and right: last_rect = rect ch_idx = [] - for i in range(wl): - #skip the middle character - if i==mid_idx: - bbs.append(mid_ch_bb) - ch_idx.append(i) - continue - if i < mid_idx: #left-chars - i = mid_idx-1-i - elif i==mid_idx+1: #right-chars begin - last_rect = rect - - ch_idx.append(i) - ch = word_text[i] - - newrect = font.get_rect(ch) - newrect.y = last_rect.y - if i > mid_idx: - newrect.topleft = (last_rect.topright[0]+2, newrect.topleft[1]) - else: - newrect.topright = (last_rect.topleft[0]-2, newrect.topleft[1]) - newrect.centery = max(newrect.height, min(fsize[1] - newrect.height, newrect.centery + curve[i])) - try: - bbrect = font.render_to(surf, newrect, ch, rotation=rots[i]) - except ValueError: - bbrect = font.render_to(surf, newrect, ch) - bbrect.x = newrect.x + bbrect.x - bbrect.y = newrect.y - bbrect.y - bbs.append(np.array(bbrect)) - last_rect = newrect + bbs.append(mid_ch_bb) + ch_idx.append(0) + + # for i in range(wl): + # #skip the middle character + # if i==mid_idx: + # bbs.append(mid_ch_bb) + # ch_idx.append(i) + # continue + + # if i < mid_idx: #left-chars + # i = mid_idx-1-i + # elif i==mid_idx+1: #right-chars begin + # last_rect = rect + + # ch_idx.append(i) + # ch = word_text[i] + + # newrect = font.get_rect(ch) + # newrect.y = last_rect.y + # if i > mid_idx: + # newrect.topleft = (last_rect.topright[0]+2, newrect.topleft[1]) + # else: + # newrect.topright = (last_rect.topleft[0]-2, newrect.topleft[1]) + # newrect.centery = max(newrect.height, min(fsize[1] - newrect.height, newrect.centery + curve[i])) + # try: + # bbrect = font.render_to(surf, newrect, ch, rotation=rots[i]) + # except ValueError: + # bbrect = font.render_to(surf, newrect, ch) + # bbrect.x = newrect.x + bbrect.x + # bbrect.y = newrect.y - bbrect.y + # bbs.append(np.array(bbrect)) + # last_rect = newrect # correct the bounding-box order: bbs_sequence_order = [None for i in ch_idx] @@ -522,9 +535,13 @@ def __init__(self, min_nchar, fn): 'LINE':self.sample_line, 'PARA':self.sample_para} - with open(fn,'r') as f: - self.txt = [l.strip() for l in f.readlines()] - + # with open(fn,'r') as f: + # self.txt = [l.strip() for l in f.readlines()] + with codecs.open(fn, 'r', "utf-8") as f: + self.txt = [] + for line in f: + self.txt.append(line.strip()) + # distribution over line/words for LINE/PARA: self.p_line_nline = np.array([0.85, 0.10, 0.05]) self.p_line_nword = [4,3,12] # normal: (mu, std) From d300b14a847940621cd36a5a29224a37de96e72b Mon Sep 17 00:00:00 2001 From: Alireza Davoudi Date: Sun, 20 May 2018 14:25:58 +0430 Subject: [PATCH 05/12] A number of improvement : 1. Correct persian text representation using FriBidi lib 2. Modified bb_filter and get_min_h to work with word level 3. Cleanup text_utils 4. added update_freq.py script for updating char_freq.cp file 5. Modified text color to be different than the background color --- colorize3_poisson.py | 21 ++++++++++++++++ fribidi.py | 7 +++--- poisson_reconstruct.py | 2 +- prep_scripts/update_freq.py | 21 ++++++++++++++++ synthgen.py | 48 ++++++++++++++++++++----------------- text_utils.py | 42 +------------------------------- 6 files changed, 74 insertions(+), 67 deletions(-) create mode 100644 prep_scripts/update_freq.py diff --git a/colorize3_poisson.py b/colorize3_poisson.py index e4f32af..ee94ef5 100644 --- a/colorize3_poisson.py +++ b/colorize3_poisson.py @@ -17,6 +17,11 @@ def sample_weighted(p_dict): ps = p_dict.keys() return ps[np.random.choice(len(ps),p=p_dict.values())] +def rgb_color_diff_in_gray(col1, col2): + gray1 = col1[0]*0.299 + col1[1]*0.587 + col1[2]*0.114 + gray2 = col2[0]*0.299 + col2[1]*0.587 + col2[2]*0.114 + return abs(gray1 - gray2) + class Layer(object): def __init__(self,alpha,color): @@ -45,6 +50,7 @@ def __init__(self,alpha,color): class FontColor(object): def __init__(self, col_file): + self.gray_diff_threshold = 25 with open(col_file,'rb') as f: #self.colorsRGB = cp.load(f) u = pickle._Unpickler(f) @@ -90,10 +96,25 @@ def sample_from_data(self, bg_mat): col1 = self.sample_normal(data_col[:3],data_col[3:6]) col2 = self.sample_normal(data_col[6:9],data_col[9:12]) + ## fix as follows + true_bg_col = np.mean(np.mean(bg_orig, axis=0), axis=0) if nn < self.ncol: + fg_col = col2 + diff = rgb_color_diff_in_gray(fg_col, true_bg_col) + while diff < self.gray_diff_threshold: + #print 'change color' + fg_col = np.random.choice(256, 3).astype('uint8') + diff = rgb_color_diff_in_gray(fg_col, true_bg_col) + col2 = fg_col return (col2, col1) else: # need to swap to make the second color close to the input backgroun color + fg_col = col1 + diff = rgb_color_diff_in_gray(fg_col, true_bg_col) + while diff < self.gray_diff_threshold: + fg_col = np.random.choice(256, 3).astype('uint8') + diff = rgb_color_diff_in_gray(fg_col, true_bg_col) + col1 = fg_col return (col1, col2) def mean_color(self, arr): diff --git a/fribidi.py b/fribidi.py index 5778090..0ef5b39 100644 --- a/fribidi.py +++ b/fribidi.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# coding=UTF-8 - """ an implementation of Unicode Bidirectional Algorithm, using GNU FriBidi @@ -10,6 +7,10 @@ GNU FriBidi is an implementation of Unicode Bidirectional Algorithm (bidi). http://unicode.org/reports/tr9/ + +This file is originally written by behnam at https://github.com/behnam/python-fribidi. +I just slightly modified it to support python3 + """ diff --git a/poisson_reconstruct.py b/poisson_reconstruct.py index aadf729..aeb2282 100644 --- a/poisson_reconstruct.py +++ b/poisson_reconstruct.py @@ -87,7 +87,7 @@ def poisson_solve(gx,gy,bnd): return img -def blit_images(im_top,im_back,scale_grad=1.0,mode='max'): +def blit_images(im_top,im_back,scale_grad=1.0,mode='src'): """ combine images using poission editing. IM_TOP and IM_BACK should be of the same size. diff --git a/prep_scripts/update_freq.py b/prep_scripts/update_freq.py new file mode 100644 index 0000000..c1453e9 --- /dev/null +++ b/prep_scripts/update_freq.py @@ -0,0 +1,21 @@ +from collections import Counter +import pickle + +cnt = 0 +filename = './data/newsgroup/newsgroup.txt' +with open(filename, 'r') as f: + c = Counter() + for line in f.readlines(): + c += Counter(line.strip()) + cnt += len(line.strip()) + # print c +print(cnt) + +for key in c: + c[key] = float(c[key]) / cnt + print(key, c[key]) + +d = dict(c) +# print d +with open("./data/models/char_freq.cp", 'wb') as f: + pickle.dump(d, f) \ No newline at end of file diff --git a/synthgen.py b/synthgen.py index 872e208..bf2477c 100644 --- a/synthgen.py +++ b/synthgen.py @@ -271,10 +271,10 @@ def get_text_placement_mask(xyz,mask,plane,pad=2,viz=False): H,_ = cv2.findHomography(pts[0].astype('float32').copy(), pts_fp_i32[0].astype('float32').copy(), method=0) - Hinv,_ = cv2.findHomography(pts_fp_i32[0].astype('float32').copy(), pts[0].astype('float32').copy(), method=0) + if viz: plt.subplot(1,2,1) plt.imshow(mask) @@ -443,7 +443,7 @@ def bb_filter(self,bb0,bb,text): bb : 2x4xn matrix of BB after perspective text: string of text -- for excluding symbols/punctuations. """ - return True + # return True h0 = np.linalg.norm(bb0[:,3,:] - bb0[:,0,:], axis=0) w0 = np.linalg.norm(bb0[:,1,:] - bb0[:,0,:], axis=0) hw0 = np.c_[h0,w0] @@ -451,14 +451,16 @@ def bb_filter(self,bb0,bb,text): h = np.linalg.norm(bb[:,3,:] - bb[:,0,:], axis=0) w = np.linalg.norm(bb[:,1,:] - bb[:,0,:], axis=0) hw = np.c_[h,w] - + + if np.sum(hw0[:,1] == 0) > 0 or np.sum(hw[:,1] == 0) > 0: + return False # remove newlines and spaces: - text = ''.join(text.split()) - assert len(text)==bb.shape[-1] + # text = ''.join(text.split()) + # assert len(text)==bb.shape[-1] - alnum = np.array([ch.isalnum() for ch in text]) - hw0 = hw0[alnum,:] - hw = hw[alnum,:] + # alnum = np.array([ch.isalnum() for ch in text]) + # hw0 = hw0[alnum,:] + # hw = hw[alnum,:] min_h0, min_h = np.min(hw0[:,0]), np.min(hw[:,0]) asp0, asp = hw0[:,0]/hw0[:,1], hw[:,0]/hw[:,1] @@ -471,30 +473,32 @@ def bb_filter(self,bb0,bb,text): return is_good - def get_min_h(selg, bb, text): - return True + def get_min_h(self, bb, text): + # return True # find min-height: h = np.linalg.norm(bb[:,3,:] - bb[:,0,:], axis=0) # remove newlines and spaces: text = ''.join(text.split()) - assert len(text)==bb.shape[-1] + # assert len(text)==bb.shape[-1] - alnum = np.array([ch.isalnum() for ch in text]) - h = h[alnum] + # alnum = np.array([ch.isalnum() for ch in text]) + # h = h[alnum] return np.min(h) def feather(self, text_mask, min_h): # determine the gaussian-blur std: - if min_h <= 15 : - bsz = 0.25 - ksz=1 - elif 15 < min_h < 30: - bsz = max(0.30, 0.5 + 0.1*np.random.randn()) - ksz = 3 - else: - bsz = max(0.5, 1.5 + 0.5*np.random.randn()) - ksz = 5 + bsz = 0.5 * np.random.rand() + ksz = np.random.choice([1,3,5]) + # if min_h <= 15 : + # bsz = 0.25 + # ksz=1 + # elif 15 < min_h < 30: + # bsz = max(0.30, 0.5 + 0.1*np.random.randn()) + # ksz = 3 + # else: + # bsz = max(0.5, 1.5 + 0.5*np.random.randn()) + # ksz = 5 return cv2.GaussianBlur(text_mask,(ksz,ksz),bsz) def place_text(self,rgb,collision_mask,H,Hinv): diff --git a/text_utils.py b/text_utils.py index d72e52c..0c0ccfc 100644 --- a/text_utils.py +++ b/text_utils.py @@ -142,12 +142,6 @@ def render_multiline(self,font,text): words = l.split(' ') for word in words: word = fribidi.log2vis(word, None, fribidi.ParType.RTL) - print('---->> ', word) - # for ch in l: # render each character - # if ch.isspace(): # just shift - # x += space.width - # else: - # render the character ch_bounds = font.render_to(surf, (x,y), word) ch_bounds.x = x + ch_bounds.x ch_bounds.y = y - ch_bounds.y @@ -173,16 +167,15 @@ def render_curved(self, font, word_text): use curved baseline for rendering word """ # word_text = word_text[::-1] + word_text = word_text.replace('\u200c', ' ') wl = len(word_text) isword = len(word_text.split())==1 - print(isword) # do curved iff, the length of the word <= 10 if not isword or wl > 10 or np.random.rand() > self.p_curved: return self.render_multiline(font, word_text) word_text = fribidi.log2vis(word_text, None, fribidi.ParType.RTL) - print('<<<<<<<', word_text) # create the surface: lspace = font.get_sized_height() + 1 lbound = font.get_rect(word_text) @@ -208,43 +201,10 @@ def render_curved(self, font, word_text): mid_ch_bb = np.array(ch_bounds) # render chars to the left and right: - last_rect = rect ch_idx = [] - bbs.append(mid_ch_bb) ch_idx.append(0) - # for i in range(wl): - # #skip the middle character - # if i==mid_idx: - # bbs.append(mid_ch_bb) - # ch_idx.append(i) - # continue - - # if i < mid_idx: #left-chars - # i = mid_idx-1-i - # elif i==mid_idx+1: #right-chars begin - # last_rect = rect - - # ch_idx.append(i) - # ch = word_text[i] - - # newrect = font.get_rect(ch) - # newrect.y = last_rect.y - # if i > mid_idx: - # newrect.topleft = (last_rect.topright[0]+2, newrect.topleft[1]) - # else: - # newrect.topright = (last_rect.topleft[0]-2, newrect.topleft[1]) - # newrect.centery = max(newrect.height, min(fsize[1] - newrect.height, newrect.centery + curve[i])) - # try: - # bbrect = font.render_to(surf, newrect, ch, rotation=rots[i]) - # except ValueError: - # bbrect = font.render_to(surf, newrect, ch) - # bbrect.x = newrect.x + bbrect.x - # bbrect.y = newrect.y - bbrect.y - # bbs.append(np.array(bbrect)) - # last_rect = newrect - # correct the bounding-box order: bbs_sequence_order = [None for i in ch_idx] for idx,i in enumerate(ch_idx): From 42f5df9f143419d381566af419a3254f18693046 Mon Sep 17 00:00:00 2001 From: Alireza Davoudi Date: Sun, 20 May 2018 14:57:44 +0430 Subject: [PATCH 06/12] Added sample output image --- sample_fa.png | Bin 0 -> 547652 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 sample_fa.png diff --git a/sample_fa.png b/sample_fa.png new file mode 100644 index 0000000000000000000000000000000000000000..e17469c7af7d4589288b29a5700ef83e4a7f6bde GIT binary patch literal 547652 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