diff --git a/main.py b/main.py index ade4f4e..d0d6f6a 100644 --- a/main.py +++ b/main.py @@ -52,6 +52,14 @@ def main(): p.add_argument('--delete-parts', action="store_true", default=False) p.set_defaults(func=run_merge_large_files) + def run_extract_FaceSynthetics(args): + from scripts import dev + dev.extract_FaceSynthetics(input_dir=args.input_dir) + + p = dev_subparsers.add_parser('extract_FaceSynthetics') + p.add_argument('--input-dir', default=None, action=fixPathAction, help="FaceSynthetics directory.") + p.set_defaults(func=run_extract_FaceSynthetics) + train_parser = subparsers.add_parser( "train", help="Train neural network.") train_parsers = train_parser.add_subparsers() @@ -64,6 +72,15 @@ def main(): p = train_parsers.add_parser('FaceAligner') p.add_argument('--faceset-path', default=None, action=fixPathAction, help=".dfs path") p.set_defaults(func=train_FaceAligner) + + def train_CTSOT(args): + from apps.trainers.CTSOT.CTSOTTrainerApp import run_app + run_app(userdata_path=Path(args.userdata_dir), faceset_path=Path(args.faceset_path)) + + p = train_parsers.add_parser('CTSOT') + p.add_argument('--userdata-dir', default=None, action=fixPathAction, help="Directory to save app data.") + p.add_argument('--faceset-path', default=None, action=fixPathAction, help=".dfs path") + p.set_defaults(func=train_CTSOT) def bad_args(arguments): parser.print_help() diff --git a/scripts/dev.py b/scripts/dev.py index f38209e..0d09cc8 100644 --- a/scripts/dev.py +++ b/scripts/dev.py @@ -26,7 +26,7 @@ def split_large_files(delete_original=False): SplittedFile.split(filepath, part_size=part_size, delete_original=delete_original) print('Done') -def extract_facesynthetics_dataset(input_dir): +def extract_FaceSynthetics(input_dir): """ extract FaceSynthetics dataset https://github.com/microsoft/FaceSynthetics @@ -53,7 +53,7 @@ def extract_facesynthetics_dataset(input_dir): """ input_path = Path(input_dir) faceset_path = input_path.parent / f'{input_path.name}.dfs' - + # fs = lib_face.Faceset(output_dbpath) # for ufm in fs.iter_UFaceMark(): # uimg = fs.get_UImage_by_uuid( ufm.get_UImage_uuid() ) @@ -65,7 +65,7 @@ def extract_facesynthetics_dataset(input_dir): filepaths = lib_path.get_files_paths(input_path)[:100] #TODO fs = lib_face.Faceset(faceset_path) - fs.clear_db() + fs.recreate() for filepath in lib_con.progress_bar_iterator(filepaths, 'Processing'): @@ -104,6 +104,7 @@ def extract_facesynthetics_dataset(input_dir): fs.add_UImage(uimg, format='png') fs.add_UFaceMark(ufm) + fs.shrink() fs.close() diff --git a/xlib/face/FLandmarks2D.py b/xlib/face/FLandmarks2D.py index 5dda90a..d3a4ff6 100644 --- a/xlib/face/FLandmarks2D.py +++ b/xlib/face/FLandmarks2D.py @@ -7,9 +7,9 @@ import numpy.linalg as npla from ..math import Affine2DMat, Affine2DUniMat from .ELandmarks2D import ELandmarks2D from .FRect import FRect +from .IState import IState - -class FLandmarks2D: +class FLandmarks2D(IState): def __init__(self): """ Describes 2D face landmarks in uniform float coordinates @@ -17,19 +17,20 @@ class FLandmarks2D: self._type : ELandmarks2D = None self._ulmrks : np.ndarray = None - def __getstate__(self): - return self.__dict__.copy() + def restore_state(self, state : dict): + self._type = IState._restore_enum(ELandmarks2D, state.get('_type', None)) + self._ulmrks = IState._restore_np_array(state.get('_ulmrks', None)) - def __setstate__(self, d): - self.__init__() - self.__dict__.update(d) + def dump_state(self) -> dict: + return {'_type' : IState._dump_enum(self._type), + '_ulmrks' : IState._dump_np_array(self._ulmrks), + } @staticmethod def create( type : ELandmarks2D, ulmrks : np.ndarray): """ ulmrks np.ndarray (*,2|3) """ - if not isinstance(type, ELandmarks2D): raise ValueError('type must be ELandmarks2D') diff --git a/xlib/face/FPose.py b/xlib/face/FPose.py index 7b76c43..801a21b 100644 --- a/xlib/face/FPose.py +++ b/xlib/face/FPose.py @@ -1,21 +1,23 @@ from typing import Tuple + import numpy as np + from .. import math as lib_math +from .IState import IState -class FPose: +class FPose(IState): """ Describes face pitch/yaw/roll """ def __init__(self): self._pyr : np.ndarray = None - def __getstate__(self): - return self.__dict__.copy() + def restore_state(self, state : dict): + self._pyr = IState._restore_np_array(state.get('_pyr', None)) - def __setstate__(self, d): - self.__init__() - self.__dict__.update(d) + def dump_state(self) -> dict: + return {'_pyr' : IState._dump_np_array(self._pyr)} def as_radians(self) -> Tuple[float, float, float]: """ diff --git a/xlib/face/FRect.py b/xlib/face/FRect.py index ccc4c60..d4457f8 100644 --- a/xlib/face/FRect.py +++ b/xlib/face/FRect.py @@ -5,22 +5,24 @@ from typing import List, Tuple import cv2 import numpy as np import numpy.linalg as npla + from .. import math as lib_math from ..math import Affine2DMat, Affine2DUniMat +from .IState import IState -class FRect: + +class FRect(IState): """ Describes face rectangle in uniform float coordinates """ def __init__(self): - self._pts = None + self._pts : np.ndarray = None - def __getstate__(self): - return self.__dict__.copy() + def restore_state(self, state : dict): + self._pts = IState._restore_np_array( state.get('_pts', None) ) - def __setstate__(self, d): - self.__init__() - self.__dict__.update(d) + def dump_state(self) -> dict: + return {'_pts' : IState._dump_np_array(self._pts) } @staticmethod def sort_by_area_size(rects : List['FRect']): diff --git a/xlib/face/FaceWarper.py b/xlib/face/FaceWarper.py index b0b7e95..439316f 100644 --- a/xlib/face/FaceWarper.py +++ b/xlib/face/FaceWarper.py @@ -39,29 +39,31 @@ class FaceWarper: self._img_to_face_uni_mat = img_to_face_uni_mat self._face_to_img_uni_mat = img_to_face_uni_mat.invert() - if rnd_state is None: - rnd_state = np.random - self._rnd_state_state = rnd_state.get_state() + rnd_state = np.random.RandomState() + rnd_state.set_state(rnd_state.get_state() if rnd_state is not None else np.random.RandomState().get_state()) - self._align_rot_deg = rnd_state.uniform(*align_rot_deg) if isinstance(align_rot_deg, Iterable) else align_rot_deg - self._align_scale = rnd_state.uniform(*align_scale) if isinstance(align_scale, Iterable) else align_scale - self._align_tx = rnd_state.uniform(*align_tx) if isinstance(align_tx, Iterable) else align_tx - self._align_ty = rnd_state.uniform(*align_ty) if isinstance(align_ty, Iterable) else align_ty + self._align_rot_deg = rnd_state.uniform(*align_rot_deg) if isinstance(align_rot_deg, Iterable) else align_rot_deg + self._align_scale = rnd_state.uniform(*align_scale) if isinstance(align_scale, Iterable) else align_scale + self._align_tx = rnd_state.uniform(*align_tx) if isinstance(align_tx, Iterable) else align_tx + self._align_ty = rnd_state.uniform(*align_ty) if isinstance(align_ty, Iterable) else align_ty self._rw_grid_cell_count = rnd_state.randint(*rw_grid_cell_count) if isinstance(rw_grid_cell_count, Iterable) else rw_grid_cell_count - self._rw_grid_rot_deg = rnd_state.uniform(*rw_grid_rot_deg) if isinstance(rw_grid_rot_deg, Iterable) else rw_grid_rot_deg - self._rw_grid_scale = rnd_state.uniform(*rw_grid_scale) if isinstance(rw_grid_scale, Iterable) else rw_grid_scale - self._rw_grid_tx = rnd_state.uniform(*rw_grid_tx) if isinstance(rw_grid_tx, Iterable) else rw_grid_tx - self._rw_grid_ty = rnd_state.uniform(*rw_grid_ty) if isinstance(rw_grid_ty, Iterable) else rw_grid_ty - + self._rw_grid_rot_deg = rnd_state.uniform(*rw_grid_rot_deg) if isinstance(rw_grid_rot_deg, Iterable) else rw_grid_rot_deg + self._rw_grid_scale = rnd_state.uniform(*rw_grid_scale) if isinstance(rw_grid_scale, Iterable) else rw_grid_scale + self._rw_grid_tx = rnd_state.uniform(*rw_grid_tx) if isinstance(rw_grid_tx, Iterable) else rw_grid_tx + self._rw_grid_ty = rnd_state.uniform(*rw_grid_ty) if isinstance(rw_grid_ty, Iterable) else rw_grid_ty + + self._rnd_state_state = rnd_state.get_state() self._cached = {} def transform(self, img : np.ndarray, out_res : int, random_warp : bool = True) -> np.ndarray: """ - transform an image. Subsequent calls will output the same result for any img shape and out_res. + transform an image. - img np.ndarray (HWC) + Subsequent calls will output the same result for any img shape and out_res. + + img np.ndarray (HWC) - out_res int + out_res int random_warp(True) bool """ diff --git a/xlib/face/Faceset.py b/xlib/face/Faceset.py index 62406eb..93f19da 100644 --- a/xlib/face/Faceset.py +++ b/xlib/face/Faceset.py @@ -38,7 +38,7 @@ class Faceset: self.shrink() else: cur.execute('END') - + def __del__(self): self.close() @@ -50,7 +50,7 @@ class Faceset: def __repr__(self): return self.__str__() def __str__(self): - return f"Faceset. UImage:{self.get_UImage_count()} UFaceMark:{self.get_UFaceMark_count()}" + return f"Faceset. UImage:{self.get_UImage_count()} UFaceMark:{self.get_UFaceMark_count()} UPerson:{self.get_UPerson_count()}" def _is_table_exists(self, name): return self._cur.execute(f"SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?", [name]).fetchone()[0] != 0 @@ -85,8 +85,8 @@ class Faceset: .execute('INSERT INTO FacesetInfo VALUES (1)') .execute('CREATE TABLE UImage (uuid BLOB, name TEXT, format TEXT, data BLOB)') - .execute('CREATE TABLE UPerson (uuid BLOB, name TEXT, age NUMERIC)') - .execute('CREATE TABLE UFaceMark (uuid BLOB, UImage_uuid BLOB, UPerson_uuid BLOB, pickled_bytes BLOB)') + .execute('CREATE TABLE UPerson (uuid BLOB, data BLOB)') + .execute('CREATE TABLE UFaceMark (uuid BLOB, UImage_uuid BLOB, UPerson_uuid BLOB, data BLOB)') ) if _transaction: @@ -99,46 +99,49 @@ class Faceset: ### UFaceMark ################### def _UFaceMark_from_db_row(self, db_row) -> UFaceMark: - uuid, UImage_uuid, UPerson_uuid, pickled_bytes = db_row - return pickle.loads(pickled_bytes) + uuid, UImage_uuid, UPerson_uuid, data = db_row + + ufm = UFaceMark() + ufm.restore_state(pickle.loads(data)) + return ufm def add_UFaceMark(self, ufacemark_or_list : UFaceMark): """ add or update UFaceMark in DB """ if not isinstance(ufacemark_or_list, Iterable): - ufacemark_or_list = [ufacemark_or_list] + ufacemark_or_list : List[UFaceMark] = [ufacemark_or_list] cur = self._cur cur.execute('BEGIN IMMEDIATE') - for ufacemark in ufacemark_or_list: - pickled_bytes = pickle.dumps(ufacemark) - uuid = ufacemark.get_uuid() - UImage_uuid = ufacemark.get_UImage_uuid() - UPerson_uuid = ufacemark.get_UPerson_uuid() + for ufm in ufacemark_or_list: + uuid = ufm.get_uuid() + UImage_uuid = ufm.get_UImage_uuid() + UPerson_uuid = ufm.get_UPerson_uuid() + + data = pickle.dumps(ufm.dump_state()) if cur.execute('SELECT COUNT(*) from UFaceMark where uuid=?', [uuid] ).fetchone()[0] != 0: - cur.execute('UPDATE UFaceMark SET UImage_uuid=?, UPerson_uuid=?, pickled_bytes=? WHERE uuid=?', - [UImage_uuid, UPerson_uuid, pickled_bytes, uuid]) + cur.execute('UPDATE UFaceMark SET UImage_uuid=?, UPerson_uuid=?, data=? WHERE uuid=?', + [UImage_uuid, UPerson_uuid, data, uuid]) else: - cur.execute('INSERT INTO UFaceMark VALUES (?, ?, ?, ?)', [uuid, UImage_uuid, UPerson_uuid, pickled_bytes]) + cur.execute('INSERT INTO UFaceMark VALUES (?, ?, ?, ?)', [uuid, UImage_uuid, UPerson_uuid, data]) cur.execute('COMMIT') - def get_UFaceMark_count(self) -> int: return self._cur.execute('SELECT COUNT(*) FROM UFaceMark').fetchone()[0] def get_all_UFaceMark(self) -> List[UFaceMark]: - return [ pickle.loads(pickled_bytes) for pickled_bytes, in self._cur.execute('SELECT pickled_bytes FROM UFaceMark').fetchall() ] - + return [ self._UFaceMark_from_db_row(db_row) for db_row in self._cur.execute('SELECT * FROM UFaceMark').fetchall() ] + def get_UFaceMark_by_uuid(self, uuid : bytes) -> Union[UFaceMark, None]: c = self._cur.execute('SELECT * FROM UFaceMark WHERE uuid=?', [uuid]) db_row = c.fetchone() if db_row is None: return None - + return self._UFaceMark_from_db_row(db_row) - + def iter_UFaceMark(self) -> Generator[UFaceMark, None, None]: """ returns Generator of UFaceMark @@ -156,35 +159,44 @@ class Faceset: ################### ### UPerson ################### + def _UPerson_from_db_row(self, db_row) -> UPerson: + uuid, data = db_row + up = UPerson() + up.restore_state(pickle.loads(data)) + return up + def add_UPerson(self, uperson_or_list : UPerson): """ add or update UPerson in DB """ if not isinstance(uperson_or_list, Iterable): - uperson_or_list = [uperson_or_list] + uperson_or_list : List[UPerson] = [uperson_or_list] cur = self._cur cur.execute('BEGIN IMMEDIATE') for uperson in uperson_or_list: uuid = uperson.get_uuid() - name = uperson.get_name() - age = uperson.get_age() + + data = pickle.dumps(uperson.dump_state()) + if cur.execute('SELECT COUNT(*) from UPerson where uuid=?', [uuid]).fetchone()[0] != 0: - cur.execute('UPDATE UPerson SET name=?, age=? WHERE uuid=?', [name, age, uuid]) + cur.execute('UPDATE UPerson SET data=? WHERE uuid=?', [data]) else: - cur.execute('INSERT INTO UPerson VALUES (?, ?, ?)', [uuid, name, age]) + cur.execute('INSERT INTO UPerson VALUES (?, ?)', [uuid, data]) cur.execute('COMMIT') + def get_UPerson_count(self) -> int: + return self._cur.execute('SELECT COUNT(*) FROM UPerson').fetchone()[0] + + def get_all_UPerson(self) -> List[UPerson]: + return [ self._UPerson_from_db_row(db_row) for db_row in self._cur.execute('SELECT * FROM UPerson').fetchall() ] + def iter_UPerson(self) -> Generator[UPerson, None, None]: """ iterator of all UPerson's """ - for uuid, name, age in self._cur.execute('SELECT * FROM UPerson').fetchall(): - uperson = UPerson() - uperson.set_uuid(uuid) - uperson.set_name(name) - uperson.set_age(age) - yield uperson + for db_row in self._cur.execute('SELECT * FROM UPerson').fetchall(): + yield self._UPerson_from_db_row(db_row) def delete_all_UPerson(self): """ @@ -211,7 +223,7 @@ class Faceset: """ add or update UImage in DB - uimage UImage object + uimage UImage or list format('png') webp ( does not support lossless on 100 quality ! ) png ( lossless ) @@ -229,13 +241,8 @@ class Faceset: if not isinstance(uimage_or_list, Iterable): uimage_or_list = [uimage_or_list] - cur = self._cur - cur.execute('BEGIN IMMEDIATE') + uimage_datas = [] for uimage in uimage_or_list: - # TODO optimize move encoding to out of transaction - img = uimage.get_image() - uuid = uimage.get_uuid() - if format == 'webp': imencode_args = [int(cv2.IMWRITE_WEBP_QUALITY), quality] elif format == 'jpg': @@ -244,15 +251,19 @@ class Faceset: imencode_args = [int(cv2.IMWRITE_JPEG2000_COMPRESSION_X1000), quality*10] else: imencode_args = [] - - ret, data_bytes = cv2.imencode( f'.{format}', img, imencode_args) + ret, data_bytes = cv2.imencode( f'.{format}', uimage.get_image(), imencode_args) if not ret: raise Exception(f'Unable to encode image format {format}') + uimage_datas.append(data_bytes.data) + cur = self._cur + cur.execute('BEGIN IMMEDIATE') + for uimage, data in zip(uimage_or_list, uimage_datas): + uuid = uimage.get_uuid() if cur.execute('SELECT COUNT(*) from UImage where uuid=?', [uuid] ).fetchone()[0] != 0: - cur.execute('UPDATE UImage SET name=?, format=?, data=? WHERE uuid=?', [uimage.get_name(), format, data_bytes.data, uuid]) + cur.execute('UPDATE UImage SET name=?, format=?, data=? WHERE uuid=?', [uimage.get_name(), format, data, uuid]) else: - cur.execute('INSERT INTO UImage VALUES (?, ?, ?, ?)', [uuid, uimage.get_name(), format, data_bytes.data]) + cur.execute('INSERT INTO UImage VALUES (?, ?, ?, ?)', [uuid, uimage.get_name(), format, data]) cur.execute('COMMIT') def get_UImage_count(self) -> int: return self._cur.execute('SELECT COUNT(*) FROM UImage').fetchone()[0] @@ -261,7 +272,7 @@ class Faceset: """ if uuid is None: return None - + db_row = self._cur.execute('SELECT * FROM UImage where uuid=?', [uuid]).fetchone() if db_row is None: return None diff --git a/xlib/face/IState.py b/xlib/face/IState.py new file mode 100644 index 0000000..ec0f25b --- /dev/null +++ b/xlib/face/IState.py @@ -0,0 +1,68 @@ +from typing import Any, Union + +import numpy as np + + +class IState: + """ + """ + def __getstate__(self): + return self.__dict__.copy() + + def __setstate__(self, d): + self.__init__() + self.__dict__.update(d) + + @staticmethod + def _dump_IState_obj(obj : Union[Any, None]) -> Union[Any, None]: + """ + """ + if obj is None: + return None + return obj.dump_state() + + @staticmethod + def _dump_np_array(n : Union[np.ndarray, None] ) -> Union[Any, None]: + if n is None: + return None + return ( n.data.tobytes(), n.dtype, n.shape ) + + @staticmethod + def _dump_enum(enum_obj : Union[Any, None]) -> Union[Any, None]: + if enum_obj is None: + return None + return enum_obj.value + + @staticmethod + def _restore_IState_obj(cls_, state : Union[Any, None]) -> Union[np.ndarray, None]: + if state is None: + return None + + obj = cls_() + obj.restore_state(state) + return obj + + @staticmethod + def _restore_np_array(state : Union[Any, None]) -> Union[np.ndarray, None]: + if state is None: + return None + return np.frombuffer(state[0], dtype=state[1]).reshape(state[2]) + + @staticmethod + def _restore_enum(enum_cls, state : Union[Any, None]) -> Union[Any, None]: + if state is None: + return None + + return enum_cls(state) + + def restore_state(self, state : dict): + """ + """ + raise NotImplementedError() + + def dump_state(self) -> dict: + """ + returns import-independent state of class in dict + """ + raise NotImplementedError() + diff --git a/xlib/face/UFaceMark.py b/xlib/face/UFaceMark.py index 97514d3..3c0c3ff 100644 --- a/xlib/face/UFaceMark.py +++ b/xlib/face/UFaceMark.py @@ -1,41 +1,53 @@ import uuid -from typing import List, Tuple, Union +from typing import List, Union -from ..math import Affine2DMat from .ELandmarks2D import ELandmarks2D from .EMaskType import EMaskType from .FLandmarks2D import FLandmarks2D from .FPose import FPose from .FRect import FRect +from .IState import IState -class UFaceMark: - def __init__(self, _from_pickled=False): +class UFaceMark(IState): + def __init__(self): """ Describes single face in the image. """ - self._uuid : Union[bytes, None] = uuid.uuid4().bytes_le if not _from_pickled else None + self._uuid : Union[bytes, None] = None self._UImage_uuid : Union[bytes, None] = None self._UPerson_uuid : Union[bytes, None] = None self._FRect : Union[FRect, None] = None self._FLandmarks2D_list : List[FLandmarks2D] = [] self._FPose : Union[FPose, None] = None - self._mask_info_list : List = [] - - def __getstate__(self): - return self.__dict__.copy() + #self._mask_info_list : List = [] - def __setstate__(self, d): - self.__init__(_from_pickled=True) - self.__dict__.update(d) - - def __str__(self): - s = "Masks: " - return f"UFaceMark UUID:[...{self._uuid[-4:].hex()}]" - def __repr__(self): return self.__str__() - - def get_uuid(self) -> Union[bytes, None]: return self._uuid + def __str__(self): + return f"UFaceMark UUID:[...{self.get_uuid()[-4:].hex()}]" + + def restore_state(self, state : dict): + self._uuid = state.get('_uuid', None) + self._UImage_uuid = state.get('_UImage_uuid', None) + self._UPerson_uuid = state.get('_UPerson_uuid', None) + self._FRect = IState._restore_IState_obj(FRect, state.get('_FRect', None)) + self._FLandmarks2D_list = [ IState._restore_IState_obj(FLandmarks2D, lmrks_state) for lmrks_state in state['_FLandmarks2D_list'] ] + self._FPose = IState._restore_IState_obj(FPose, state.get('_FPose', None)) + + def dump_state(self) -> dict: + return {'_uuid' : self._uuid, + '_UImage_uuid' : self._UImage_uuid, + '_UPerson_uuid' : self._UPerson_uuid, + '_FRect' : IState._dump_IState_obj(self._FRect), + '_FLandmarks2D_list': tuple( IState._dump_IState_obj(fl) for fl in self._FLandmarks2D_list), + '_FPose' : IState._dump_IState_obj(self._FPose), + } + + def get_uuid(self) -> Union[bytes, None]: + if self._uuid is None: + self._uuid = uuid.uuid4().bytes_le + return self._uuid + def set_uuid(self, uuid : Union[bytes, None]): if uuid is not None and not isinstance(uuid, bytes): raise ValueError(f'uuid must be an instance of bytes or None') @@ -85,15 +97,15 @@ class UFaceMark: raise ValueError('face_pose must be an instance of FPose') self._FPose = face_pose - def get_mask_info_list(self) -> List[Tuple[EMaskType, bytes, Affine2DMat]]: - return self._mask_info_list - - def add_mask_info(self, mask_type : EMaskType, UImage_uuid : bytes, mask_to_mark_uni_mat : Affine2DMat): - if not isinstance(mask_type, EMaskType): - raise ValueError('mask_type must be an instance of EMaskType') - if not isinstance(UImage_uuid, bytes): - raise ValueError('UImage_uuid must be an instance of bytes') - if not isinstance(mask_to_mark_uni_mat, Affine2DMat): - raise ValueError('mask_to_mark_uni_mat must be an instance of Affine2DMat') - - self._mask_info_list.append( (mask_type, UImage_uuid, mask_to_mark_uni_mat) ) + # def get_mask_info_list(self) -> List[Tuple[EMaskType, bytes, Affine2DMat]]: + # return self._mask_info_list + + # def add_mask_info(self, mask_type : EMaskType, UImage_uuid : bytes, mask_to_mark_uni_mat : Affine2DMat): + # if not isinstance(mask_type, EMaskType): + # raise ValueError('mask_type must be an instance of EMaskType') + # if not isinstance(UImage_uuid, bytes): + # raise ValueError('UImage_uuid must be an instance of bytes') + # if not isinstance(mask_to_mark_uni_mat, Affine2DMat): + # raise ValueError('mask_to_mark_uni_mat must be an instance of Affine2DMat') + + # self._mask_info_list.append( (mask_type, UImage_uuid, mask_to_mark_uni_mat) ) diff --git a/xlib/face/UImage.py b/xlib/face/UImage.py index 76edb75..c22ba8c 100644 --- a/xlib/face/UImage.py +++ b/xlib/face/UImage.py @@ -3,27 +3,27 @@ from typing import Union import numpy as np -class UImage: +from .IState import IState + + +class UImage(IState): def __init__(self): """ represents uncompressed image uint8 HWC ( 1/3/4 channels ) """ - self._uuid : Union[bytes, None] = uuid.uuid4().bytes_le + self._uuid : Union[bytes, None] = None self._name : Union[str, None] = None self._image : np.ndarray = None - def __getstate__(self): - return self.__dict__.copy() - - def __setstate__(self, d): - self.__init__() - self.__dict__.update(d) - - def __str__(self): return f"UImage UUID:[...{self._uuid[-4:].hex()}] name:[{self._name}] image:[{ (self._image.shape, self._image.dtype) if self._image is not None else None}]" + def __str__(self): return f"UImage UUID:[...{self.get_uuid()[-4:].hex()}] name:[{self._name}] image:[{ (self._image.shape, self._image.dtype) if self._image is not None else None}]" def __repr__(self): return self.__str__() - def get_uuid(self) -> Union[bytes, None]: return self._uuid + def get_uuid(self) -> Union[bytes, None]: + if self._uuid is None: + self._uuid = uuid.uuid4().bytes_le + return self._uuid + def set_uuid(self, uuid : Union[bytes, None]): if uuid is not None and not isinstance(uuid, bytes): raise ValueError(f'uuid must be an instance of bytes or None') @@ -47,7 +47,7 @@ class UImage: if image is not None: if image.ndim == 2: image = image[...,None] - + if image.ndim != 3: raise ValueError('image must have ndim == 3') _,_,C = image.shape diff --git a/xlib/face/UPerson.py b/xlib/face/UPerson.py index 37bbf25..ac6280d 100644 --- a/xlib/face/UPerson.py +++ b/xlib/face/UPerson.py @@ -1,26 +1,36 @@ import uuid from typing import Union +from .IState import IState -class UPerson: - def __init__(self, _from_pickled=False): + +class UPerson(IState): + def __init__(self): """ """ - self._uuid : Union[bytes, None] = uuid.uuid4().bytes_le if not _from_pickled else None + self._uuid : Union[bytes, None] = None self._name : Union[str, None] = None self._age : Union[int, None] = None - def __getstate__(self): - return self.__dict__.copy() - - def __setstate__(self, d): - self.__init__(_from_pickled=True) - self.__dict__.update(d) - def __str__(self): return f"UPerson UUID:[...{self._uuid[-4:].hex()}] name:[{self._name}] age:[{self._age}]" def __repr__(self): return self.__str__() - def get_uuid(self) -> Union[bytes, None]: return self._uuid + def restore_state(self, state : dict): + self._uuid = state.get('_uuid', None) + self._name = state.get('_name', None) + self._age = state.get('_age', None) + + def dump_state(self) -> dict: + return {'_uuid' : self._uuid, + '_name' : self._name, + '_age' : self._age, + } + + def get_uuid(self) -> Union[bytes, None]: + if self._uuid is None: + self._uuid = uuid.uuid4().bytes_le + return self._uuid + def set_uuid(self, uuid : Union[bytes, None]): if uuid is not None and not isinstance(uuid, bytes): raise ValueError(f'uuid must be an instance of bytes or None') @@ -36,4 +46,4 @@ class UPerson: def set_age(self, age : Union[int, None]): if age is not None and not isinstance(age, int): raise ValueError(f'age must be an instance of int or None') - self._age = age \ No newline at end of file + self._age = age