Skip to content

TexnoMagic Symbol

TexnoMagicSymbol

TexnoMagic Symbol has:

  • name: arbitrary unicode string without spaces
  • meaning: english meaning in lowercase
  • path: path to Symbol dir

Symbol can optionally contain:

  • drawings: a set of Drawings
  • images: images of the symbol in different formats (primary SVG)
  • model: model for symbol recognition

Symbols usually reside within an Alphabet.

This class provides convenient utilities for working with TexnoMagic Symbols, see individual methods.

Source code in texnomagic/symbol.py
 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
class TexnoMagicSymbol:
    """TexnoMagic Symbol has:

    * `name`: arbitrary unicode string without spaces
    * `meaning`: english meaning in lowercase
    * `path`: path to Symbol dir

    Symbol can optionally contain:

    * `drawings`: a set of [Drawings][texnomagic.drawing.TexnoMagicDrawing]
    * `images`: images of the symbol in different formats (primary SVG)
    * `model`: model for symbol recognition

    Symbols usually reside within an [Alphabet][texnomagic.abc.TexnoMagicAlphabet].

    This class provides convenient utilities for working with TexnoMagic Symbols,
    see individual methods.
    """
    def __init__(self, path=None, meaning=None, name=None):
        if path and path.name.lower() == INFO_FILE:
            # accept path to symbol info file as well
            path = path.parent

        self.path = path
        self.name = name
        self.meaning = meaning
        self._drawings = None
        self._images = None
        self._model = None

    @property
    def info_path(self) -> Path:
        f"""Path to Symbol `{INFO_FILE}` info file."""
        return self.path / INFO_FILE

    @property
    def drawings_path(self) -> Path:
        """Path to Symbol `drawings` dir."""
        return self.path / 'drawings'

    @property
    def model_path(self) -> Path:
        """Path to Symbol `model` dir."""
        return self.path / 'model'

    @property
    def image_base_path(self) -> Path:
        """Path to Symbol `image` dir."""
        return self.path / 'image'

    @property
    def handle(self) -> str:
        """Symbol handle (lowercase string)."""
        return common.name2handle(self.name)

    @property
    def model(self) -> TexnoMagicSymbolModel:
        """Symbol model.

        Use `symbol.model.ready` to check if there is actually an usable model."""
        if self._model is None:
            self.load_model()
        return self._model

    def get_image_path(self, format=common.IMAGE_FORMAT_DEFAULT) -> Path:
        return self.image_base_path / f'symbol.{format}'

    def get_images(self) -> dict:
        imgs = {}
        for format in common.IMAGE_FORMATS:
            image_path = self.get_image_path(format=format)
            if image_path.exists():
                imgs[format] = image_path
        return imgs

    @property
    def images(self) -> dict:
        """A dict of available Symbol images with format as key."""
        if self._images is None:
            self._images = self.get_images()
        return self._images

    def load(self, path=None):
        f"""Load Symbol metadata from info file ({INFO_FILE})."""
        if path:
            self.path = path

        assert self.path
        info = json.load(self.info_path.open())

        name = info.get('name')
        if not name:
            name = self.path.name
        self.name = name
        self.meaning = info.get('meaning')

        return self

    def load_drawings(self):
        """Load Symbol drawings from `drawings` dir."""
        self._drawings = []
        for drawing_path in self.drawings_path.glob('*'):
            drawing = TexnoMagicDrawing()
            drawing.load(drawing_path)
            self._drawings.append(drawing)

    def load_model(self):
        """Load Symbol model."""
        self._model = TexnoMagicSymbolModel(self.model_path)
        self._model.load()

    def train_model(self, n_gauss=0):
        """Train Symbol model from drawings."""
        if not self._model:
            self._model = TexnoMagicSymbolModel(self.model_path)
        if n_gauss:
            self._model.n_gauss = n_gauss
        return self._model.train_symbol(self)

    def save(self):
        """Save the Symbol into path."""
        self.path.mkdir(parents=True, exist_ok=True)
        info = {
            'name': self.name,
            'meaning': self.meaning,
        }
        return json.dump(info, self.info_path.open('w'))

    def save_new_drawing(self, drawing):
        """Save new Drawing into `drawings` dir."""
        assert drawing

        if self._drawings is None:
            self.load_drawings()

        fn = "%s_%s.csv" % (common.name2fn(self.name), int(time.time() * 1000))
        drawing.path = self.drawings_path / fn
        drawing.save()
        return self._drawings.insert(0, drawing)

    @property
    def drawings(self) -> list[TexnoMagicDrawing]:
        """A list of Symbol drawings in `drawings` dir.

        Lazy loaded on-demand.
        """
        if self._drawings is None:
            self.load_drawings()
        return self._drawings

    def get_all_drawing_points(self) -> np.array:
        """Get a list of all points from all drawings."""
        pp = [d.points for d in self.drawings]
        if pp:
            return np.concatenate(pp)
        return np.array([])

    def random_drawing(self) -> TexnoMagicDrawing:
        """Get a random drawing."""
        if self.drawings:
            return random.choice(self.drawings)
        return None

    def normalize(self):
        """Normalize all drawings. Overwrites files.

        See [texnomagic.drawing.TexnoMagicDrawing.normalize]."""
        for d in self.drawings:
            if d.points.any():
                d.normalize()
                d.save()

    def as_dict(self) -> dict:
        """Return Symbol as a dict."""
        images = {f: str(p.relative_to(self.path)) for f, p in self.images.items()}
        d = {
            'name': self.name,
            'meaning': self.meaning,
            'path': str(self.path),
            'n_drawings': len(self.drawings),
            'images': images,
        }
        if self.model:
            d['model'] = self.model.as_dict(relative_to=self.path)
        return d

    def pretty(self, drawings=False, images=False, model=False, path=False) -> str:
        """Pretty Symbol string with colors in rich formatting."""
        s = f'[bright_green]{self.name}[/]'
        if self.name != self.meaning:
            s += f' ([green]{self.meaning}[/])'

        extras = []
        if images and self.images:
            fmts = [f'[blue]{f.upper()}[/]' for f in self.images.keys()]
            extras.append(f"{', '.join(fmts)} image")
        if drawings and self.drawings:
            extras.append(f'[white]{len(self.drawings)}[/] drawings')
        if model and self.model.ready:
            extras.append(f'{self.model.pretty()}')
        if extras:
            s += f": {', '.join(extras)}"

        if path:
            s += f' @ [white]{self.path}[/]'

        return s

    def __str__(self) -> str:
        return f'{self.name} ({self.meaning})'

    def __repr__(self) -> str:
        return '<TexnoMagicSymbol %s>' % self.__str__()

drawings: list[TexnoMagicDrawing] property

A list of Symbol drawings in drawings dir.

Lazy loaded on-demand.

drawings_path: Path property

Path to Symbol drawings dir.

handle: str property

Symbol handle (lowercase string).

image_base_path: Path property

Path to Symbol image dir.

images: dict property

A dict of available Symbol images with format as key.

model: TexnoMagicSymbolModel property

Symbol model.

Use symbol.model.ready to check if there is actually an usable model.

model_path: Path property

Path to Symbol model dir.

as_dict()

Return Symbol as a dict.

Source code in texnomagic/symbol.py
187
188
189
190
191
192
193
194
195
196
197
198
199
def as_dict(self) -> dict:
    """Return Symbol as a dict."""
    images = {f: str(p.relative_to(self.path)) for f, p in self.images.items()}
    d = {
        'name': self.name,
        'meaning': self.meaning,
        'path': str(self.path),
        'n_drawings': len(self.drawings),
        'images': images,
    }
    if self.model:
        d['model'] = self.model.as_dict(relative_to=self.path)
    return d

get_all_drawing_points()

Get a list of all points from all drawings.

Source code in texnomagic/symbol.py
165
166
167
168
169
170
def get_all_drawing_points(self) -> np.array:
    """Get a list of all points from all drawings."""
    pp = [d.points for d in self.drawings]
    if pp:
        return np.concatenate(pp)
    return np.array([])

load_drawings()

Load Symbol drawings from drawings dir.

Source code in texnomagic/symbol.py
113
114
115
116
117
118
119
def load_drawings(self):
    """Load Symbol drawings from `drawings` dir."""
    self._drawings = []
    for drawing_path in self.drawings_path.glob('*'):
        drawing = TexnoMagicDrawing()
        drawing.load(drawing_path)
        self._drawings.append(drawing)

load_model()

Load Symbol model.

Source code in texnomagic/symbol.py
121
122
123
124
def load_model(self):
    """Load Symbol model."""
    self._model = TexnoMagicSymbolModel(self.model_path)
    self._model.load()

normalize()

Normalize all drawings. Overwrites files.

See [texnomagic.drawing.TexnoMagicDrawing.normalize].

Source code in texnomagic/symbol.py
178
179
180
181
182
183
184
185
def normalize(self):
    """Normalize all drawings. Overwrites files.

    See [texnomagic.drawing.TexnoMagicDrawing.normalize]."""
    for d in self.drawings:
        if d.points.any():
            d.normalize()
            d.save()

pretty(drawings=False, images=False, model=False, path=False)

Pretty Symbol string with colors in rich formatting.

Source code in texnomagic/symbol.py
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def pretty(self, drawings=False, images=False, model=False, path=False) -> str:
    """Pretty Symbol string with colors in rich formatting."""
    s = f'[bright_green]{self.name}[/]'
    if self.name != self.meaning:
        s += f' ([green]{self.meaning}[/])'

    extras = []
    if images and self.images:
        fmts = [f'[blue]{f.upper()}[/]' for f in self.images.keys()]
        extras.append(f"{', '.join(fmts)} image")
    if drawings and self.drawings:
        extras.append(f'[white]{len(self.drawings)}[/] drawings')
    if model and self.model.ready:
        extras.append(f'{self.model.pretty()}')
    if extras:
        s += f": {', '.join(extras)}"

    if path:
        s += f' @ [white]{self.path}[/]'

    return s

random_drawing()

Get a random drawing.

Source code in texnomagic/symbol.py
172
173
174
175
176
def random_drawing(self) -> TexnoMagicDrawing:
    """Get a random drawing."""
    if self.drawings:
        return random.choice(self.drawings)
    return None

save()

Save the Symbol into path.

Source code in texnomagic/symbol.py
134
135
136
137
138
139
140
141
def save(self):
    """Save the Symbol into path."""
    self.path.mkdir(parents=True, exist_ok=True)
    info = {
        'name': self.name,
        'meaning': self.meaning,
    }
    return json.dump(info, self.info_path.open('w'))

save_new_drawing(drawing)

Save new Drawing into drawings dir.

Source code in texnomagic/symbol.py
143
144
145
146
147
148
149
150
151
152
153
def save_new_drawing(self, drawing):
    """Save new Drawing into `drawings` dir."""
    assert drawing

    if self._drawings is None:
        self.load_drawings()

    fn = "%s_%s.csv" % (common.name2fn(self.name), int(time.time() * 1000))
    drawing.path = self.drawings_path / fn
    drawing.save()
    return self._drawings.insert(0, drawing)

train_model(n_gauss=0)

Train Symbol model from drawings.

Source code in texnomagic/symbol.py
126
127
128
129
130
131
132
def train_model(self, n_gauss=0):
    """Train Symbol model from drawings."""
    if not self._model:
        self._model = TexnoMagicSymbolModel(self.model_path)
    if n_gauss:
        self._model.n_gauss = n_gauss
    return self._model.train_symbol(self)