improve documentation and command line usage
[face-privacy-filter.git] / face_privacy_filter / filter_image.py
1 #! python
2 # -*- coding: utf-8 -*-
3 # ===============LICENSE_START=======================================================
4 # Acumos Apache-2.0
5 # ===================================================================================
6 # Copyright (C) 2017-2018 AT&T Intellectual Property & Tech Mahindra. All rights reserved.
7 # ===================================================================================
8 # This Acumos software file is distributed by AT&T and Tech Mahindra
9 # under the Apache License, Version 2.0 (the "License");
10 # you may not use this file except in compliance with the License.
11 # You may obtain a copy of the License at
12 #
13 # http://www.apache.org/licenses/LICENSE-2.0
14 #
15 # This file is distributed on an "AS IS" BASIS,
16 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 # See the License for the specific language governing permissions and
18 # limitations under the License.
19 # ===============LICENSE_END=========================================================
20 """
21 Wrapper for face privacy transform task
22 """
23
24 import os.path
25 import sys
26
27 import numpy as np
28 import pandas as pd
29
30
31 def model_create_pipeline(transformer, funcName, inputIsSet, outputIsSet):
32     from acumos.session import Requirements
33     from acumos.modeling import Model, List, create_namedtuple
34     from face_privacy_filter._version import MODEL_NAME, __version__ as VERSION
35     import sklearn
36     import cv2
37     from os import path
38
39     # derive the input type from the transformer
40     input_type, type_name = transformer._type_in  # it looked like this [('test', int), ('tag', str)]
41     type_in = create_namedtuple(type_name, input_type)
42     input_set = type_in
43     name_multiple_in = type_name
44     if inputIsSet:
45         name_multiple_in = type_name + "s"
46         input_set = create_namedtuple(type_name + "Set", [(name_multiple_in, List[type_in])])
47
48     # derive the output type from the transformer
49     output_type, type_name = transformer._type_out
50     type_out = create_namedtuple(type_name, output_type)
51     output_set = type_out
52     if outputIsSet:
53         name_multiple_out = type_name + "s"
54         output_set = create_namedtuple(type_name + "Set", [(name_multiple_out, List[type_out])])
55
56     def transform(val_wrapped: input_set) -> output_set:
57         '''Transform from image or detection and return score or image'''
58         # print("-===== input -===== ")
59         # print(input_set)
60         if inputIsSet:
61             df = pd.DataFrame(getattr(val_wrapped, name_multiple_in), columns=type_in._fields)
62         else:
63             df = pd.DataFrame([val_wrapped], columns=type_in._fields)
64         # print("-===== df -===== ")
65         # print(df)
66         result_df = transformer.predict(df)
67         # print("-===== out df -===== ")
68         # print(result_df)
69         # print(result_parts)
70         result_parts = result_df.to_dict('split')
71         print("[{} - {}:{}]: Input {} row(s) ({}), output {} row(s) ({}))".format(
72               "classify", MODEL_NAME, VERSION, len(df), type_in, len(result_df), output_set))
73         output_obj = []
74         if len(df):
75             if outputIsSet:
76                 output_obj = output_set([type_out(*r) for r in result_parts['data']])
77             else:
78                 output_obj = output_set(*result_parts['data'][0])
79         # print("-===== out list -===== ")
80         # print(output_obj)
81         return output_obj
82
83     # compute path of this package to add it as a dependency
84     package_path = path.dirname(path.realpath(__file__))
85     objModelDeclare = {}
86     objModelDeclare[funcName] = transform
87     # add the model dependency manually because of the way we constructed the package;
88     # the opencv-python/cv2 dependency is not picked up automagically
89     return Model(**objModelDeclare), Requirements(packages=[package_path], reqs=[pd, np, sklearn, 'opencv-python'],
90                                                   req_map={cv2: 'opencv-python'})
91
92
93 def main(config={}):
94     from face_privacy_filter.transform_detect import FaceDetectTransform
95     from face_privacy_filter.transform_region import RegionTransform
96     from face_privacy_filter._version import MODEL_NAME
97     import argparse
98     parser = argparse.ArgumentParser()
99     submain = parser.add_argument_group('main execution and evaluation functionality')
100     submain.add_argument('-p', '--predict_path', type=str, default='', help="save detections from model (model must be provided via 'dump_model')")
101     submain.add_argument('-i', '--input', type=str, default='', help='absolute path to input data (image or csv, only during prediction / dump)')
102     submain.add_argument('-c', '--csv_input', dest='csv_input', action='store_true', default=False, help='input as CSV format not an image')
103     submain.add_argument('-f', '--function', type=str, default='detect', help='which type of model to generate', choices=['detect', 'pixelate'])
104     submain.add_argument('-s', '--suppress_image', dest='suppress_image', action='store_true', default=False, help='do not create an extra row for a returned image')
105     subopts = parser.add_argument_group('model creation and configuration options')
106     subopts.add_argument('-a', '--push_address', help='server address to push the model (e.g. http://localhost:8887/v2/models)', default=os.getenv('ACUMOS_PUSH', ""))
107     subopts.add_argument('-A', '--auth_address', help='server address for login and push of the model (e.g. http://localhost:8887/v2/auth)', default=os.getenv('ACUMOS_AUTH', ""))
108     subopts.add_argument('-d', '--dump_model', help='dump model to a pickle directory for local running', default='')
109     config.update(vars(parser.parse_args()))     # pargs, unparsed = parser.parse_known_args()
110
111     if not config['predict_path']:
112         print("Attempting to create new model for dump or push...")
113     elif not os.path.exists(config['input']):
114         print("Prediction requested but target input '{:}' was not found, please check input arguments.".format(config['input']))
115         sys.exit(-1)
116
117     # refactor the raw samples from upstream image classifier
118     if config['function'] == "detect":
119         transform = FaceDetectTransform(include_image=not config['suppress_image'])
120         pipeline, reqs = model_create_pipeline(transform, config['function'], False, True)
121     elif config['function'] == "pixelate":
122         transform = RegionTransform()
123         pipeline, reqs = model_create_pipeline(transform, config['function'], True, False)
124     else:
125         print("Error: Functional mode '{:}' unknown, aborting create".format(config['function']))
126     print(pipeline)
127     print(getattr(pipeline, config['function']))
128
129     # formulate the pipeline to be used
130     model_name = MODEL_NAME + "_" + config['function']
131     if config['push_address'] and config['auth_address']:
132         from acumos.session import AcumosSession
133         print("Pushing new model to '{:}'...".format(config['push_address']))
134         session = AcumosSession(push_api=config['push_address'], auth_api=config['auth_address'])
135         session.push(pipeline, model_name, reqs)  # pushes model directly to servers
136
137     if config['dump_model']:
138         from acumos.session import AcumosSession
139         from os import makedirs
140         if not os.path.exists(config['dump_model']):
141             makedirs(config['dump_model'])
142         print("Dumping new model to '{:}'...".format(config['dump_model']))
143         session = AcumosSession()
144         session.dump(pipeline, model_name, config['dump_model'], reqs)  # creates model subdirectory
145
146     if config['predict_path']:
147         print("Using newly created model for local prediction...")
148         if not config['csv_input']:
149             inputDf = FaceDetectTransform.generate_in_df(config['input'])
150         else:
151             inputDf = pd.read_csv(config['input'], converters={FaceDetectTransform.COL_IMAGE_DATA: FaceDetectTransform.read_byte_arrays})
152
153         func_action = getattr(pipeline, config['function'])  # simplify to just use loaded model 6/1
154         pred_raw = func_action.wrapped(inputDf)
155         transform_out = func_action.from_wrapped(pred_raw).as_wrapped()
156         dfPred = pd.DataFrame(list(zip(*transform_out)), columns=transform_out._fields)
157
158         if not config['csv_input']:
159             dfPred = FaceDetectTransform.suppress_image(dfPred)
160
161         if config['predict_path']:
162             print("Writing prediction to file '{:}'...".format(config['predict_path']))
163             if not config['csv_input']:
164                 dfPred.to_csv(config['predict_path'], sep=",", index=False)
165             else:
166                 FaceDetectTransform.generate_out_image(dfPred, config['predict_path'])
167
168         if dfPred is not None:
169             print("Predictions:\n{:}".format(dfPred))
170
171
172 if __name__ == '__main__':
173     # patch the path to include this object
174     pathRoot = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
175     if pathRoot not in sys.path:
176         sys.path.append(pathRoot)
177     main()