i/o for single image, js update (CORS+generalize)
[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     parser.add_argument('-p', '--predict_path', type=str, default='', help="save detections from model (model must be provided via 'dump_model')")
100     parser.add_argument('-i', '--input', type=str, default='', help='absolute path to input data (image or csv, only during prediction / dump)')
101     parser.add_argument('-c', '--csv_input', dest='csv_input', action='store_true', default=False, help='input as CSV format not an image')
102     parser.add_argument('-s', '--suppress_image', dest='suppress_image', action='store_true', default=False, help='do not create an extra row for a returned image')
103     parser.add_argument('-f', '--function', type=str, default='detect', help='which type of model to generate', choices=['detect', 'pixelate'])
104     parser.add_argument('-a', '--push_address', help='server address to push the model (e.g. http://localhost:8887/v2/models)', default=os.getenv('ACUMOS_PUSH', ""))
105     parser.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', ""))
106     parser.add_argument('-d', '--dump_model', help='dump model to a pickle directory for local running', default='')
107     config.update(vars(parser.parse_args()))     # pargs, unparsed = parser.parse_known_args()
108
109     if not config['predict_path']:
110         print("Attempting to create new model for dump or push...")
111     elif not os.path.exists(config['input']):
112         print("Prediction requested but target input '{:}' was not found, please check input arguments.".format(config['input']))
113         sys.exit(-1)
114
115     # refactor the raw samples from upstream image classifier
116     if config['function'] == "detect":
117         transform = FaceDetectTransform(include_image=not config['suppress_image'])
118         pipeline, reqs = model_create_pipeline(transform, config['function'], False, True)
119     elif config['function'] == "pixelate":
120         transform = RegionTransform()
121         pipeline, reqs = model_create_pipeline(transform, config['function'], True, False)
122     else:
123         print("Error: Functional mode '{:}' unknown, aborting create".format(config['function']))
124     print(pipeline)
125     print(getattr(pipeline, config['function']))
126
127     # formulate the pipeline to be used
128     model_name = MODEL_NAME + "_" + config['function']
129     if config['push_address']:
130         from acumos.session import AcumosSession
131         print("Pushing new model to '{:}'...".format(config['push_address']))
132         session = AcumosSession(push_api=config['push_address'], auth_api=config['auth_address'])
133         session.push(pipeline, model_name, reqs)  # pushes model directly to servers
134
135     if config['dump_model']:
136         from acumos.session import AcumosSession
137         from os import makedirs
138         if not os.path.exists(config['dump_model']):
139             makedirs(config['dump_model'])
140         print("Dumping new model to '{:}'...".format(config['dump_model']))
141         session = AcumosSession()
142         session.dump(pipeline, model_name, config['dump_model'], reqs)  # creates model subdirectory
143
144     if config['predict_path']:
145         print("Using newly created model for local prediction...")
146         if not config['csv_input']:
147             inputDf = FaceDetectTransform.generate_in_df(config['input'])
148         else:
149             inputDf = pd.read_csv(config['input'], converters={FaceDetectTransform.COL_IMAGE_DATA: FaceDetectTransform.read_byte_arrays})
150
151         func_action = getattr(pipeline, config['function'])  # simplify to just use loaded model 6/1
152         pred_raw = func_action.wrapped(inputDf)
153         transform_out = func_action.from_wrapped(pred_raw).as_wrapped()
154         dfPred = pd.DataFrame(list(zip(*transform_out)), columns=transform_out._fields)
155
156         if not config['csv_input']:
157             dfPred = FaceDetectTransform.suppress_image(dfPred)
158
159         if config['predict_path']:
160             print("Writing prediction to file '{:}'...".format(config['predict_path']))
161             if not config['csv_input']:
162                 dfPred.to_csv(config['predict_path'], sep=",", index=False)
163             else:
164                 FaceDetectTransform.generate_out_image(dfPred, config['predict_path'])
165
166         if dfPred is not None:
167             print("Predictions:\n{:}".format(dfPred))
168
169
170 if __name__ == '__main__':
171     # patch the path to include this object
172     pathRoot = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
173     if pathRoot not in sys.path:
174         sys.path.append(pathRoot)
175     main()