Skip to content

Commit

Permalink
fromatting changes and bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
dhaniram-kshirsagar committed Dec 4, 2019
1 parent 64880ec commit 36f4a64
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ protected void encode(ChannelHandlerContext ctx, BaseModelRequest msg, ByteBuf o
batchSize = 1;
}
out.writeInt(batchSize);

String handler = request.getHandler();
if (handler != null){
buf = handler.getBytes(StandardCharsets.UTF_8);
if (handler != null) {
buf = handler.getBytes(StandardCharsets.UTF_8);
}

out.writeInt(buf.length);
out.writeBytes(buf);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,20 @@ private String[] getEnvString(String cwd, String modelPath, String handler) {
ArrayList<String> envList = new ArrayList<>();
Pattern blackList = configManager.getBlacklistPattern();
StringBuilder pythonPath = new StringBuilder();

if (handler != null && handler.contains(":")) {
String handlerFile = handler;
String handlerFile = handler;
handlerFile = handler.split(":")[0];
if (handlerFile.contains("/")) {
handlerFile = handlerFile.substring(0, handlerFile.lastIndexOf('/'));
}

pythonPath.append(handlerFile).append(File.pathSeparatorChar);
}


HashMap<String, String> environment = new HashMap<>(System.getenv());
environment.putAll(configManager.getBackendConfiguration());

if (System.getenv("PYTHONPATH") != null) {
pythonPath.append(System.getenv("PYTHONPATH")).append(File.pathSeparatorChar);
}
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def run(self):
if __name__ == '__main__':
version = detect_model_server_version()

requirements = ['Pillow', 'psutil', 'future', 'model-archiver', 'torch']
requirements = ['Pillow', 'psutil', 'future', 'torch']

setup(
name='torchserve',
Expand Down
33 changes: 18 additions & 15 deletions ts/torch_hanlder/base_handler.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@


"""
Base default handler to load torchscript or eager mode [state_dict] models
Also, provides handle method per torch serve custom model specification
"""
import abc
import logging
import os
import json

import torch

Expand All @@ -14,9 +17,10 @@
class BaseHandler(abc.ABC):
def __init__(self):
self.model = None
self.mapping = None
self.device = "cpu"
self.initialized = False

@abc.abstractmethod
def initialize(self, ctx):
"""First try to load torchscript else load eager mode state_dict based model"""

Expand All @@ -26,7 +30,7 @@ def initialize(self, ctx):
# Read model serialize/pt file
model_pt_path = os.path.join(model_dir, "model.pt")
if not os.path.isfile(model_pt_path):
raise RuntimeError("Missing model.pt file")
raise RuntimeError("Missing the model.pt file")

try:
logger.debug('Loading torchscript model')
Expand All @@ -35,7 +39,7 @@ def initialize(self, ctx):
# Read model definition file
model_def_path = os.path.join(model_dir, "model.py")
if not os.path.isfile(model_def_path):
raise RuntimeError("Missing model.py file")
raise RuntimeError("Missing the model.py file")

import importlib
from ..utils import list_classes_from_module
Expand All @@ -53,6 +57,17 @@ def initialize(self, ctx):

logger.debug('Model file {0} loaded successfully'.format(model_pt_path))

# Read the mapping file, index to object name
mapping_file_path = os.path.join(model_dir, "index_to_name.json")

if os.path.isfile(mapping_file_path):
with open(mapping_file_path) as f:
self.mapping = json.load(f)
else:
logger.warning('Missing the index_to_name.json file. Inference output will not include class name.')

self.initialized = True

@abc.abstractmethod
def preprocess(self, data):
pass
Expand All @@ -64,15 +79,3 @@ def inference(self, data):
@abc.abstractmethod
def postprocess(self, data):
pass

def handle(self, data, context):
if not self.initialized:
self.initialize(context)

if data is None:
return None

data = self.preprocess(data)
data = self.inference(data)
data = self.postprocess(data)
return data
107 changes: 107 additions & 0 deletions ts/torch_hanlder/encoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the 'License'). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the 'license' file accompanying this file. This file is
# distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from __future__ import absolute_import

import json
from typing import Iterable # noqa ignore=F401 imported but unused

import numpy as np
from six import BytesIO, StringIO

JSON = 'application/json'
CSV = "text/csv"
OCTET_STREAM = "application/octet-stream"
ANY = '*/*'
NPY = 'application/x-npy'
UTF8_TYPES = [JSON, CSV]


def _array_to_json(array_like): # type: (np.array or Iterable or int or float) -> str
"""Convert an array-like object to JSON.
To understand better what an array-like object is see:
https://docs.scipy.org/doc/numpy/user/basics.creation.html#converting-python-array-like-objects-to-numpy-arrays
Args:
array_like (np.array or Iterable or int or float): array-like object to be converted to JSON.
Returns:
(str): object serialized to JSON
"""

def default(_array_like):
if hasattr(_array_like, 'tolist'):
return _array_like.tolist()
return json.JSONEncoder().default(_array_like)

return json.dumps(array_like, default=default)


def _array_to_npy(array_like): # type: (np.array or Iterable or int or float) -> object
"""Convert an array-like object to the NPY format.
To understand better what an array-like object is see:
https://docs.scipy.org/doc/numpy/user/basics.creation.html#converting-python-array-like-objects-to-numpy-arrays
Args:
array_like (np.array or Iterable or int or float): array-like object to be converted to NPY.
Returns:
(obj): NPY array.
"""
buffer = BytesIO()
np.save(buffer, array_like)
return buffer.getvalue()


def _array_to_csv(array_like): # type: (np.array or Iterable or int or float) -> str
"""Convert an array-like object to CSV.
To understand better what an array-like object is see:
https://docs.scipy.org/doc/numpy/user/basics.creation.html#converting-python-array-like-objects-to-numpy-arrays
Args:
array_like (np.array or Iterable or int or float): array-like object to be converted to CSV.
Returns:
(str): object serialized to CSV
"""
stream = StringIO()
np.savetxt(stream, array_like, delimiter=',', fmt='%s')
return stream.getvalue()


_encoder_map = {NPY: _array_to_npy,
CSV: _array_to_csv,
JSON: _array_to_json}


def encode(array_like, content_type):
# type: (np.array or Iterable or int or float, str) -> np.array
"""Encode an array-like object in a specific content_type to a numpy array.
To understand better what an array-like object is see:
https://docs.scipy.org/doc/numpy/user/basics.creation.html#converting-python-array-like-objects-to-numpy-arrays
Args:
array_like (np.array or Iterable or int or float): to be converted to numpy.
content_type (str): content type to be used.
Returns:
(np.array): object converted as numpy array.
"""
try:
encoder = _encoder_map[content_type]
return encoder(array_like)
except KeyError:
raise Exception(content_type)
64 changes: 33 additions & 31 deletions ts/torch_hanlder/image_classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,7 @@ class ImageClassifier(BaseHandler):
"""

def __init__(self):
self.checkpoint_file_path = None
self.mapping = None
self.initialized = False

def initialize(self, context):
"""
Load the model and mapping file to perform infernece.
"""

# Initialize model
super(ImageClassifier, self).initialize(context)

properties = context.system_properties
model_dir = properties.get("model_dir")

# Read the mapping file, index to object name
mapping_file_path = os.path.join(model_dir, "index_to_name.json")
if not os.path.isfile(mapping_file_path):
raise RuntimeError("Missing the mapping file")
with open(mapping_file_path) as f:
self.mapping = json.load(f)

self.initialized = True

super(ImageClassifier, self).__init__()

def preprocess(self, data):
"""
Expand All @@ -69,25 +46,50 @@ def inference(self, img, topk=5):
'''
# Convert 2D image to 1D vector
img = np.expand_dims(img, 0)

img = torch.from_numpy(img)

self.model.eval()
inputs = Variable(img).to(self.device)
logits = self.model.forward(inputs)
outputs = self.model.forward(inputs)

ps = F.softmax(logits, dim=1)
ps = F.softmax(outputs, dim=1)
topk = ps.cpu().topk(topk)

probs, classes = (e.data.numpy().squeeze().tolist() for e in topk)

results = []
for i in range(len(probs)):
tmp = dict()
tmp[self.mapping[str(classes[i])][1]] = probs[i]
# tmp[self.mapping[str(classes[i])]] = probs[i]
results.append(tmp)
if self.mapping:
tmp = dict()
if isinstance(self.mapping, dict) and isinstance(list(self.mapping.values())[0], list):
tmp[self.mapping[str(classes[i])][1]] = probs[i]
elif isinstance(self.mapping, dict) and isinstance(list(self.mapping.values())[0], str):
tmp[self.mapping[str(classes[i])]] = probs[i]
else:
raise Exception('index_to_name mapping should be in "class":"label" json format')

results.append(tmp)
else:
results.append({str(classes[i]), probs[i]})

return [results]

def postprocess(self, inference_output):
return inference_output


_service = ImageClassifier()


def handle(data, context):
if not _service.initialized:
_service.initialize(context)

if data is None:
return None

data = _service.preprocess(data)
data = _service.inference(data)
data = _service.postprocess(data)

return data

0 comments on commit 36f4a64

Please sign in to comment.