From 4a8b5be0d18f13b0637447fe0f7d18204e2d7434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Tue, 2 Nov 2021 05:21:40 +0000 Subject: [PATCH 01/16] Initial version of DarkNet for SM Toolkit --- .gitignore | 6 ++ Vagrantfile | 91 +++++++++++++++++++ environment.yml | 11 ++- setup.py | 11 ++- src/darknet/py/__init__.py | 3 +- src/darknet/sagemaker/__init__.py | 4 + src/darknet/sagemaker/__main__.py | 29 ++++++ src/darknet/sagemaker/classifier/__init__.py | 3 + src/darknet/sagemaker/classifier/__main__.py | 28 ++++++ .../classifier/default_inference_handler.py | 38 ++++++++ .../sagemaker/classifier/handler_service.py | 19 ++++ .../sagemaker/default_inference_handler.py | 43 +++++++++ src/darknet/sagemaker/detector/__init__.py | 3 + src/darknet/sagemaker/detector/__main__.py | 27 ++++++ .../detector/default_inference_handler.py | 42 +++++++++ .../sagemaker/detector/handler_service.py | 19 ++++ 16 files changed, 373 insertions(+), 4 deletions(-) create mode 100644 Vagrantfile create mode 100644 src/darknet/sagemaker/__init__.py create mode 100644 src/darknet/sagemaker/__main__.py create mode 100644 src/darknet/sagemaker/classifier/__init__.py create mode 100644 src/darknet/sagemaker/classifier/__main__.py create mode 100644 src/darknet/sagemaker/classifier/default_inference_handler.py create mode 100644 src/darknet/sagemaker/classifier/handler_service.py create mode 100644 src/darknet/sagemaker/default_inference_handler.py create mode 100644 src/darknet/sagemaker/detector/__init__.py create mode 100644 src/darknet/sagemaker/detector/__main__.py create mode 100644 src/darknet/sagemaker/detector/default_inference_handler.py create mode 100644 src/darknet/sagemaker/detector/handler_service.py diff --git a/.gitignore b/.gitignore index df17089..c1fbd07 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# MacOS +.DS_Store + +# Vagrant +.vagrant + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..6070543 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,91 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# All Vagrant configuration is done below. The "2" in Vagrant.configure +# configures the configuration version (we support older styles for +# backwards compatibility). Please don't change it unless you know what +# you're doing. +Vagrant.configure("2") do |config| + # The most common configuration options are documented and commented below. + # For a complete reference, please see the online documentation at + # https://docs.vagrantup.com. + + # Every Vagrant development environment requires a box. You can search for + # boxes at https://vagrantcloud.com/search. + config.vm.box = "hashicorp/bionic64" + + # Disable automatic box update checking. If you disable this, then + # boxes will only be checked for updates when the user runs + # `vagrant box outdated`. This is not recommended. + # config.vm.box_check_update = false + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + # NOTE: This will enable public access to the opened port + config.vm.network "forwarded_port", guest: 8888, host: 8888 + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine and only allow access + # via 127.0.0.1 to disable public access + # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + # config.vm.network "private_network", ip: "192.168.33.10" + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + # config.vm.network "public_network" + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + # config.vm.provider "virtualbox" do |vb| + # # Display the VirtualBox GUI when booting the machine + # vb.gui = true + # + # # Customize the amount of memory on the VM: + # vb.memory = "1024" + # end + # + # View the documentation for the provider you are using for more + # information on available options. + + # Enable provisioning with a shell script. Additional provisioners such as + # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the + # documentation for more information about their specific syntax and use. + config.vm.provision "shell", name: "Install Mambaforge", privileged: false, reset: true, inline: <<-SHELL + MAMBA_FORGE_FILE=Mambaforge-$(uname)-$(uname -m).sh + if ! [ -d ~/mambaforge ]; then + if ! [ -f $MAMBA_FORGE_FILE ]; then + wget -q https://github.com/conda-forge/miniforge/releases/latest/download/$MAMBA_FORGE_FILE + fi + bash $MAMBA_FORGE_FILE -b -u + rm -f $MAMBA_FORGE_FILE + + mambaforge/bin/conda init --all + fi + SHELL + + config.vm.provision "shell", name: "Install OS Packages", inline: <<-SHELL + ### Add OpenJDK 8 + apt-get update + apt-get --yes install openjdk-8-jre-headless + SHELL + + config.vm.provision "shell", name: "Create Development Environment", privileged: false, inline: <<-SHELL + ### Create the DarkNet Environment + source mambaforge/etc/profile.d/conda.sh + mamba env update --name darknet-cpu -f /vagrant/environment.yml + echo 'conda activate darknet-cpu' >> ~/.bashrc + SHELL +end diff --git a/environment.yml b/environment.yml index cd34fd5..1176b58 100644 --- a/environment.yml +++ b/environment.yml @@ -1,4 +1,3 @@ - name: darknet.py-dev channels: - zeroae @@ -9,6 +8,8 @@ dependencies: - pip - pip: - -e . + - multi-model-server + - sagemaker-inference # Setup Requirements (setup.py:setup_requirements) - compilers @@ -21,7 +22,7 @@ dependencies: # Install Requirements (setup.py:requirements) - click >=7.0 - click-plugins - - darknet + - darknet-cpu - entrypoints - fsspec <=0.7.5 - numpy @@ -30,6 +31,12 @@ dependencies: # Zoo Optional Requirements - intake + # MMS Requirements + - enum-compat + - future + - retrying + - scipy + # Test Requirements (setup.py:test_requirements) - pytest >=3 - pytest-cov diff --git a/setup.py b/setup.py index 254725d..ddf9f67 100755 --- a/setup.py +++ b/setup.py @@ -47,6 +47,14 @@ # fmt: on ] +mms_requirements = [ + # fmt: off + "future", + "multi-model-server", + "retrying", + "sagemaker-inference", + # fmt: on +] zoo_requirements = [ # fmt: off "intake", @@ -130,7 +138,7 @@ long_description_content_type="text/x-rst", include_package_data=True, keywords="py darknet", - name="darknet-py", + name="darknet.py", package_dir={"": "src"}, packages=find_namespace_packages(where="./src"), setup_requires=setup_requirements, @@ -141,6 +149,7 @@ "test": test_requirements, "doc": doc_requirements, "zoo": zoo_requirements, + "mms": mms_requirements, # fmt: on }, url="https://github.com/zeroae/darknet.py", diff --git a/src/darknet/py/__init__.py b/src/darknet/py/__init__.py index a810fc5..80945b2 100644 --- a/src/darknet/py/__init__.py +++ b/src/darknet/py/__init__.py @@ -1,6 +1,7 @@ """Top-level package for DarkNet OpenSource Neural Networks in Python.""" from ._version import version as __version__ # noqa: F401 +from .network import Network from .classifier import Classifier, ImageClassifier from .detector import ImageDetector -__all__ = ["Classifier", "ImageClassifier", "ImageDetector"] +__all__ = ["Network", "Classifier", "ImageClassifier", "ImageDetector"] diff --git a/src/darknet/sagemaker/__init__.py b/src/darknet/sagemaker/__init__.py new file mode 100644 index 0000000..383b92b --- /dev/null +++ b/src/darknet/sagemaker/__init__.py @@ -0,0 +1,4 @@ +from .default_inference_handler import DefaultDarknetInferenceHandler, Network +from ..py.util import image_to_3darray + +__all__ = ["DefaultDarknetInferenceHandler", "Network", "image_to_3darray"] diff --git a/src/darknet/sagemaker/__main__.py b/src/darknet/sagemaker/__main__.py new file mode 100644 index 0000000..78a4a68 --- /dev/null +++ b/src/darknet/sagemaker/__main__.py @@ -0,0 +1,29 @@ +from retrying import retry +from subprocess import CalledProcessError +from sagemaker_inference import model_server + +from .classfier import handler_service as classifier_service +from .detector import handler_service as detector_service + + +def _retry_if_error(exception): + return isinstance(exception, CalledProcessError or OSError) + + +@retry(stop_max_delay=1000 * 50, + retry_on_exception=_retry_if_error) +def _start_mms(): + # by default the number of workers per model is 1, but we can configure it through the + # environment variable below if desired. + # os.environ['SAGEMAKER_MODEL_SERVER_WORKERS'] = '2' + # TODO: Start Classifier *or* Detector Service + model_server.start_model_server( + handler_service=detector_service.__name__ + ) + + +def main(): + _start_mms() + + +main() diff --git a/src/darknet/sagemaker/classifier/__init__.py b/src/darknet/sagemaker/classifier/__init__.py new file mode 100644 index 0000000..ddee0ce --- /dev/null +++ b/src/darknet/sagemaker/classifier/__init__.py @@ -0,0 +1,3 @@ +from .handler_service import HandlerService + +__all__ = ["HandlerService"] diff --git a/src/darknet/sagemaker/classifier/__main__.py b/src/darknet/sagemaker/classifier/__main__.py new file mode 100644 index 0000000..14dfa1a --- /dev/null +++ b/src/darknet/sagemaker/classifier/__main__.py @@ -0,0 +1,28 @@ +from retrying import retry +from subprocess import CalledProcessError +from sagemaker_inference import model_server + +from . import handler_service as classifier_service + + +def _retry_if_error(exception): + return isinstance(exception, CalledProcessError or OSError) + + +@retry(stop_max_delay=1000 * 50, + retry_on_exception=_retry_if_error) +def _start_mms(): + # by default the number of workers per model is 1, but we can configure it through the + # environment variable below if desired. + # os.environ['SAGEMAKER_MODEL_SERVER_WORKERS'] = '2' + # TODO: Start Classifier *or* Detector Service + model_server.start_model_server( + handler_service=classifier_service.__name__ + ) + + +def main(): + _start_mms() + + +main() diff --git a/src/darknet/sagemaker/classifier/default_inference_handler.py b/src/darknet/sagemaker/classifier/default_inference_handler.py new file mode 100644 index 0000000..0ff7fef --- /dev/null +++ b/src/darknet/sagemaker/classifier/default_inference_handler.py @@ -0,0 +1,38 @@ +import PIL.Image as Image + +from typing import Tuple, List +from sagemaker_inference import content_types, decoder, encoder, errors + +from .. import DefaultDarknetInferenceHandler, image_to_3darray + + +class DefaultDarknetClassifierInferenceHandler(DefaultDarknetInferenceHandler): + + def default_predict_fn(self, data, model: Tuple[Network, List[str]]): + """A default predict_fn for DarkNet. Calls a model on data deserialized in input_fn. + Args: + data: input data (PIL.Image) for prediction deserialized by input_fn + model: Darknet model loaded in memory by model_fn + + Returns: a prediction + """ + network, labels = model + if isinstance(data, Image.Image): + image, _ = image_to_3darray(data, network.shape) + probabilities = network.predict_image(image) + else: + probabilities = network.predict(data) + + return zip(labels, probabilities) + + def default_output_fn(self, prediction, accept): + """A default output_fn for PyTorch. Serializes predictions from predict_fn to JSON, CSV or NPY format. + + Args: + prediction: a prediction result from predict_fn + accept: type which the output data needs to be serialized + + Returns: output data serialized + """ + prediction = sorted(prediction, key=lambda x: x[1], reverse=True)[0:5] + return encoder.encode(list(prediction), accept) diff --git a/src/darknet/sagemaker/classifier/handler_service.py b/src/darknet/sagemaker/classifier/handler_service.py new file mode 100644 index 0000000..3d105f3 --- /dev/null +++ b/src/darknet/sagemaker/classifier/handler_service.py @@ -0,0 +1,19 @@ +from sagemaker_inference.default_handler_service import DefaultHandlerService +from sagemaker_inference.transformer import Transformer + +from .default_inference_handler import DefaultDarknetClassifierInferenceHandler + + +class HandlerService(DefaultHandlerService): + """Handler service that is executed by the model server. + Determines specific default inference handlers to use based on the type MXNet model being used. + This class extends ``DefaultHandlerService``, which define the following: + - The ``handle`` method is invoked for all incoming inference requests to the model server. + - The ``initialize`` method is invoked at model server start up. + Based on: https://github.com/awslabs/mxnet-model-server/blob/master/docs/custom_service.md + """ + def __init__(self): + self._initialized = False + + transformer = Transformer(default_inference_handler=DefaultDarknetClassifierInferenceHandler()) + super(HandlerService, self).__init__(transformer=transformer) diff --git a/src/darknet/sagemaker/default_inference_handler.py b/src/darknet/sagemaker/default_inference_handler.py new file mode 100644 index 0000000..e9f0134 --- /dev/null +++ b/src/darknet/sagemaker/default_inference_handler.py @@ -0,0 +1,43 @@ +import io +import numpy as np +import PIL.Image as Image + +from abc import ABC +from glob import glob +from typing import Tuple, List, Union + +from sagemaker_inference.decoder import decode +from sagemaker_inference.default_inference_handler import DefaultInferenceHandler + +from ..py import Network + + +class DefaultDarknetInferenceHandler(DefaultInferenceHandler, ABC): + def default_model_fn(self, model_dir) -> Tuple[Network, List[str]]: + """ + Loads a model. + For PyTorch, a default function to load a model cannot be provided. + Returns: A DarkNet Detector. + """ + labels_file = glob(f"{model_dir}/*.labels")[0] + with open(labels_file) as f: + labels = [line.rstrip() for line in f.readlines()] + + cfg_file = glob(f"{model_dir}/*.cfg")[0] + weights_file = glob(f"{model_dir}/*.weights")[0] + + return Network(cfg_file, weights_file, batch_size=1), labels + + def default_input_fn(self, input_data, content_type) -> Union[Image.Image, np.array]: + """A default input_fn that can handle PIL Image Types + + Args: + input_data: the request payload serialized in the content_type format + content_type: the request content_type + + Returns: a PIL Image ready for ImageClassifier + """ + if content_type.startswith("image/"): + image = Image.open(io.BytesIO(input_data)) + return image + return decode(input_data, content_type) diff --git a/src/darknet/sagemaker/detector/__init__.py b/src/darknet/sagemaker/detector/__init__.py new file mode 100644 index 0000000..ddee0ce --- /dev/null +++ b/src/darknet/sagemaker/detector/__init__.py @@ -0,0 +1,3 @@ +from .handler_service import HandlerService + +__all__ = ["HandlerService"] diff --git a/src/darknet/sagemaker/detector/__main__.py b/src/darknet/sagemaker/detector/__main__.py new file mode 100644 index 0000000..3b91989 --- /dev/null +++ b/src/darknet/sagemaker/detector/__main__.py @@ -0,0 +1,27 @@ +from retrying import retry +from subprocess import CalledProcessError +from sagemaker_inference import model_server + +from . import handler_service + + +def _retry_if_error(exception): + return isinstance(exception, CalledProcessError or OSError) + + +@retry(stop_max_delay=1000 * 50, + retry_on_exception=_retry_if_error) +def _start_mms(): + # by default the number of workers per model is 1, but we can configure it through the + # environment variable below if desired. + # os.environ['SAGEMAKER_MODEL_SERVER_WORKERS'] = '2' + model_server.start_model_server( + handler_service=handler_service.__name__ + ) + + +def main(): + _start_mms() + + +main() diff --git a/src/darknet/sagemaker/detector/default_inference_handler.py b/src/darknet/sagemaker/detector/default_inference_handler.py new file mode 100644 index 0000000..38eab54 --- /dev/null +++ b/src/darknet/sagemaker/detector/default_inference_handler.py @@ -0,0 +1,42 @@ +import PIL.Image as Image + +from typing import Tuple, List +from sagemaker_inference import encoder, errors + +from .. import DefaultDarknetInferenceHandler, Network, image_to_3darray + + +class DefaultDarknetDetectorInferenceHandler(DefaultDarknetInferenceHandler): + def default_predict_fn(self, data, model: Tuple[Network, List[str]]): + """A default predict_fn for DarkNet. Calls a model on data deserialized in input_fn. + Args: + data: input data (PIL.Image) for prediction deserialized by input_fn + model: Darknet model loaded in memory by model_fn + + Returns: detected labels + """ + network, labels = model + if not isinstance(data, Image.Image): + raise TypeError("Input data is not an Image.") + + image, frame_size = image_to_3darray(data, network.shape) + _ = network.predict_image(image) + detections = network.detect(frame_size=frame_size) + + return ( + detections + if labels is None + else [(labels[label_idx], prob, bbox) for label_idx, prob, bbox in detections] + ) + + def default_output_fn(self, prediction, accept): + """A default output_fn for PyTorch. Serializes predictions from predict_fn to JSON, CSV or NPY format. + + Args: + prediction: a prediction result from predict_fn + accept: type which the output data needs to be serialized + + Returns: output data serialized (Return Sagemaker format?) + """ + prediction = sorted(prediction, key=lambda x: x[1], reverse=True)[0:5] + return encoder.encode(list(prediction), accept) diff --git a/src/darknet/sagemaker/detector/handler_service.py b/src/darknet/sagemaker/detector/handler_service.py new file mode 100644 index 0000000..b87db17 --- /dev/null +++ b/src/darknet/sagemaker/detector/handler_service.py @@ -0,0 +1,19 @@ +from sagemaker_inference.default_handler_service import DefaultHandlerService +from sagemaker_inference.transformer import Transformer + +from .default_inference_handler import DefaultDarknetDetectorInferenceHandler + + +class HandlerService(DefaultHandlerService): + """Handler service that is executed by the model server. + Determines specific default inference handlers to use based on the type MXNet model being used. + This class extends ``DefaultHandlerService``, which define the following: + - The ``handle`` method is invoked for all incoming inference requests to the model server. + - The ``initialize`` method is invoked at model server start up. + Based on: https://github.com/awslabs/mxnet-model-server/blob/master/docs/custom_service.md + """ + def __init__(self): + self._initialized = False + + transformer = Transformer(default_inference_handler=DefaultDarknetDetectorInferenceHandler()) + super(HandlerService, self).__init__(transformer=transformer) From 4bf2da316255b84ca44f659ffc05c09e340089a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Tue, 2 Nov 2021 12:00:37 -0400 Subject: [PATCH 02/16] Fix a few typos --- src/darknet/sagemaker/__main__.py | 2 +- src/darknet/sagemaker/classifier/default_inference_handler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/darknet/sagemaker/__main__.py b/src/darknet/sagemaker/__main__.py index 78a4a68..cae3e8b 100644 --- a/src/darknet/sagemaker/__main__.py +++ b/src/darknet/sagemaker/__main__.py @@ -2,7 +2,7 @@ from subprocess import CalledProcessError from sagemaker_inference import model_server -from .classfier import handler_service as classifier_service +from .classifier import handler_service as classifier_service from .detector import handler_service as detector_service diff --git a/src/darknet/sagemaker/classifier/default_inference_handler.py b/src/darknet/sagemaker/classifier/default_inference_handler.py index 0ff7fef..125e0c0 100644 --- a/src/darknet/sagemaker/classifier/default_inference_handler.py +++ b/src/darknet/sagemaker/classifier/default_inference_handler.py @@ -3,7 +3,7 @@ from typing import Tuple, List from sagemaker_inference import content_types, decoder, encoder, errors -from .. import DefaultDarknetInferenceHandler, image_to_3darray +from .. import DefaultDarknetInferenceHandler, image_to_3darray, Network class DefaultDarknetClassifierInferenceHandler(DefaultDarknetInferenceHandler): From df8570842e6644f79febf605e405cac7ecff9dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 00:38:49 -0400 Subject: [PATCH 03/16] Convert prediction output format to conform to AWS Rekognition. Ref: https://docs.aws.amazon.com/rekognition/latest/dg/API_DetectLabels.html --- .../classifier/default_inference_handler.py | 23 +++----- .../sagemaker/default_inference_handler.py | 12 +++++ .../detector/default_inference_handler.py | 54 ++++++++++++------- 3 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/darknet/sagemaker/classifier/default_inference_handler.py b/src/darknet/sagemaker/classifier/default_inference_handler.py index 125e0c0..fde3096 100644 --- a/src/darknet/sagemaker/classifier/default_inference_handler.py +++ b/src/darknet/sagemaker/classifier/default_inference_handler.py @@ -1,13 +1,11 @@ import PIL.Image as Image from typing import Tuple, List -from sagemaker_inference import content_types, decoder, encoder, errors from .. import DefaultDarknetInferenceHandler, image_to_3darray, Network class DefaultDarknetClassifierInferenceHandler(DefaultDarknetInferenceHandler): - def default_predict_fn(self, data, model: Tuple[Network, List[str]]): """A default predict_fn for DarkNet. Calls a model on data deserialized in input_fn. Args: @@ -17,22 +15,17 @@ def default_predict_fn(self, data, model: Tuple[Network, List[str]]): Returns: a prediction """ network, labels = model + max_labels = 5 + min_confidence = 55 + if isinstance(data, Image.Image): image, _ = image_to_3darray(data, network.shape) probabilities = network.predict_image(image) else: probabilities = network.predict(data) - return zip(labels, probabilities) - - def default_output_fn(self, prediction, accept): - """A default output_fn for PyTorch. Serializes predictions from predict_fn to JSON, CSV or NPY format. - - Args: - prediction: a prediction result from predict_fn - accept: type which the output data needs to be serialized - - Returns: output data serialized - """ - prediction = sorted(prediction, key=lambda x: x[1], reverse=True)[0:5] - return encoder.encode(list(prediction), accept) + rv = [{ + "Name": label, + "Confidence": prob*100, + } for label, prob in sorted(zip(labels, probabilities), key=lambda x: x[1], reverse=True)] + return {"Labels": rv[0:max_labels] if max_labels else rv} diff --git a/src/darknet/sagemaker/default_inference_handler.py b/src/darknet/sagemaker/default_inference_handler.py index e9f0134..c5632b1 100644 --- a/src/darknet/sagemaker/default_inference_handler.py +++ b/src/darknet/sagemaker/default_inference_handler.py @@ -6,6 +6,7 @@ from glob import glob from typing import Tuple, List, Union +from sagemaker_inference.encoder import encode from sagemaker_inference.decoder import decode from sagemaker_inference.default_inference_handler import DefaultInferenceHandler @@ -41,3 +42,14 @@ def default_input_fn(self, input_data, content_type) -> Union[Image.Image, np.ar image = Image.open(io.BytesIO(input_data)) return image return decode(input_data, content_type) + + def default_output_fn(self, prediction, accept): + """A default output_fn for PyTorch. Serializes predictions from predict_fn to JSON, CSV or NPY format. + + Args: + prediction: a prediction result from predict_fn + accept: type which the output data needs to be serialized + + Returns: output data serialized (Return Sagemaker format?) + """ + return encode(prediction, accept) diff --git a/src/darknet/sagemaker/detector/default_inference_handler.py b/src/darknet/sagemaker/detector/default_inference_handler.py index 38eab54..b7a7ef8 100644 --- a/src/darknet/sagemaker/detector/default_inference_handler.py +++ b/src/darknet/sagemaker/detector/default_inference_handler.py @@ -1,7 +1,9 @@ +from itertools import groupby + import PIL.Image as Image from typing import Tuple, List -from sagemaker_inference import encoder, errors +from sagemaker_inference import errors from .. import DefaultDarknetInferenceHandler, Network, image_to_3darray @@ -16,27 +18,43 @@ def default_predict_fn(self, data, model: Tuple[Network, List[str]]): Returns: detected labels """ network, labels = model + max_labels = None + min_confidence = 55 + if not isinstance(data, Image.Image): raise TypeError("Input data is not an Image.") image, frame_size = image_to_3darray(data, network.shape) _ = network.predict_image(image) - detections = network.detect(frame_size=frame_size) - - return ( - detections - if labels is None - else [(labels[label_idx], prob, bbox) for label_idx, prob, bbox in detections] + detections = network.detect( + frame_size=frame_size, + threshold=min_confidence/100.0, + hierarchical_threshold=min_confidence/100, ) + detections = sorted(detections, key=lambda x: x[0]) + + def bbox_to_sm_map(x_0, y_0, w, h): + return { + "Width": w, + "Height": h, + "Left": x_0 - w/2, + "Top": y_0 + h/2 + } + + rv = [] + for label_idx, instances in groupby(detections, key=lambda x: x[0]): + instances = [{ + "Confidence": prob*100, + "BoundingBox": bbox_to_sm_map(*bbox) + } for _, prob, bbox in sorted(instances, key=lambda x: x[1], reverse=True) + ] + detection = { + "Name": labels[label_idx], + "Confidence": instances[0]["Confidence"], + "Instances": instances, + "Parents": [] + } + detection["Confidence"] = detection["Instances"][0]["Confidence"] + rv.append(detection) + return {"Labels": rv[0:max_labels] if max_labels else rv} - def default_output_fn(self, prediction, accept): - """A default output_fn for PyTorch. Serializes predictions from predict_fn to JSON, CSV or NPY format. - - Args: - prediction: a prediction result from predict_fn - accept: type which the output data needs to be serialized - - Returns: output data serialized (Return Sagemaker format?) - """ - prediction = sorted(prediction, key=lambda x: x[1], reverse=True)[0:5] - return encoder.encode(list(prediction), accept) From edce6ece302b471ce21d2d3705afdc5cc6801c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 00:39:40 -0400 Subject: [PATCH 04/16] Increase VagrantBox memory to 2GB. --- Vagrantfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Vagrantfile b/Vagrantfile index 6070543..9e13fe7 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -59,6 +59,9 @@ Vagrant.configure("2") do |config| # # View the documentation for the provider you are using for more # information on available options. + config.vm.provider "vmware_desktop" do |v| + v.vmx["memsize"] = "2048" + end # Enable provisioning with a shell script. Additional provisioners such as # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the From f87d8a93c54726a50040f2adbfcaa78bd5ebc3c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 01:47:28 -0400 Subject: [PATCH 05/16] Refactor code --- .../classifier/default_inference_handler.py | 14 +++++++++----- .../sagemaker/default_inference_handler.py | 9 +++++++-- .../detector/default_inference_handler.py | 15 +++++++++------ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/darknet/sagemaker/classifier/default_inference_handler.py b/src/darknet/sagemaker/classifier/default_inference_handler.py index fde3096..43d5380 100644 --- a/src/darknet/sagemaker/classifier/default_inference_handler.py +++ b/src/darknet/sagemaker/classifier/default_inference_handler.py @@ -2,6 +2,8 @@ from typing import Tuple, List +from sagemaker_inference.errors import UnsupportedFormatError + from .. import DefaultDarknetInferenceHandler, image_to_3darray, Network @@ -15,14 +17,16 @@ def default_predict_fn(self, data, model: Tuple[Network, List[str]]): Returns: a prediction """ network, labels = model - max_labels = 5 - min_confidence = 55 + max_labels = data.get("MaxLabels", 5) + min_confidence = data.get("MinConfidence", 55) - if isinstance(data, Image.Image): - image, _ = image_to_3darray(data, network.shape) + if "NDArray" in data: + probabilities = network.predict(data["NDArray"]) + elif "Image" in data: + image, _ = image_to_3darray(data["Image"], network.shape) probabilities = network.predict_image(image) else: - probabilities = network.predict(data) + raise UnsupportedFormatError("Expected an NDArray or an Image") rv = [{ "Name": label, diff --git a/src/darknet/sagemaker/default_inference_handler.py b/src/darknet/sagemaker/default_inference_handler.py index c5632b1..60ecb99 100644 --- a/src/darknet/sagemaker/default_inference_handler.py +++ b/src/darknet/sagemaker/default_inference_handler.py @@ -1,8 +1,12 @@ import io +import json + +import base64 import numpy as np import PIL.Image as Image from abc import ABC +from base64 import b64decode from glob import glob from typing import Tuple, List, Union @@ -40,8 +44,9 @@ def default_input_fn(self, input_data, content_type) -> Union[Image.Image, np.ar """ if content_type.startswith("image/"): image = Image.open(io.BytesIO(input_data)) - return image - return decode(input_data, content_type) + return {"Image": image} + else: + return {"NDArray": decode(input_data, content_type)} def default_output_fn(self, prediction, accept): """A default output_fn for PyTorch. Serializes predictions from predict_fn to JSON, CSV or NPY format. diff --git a/src/darknet/sagemaker/detector/default_inference_handler.py b/src/darknet/sagemaker/detector/default_inference_handler.py index b7a7ef8..1e56706 100644 --- a/src/darknet/sagemaker/detector/default_inference_handler.py +++ b/src/darknet/sagemaker/detector/default_inference_handler.py @@ -3,7 +3,7 @@ import PIL.Image as Image from typing import Tuple, List -from sagemaker_inference import errors +from sagemaker_inference.errors import UnsupportedFormatError from .. import DefaultDarknetInferenceHandler, Network, image_to_3darray @@ -17,15 +17,18 @@ def default_predict_fn(self, data, model: Tuple[Network, List[str]]): Returns: detected labels """ + if "Image" not in data: + raise UnsupportedFormatError("Detector model expects an Image.") + network, labels = model - max_labels = None - min_confidence = 55 - if not isinstance(data, Image.Image): - raise TypeError("Input data is not an Image.") + max_labels = data.get("MaxLabels", None) + min_confidence = data.get("MinConfidence", 55) - image, frame_size = image_to_3darray(data, network.shape) + image = data["Image"] + image, frame_size = image_to_3darray(image, network.shape) _ = network.predict_image(image) + detections = network.detect( frame_size=frame_size, threshold=min_confidence/100.0, From b1af2cd6b628f19e7ba7ef6c2754c76fe240a67c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 02:00:45 -0400 Subject: [PATCH 06/16] Update actions/setup-python@v2.2.2 --- .github/workflows/pypa-conda.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypa-conda.yml b/.github/workflows/pypa-conda.yml index 38c4bb6..dc99ecc 100644 --- a/.github/workflows/pypa-conda.yml +++ b/.github/workflows/pypa-conda.yml @@ -28,7 +28,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 - - uses: actions/setup-python@v1.1.1 + - uses: actions/setup-python@v2.2.2 with: python-version: ${{ matrix.python-version }} - uses: actions/cache@v1 From 620690f834d0560acf3b2746409ec049e9e3fe86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 06:02:14 +0000 Subject: [PATCH 07/16] Make it black --- src/darknet/sagemaker/__main__.py | 7 ++----- src/darknet/sagemaker/classifier/__main__.py | 7 ++----- .../classifier/default_inference_handler.py | 11 ++++++---- .../sagemaker/classifier/handler_service.py | 5 ++++- src/darknet/sagemaker/detector/__main__.py | 7 ++----- .../detector/default_inference_handler.py | 21 +++++++------------ .../sagemaker/detector/handler_service.py | 5 ++++- 7 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/darknet/sagemaker/__main__.py b/src/darknet/sagemaker/__main__.py index cae3e8b..cb005a5 100644 --- a/src/darknet/sagemaker/__main__.py +++ b/src/darknet/sagemaker/__main__.py @@ -10,16 +10,13 @@ def _retry_if_error(exception): return isinstance(exception, CalledProcessError or OSError) -@retry(stop_max_delay=1000 * 50, - retry_on_exception=_retry_if_error) +@retry(stop_max_delay=1000 * 50, retry_on_exception=_retry_if_error) def _start_mms(): # by default the number of workers per model is 1, but we can configure it through the # environment variable below if desired. # os.environ['SAGEMAKER_MODEL_SERVER_WORKERS'] = '2' # TODO: Start Classifier *or* Detector Service - model_server.start_model_server( - handler_service=detector_service.__name__ - ) + model_server.start_model_server(handler_service=detector_service.__name__) def main(): diff --git a/src/darknet/sagemaker/classifier/__main__.py b/src/darknet/sagemaker/classifier/__main__.py index 14dfa1a..afb949a 100644 --- a/src/darknet/sagemaker/classifier/__main__.py +++ b/src/darknet/sagemaker/classifier/__main__.py @@ -9,16 +9,13 @@ def _retry_if_error(exception): return isinstance(exception, CalledProcessError or OSError) -@retry(stop_max_delay=1000 * 50, - retry_on_exception=_retry_if_error) +@retry(stop_max_delay=1000 * 50, retry_on_exception=_retry_if_error) def _start_mms(): # by default the number of workers per model is 1, but we can configure it through the # environment variable below if desired. # os.environ['SAGEMAKER_MODEL_SERVER_WORKERS'] = '2' # TODO: Start Classifier *or* Detector Service - model_server.start_model_server( - handler_service=classifier_service.__name__ - ) + model_server.start_model_server(handler_service=classifier_service.__name__) def main(): diff --git a/src/darknet/sagemaker/classifier/default_inference_handler.py b/src/darknet/sagemaker/classifier/default_inference_handler.py index 43d5380..e03efbd 100644 --- a/src/darknet/sagemaker/classifier/default_inference_handler.py +++ b/src/darknet/sagemaker/classifier/default_inference_handler.py @@ -28,8 +28,11 @@ def default_predict_fn(self, data, model: Tuple[Network, List[str]]): else: raise UnsupportedFormatError("Expected an NDArray or an Image") - rv = [{ - "Name": label, - "Confidence": prob*100, - } for label, prob in sorted(zip(labels, probabilities), key=lambda x: x[1], reverse=True)] + rv = [ + { + "Name": label, + "Confidence": prob * 100, + } + for label, prob in sorted(zip(labels, probabilities), key=lambda x: x[1], reverse=True) + ] return {"Labels": rv[0:max_labels] if max_labels else rv} diff --git a/src/darknet/sagemaker/classifier/handler_service.py b/src/darknet/sagemaker/classifier/handler_service.py index 3d105f3..cacbf72 100644 --- a/src/darknet/sagemaker/classifier/handler_service.py +++ b/src/darknet/sagemaker/classifier/handler_service.py @@ -12,8 +12,11 @@ class HandlerService(DefaultHandlerService): - The ``initialize`` method is invoked at model server start up. Based on: https://github.com/awslabs/mxnet-model-server/blob/master/docs/custom_service.md """ + def __init__(self): self._initialized = False - transformer = Transformer(default_inference_handler=DefaultDarknetClassifierInferenceHandler()) + transformer = Transformer( + default_inference_handler=DefaultDarknetClassifierInferenceHandler() + ) super(HandlerService, self).__init__(transformer=transformer) diff --git a/src/darknet/sagemaker/detector/__main__.py b/src/darknet/sagemaker/detector/__main__.py index 3b91989..76e5b2a 100644 --- a/src/darknet/sagemaker/detector/__main__.py +++ b/src/darknet/sagemaker/detector/__main__.py @@ -9,15 +9,12 @@ def _retry_if_error(exception): return isinstance(exception, CalledProcessError or OSError) -@retry(stop_max_delay=1000 * 50, - retry_on_exception=_retry_if_error) +@retry(stop_max_delay=1000 * 50, retry_on_exception=_retry_if_error) def _start_mms(): # by default the number of workers per model is 1, but we can configure it through the # environment variable below if desired. # os.environ['SAGEMAKER_MODEL_SERVER_WORKERS'] = '2' - model_server.start_model_server( - handler_service=handler_service.__name__ - ) + model_server.start_model_server(handler_service=handler_service.__name__) def main(): diff --git a/src/darknet/sagemaker/detector/default_inference_handler.py b/src/darknet/sagemaker/detector/default_inference_handler.py index 1e56706..647a714 100644 --- a/src/darknet/sagemaker/detector/default_inference_handler.py +++ b/src/darknet/sagemaker/detector/default_inference_handler.py @@ -31,33 +31,26 @@ def default_predict_fn(self, data, model: Tuple[Network, List[str]]): detections = network.detect( frame_size=frame_size, - threshold=min_confidence/100.0, - hierarchical_threshold=min_confidence/100, + threshold=min_confidence / 100.0, + hierarchical_threshold=min_confidence / 100, ) detections = sorted(detections, key=lambda x: x[0]) def bbox_to_sm_map(x_0, y_0, w, h): - return { - "Width": w, - "Height": h, - "Left": x_0 - w/2, - "Top": y_0 + h/2 - } + return {"Width": w, "Height": h, "Left": x_0 - w / 2, "Top": y_0 + h / 2} rv = [] for label_idx, instances in groupby(detections, key=lambda x: x[0]): - instances = [{ - "Confidence": prob*100, - "BoundingBox": bbox_to_sm_map(*bbox) - } for _, prob, bbox in sorted(instances, key=lambda x: x[1], reverse=True) + instances = [ + {"Confidence": prob * 100, "BoundingBox": bbox_to_sm_map(*bbox)} + for _, prob, bbox in sorted(instances, key=lambda x: x[1], reverse=True) ] detection = { "Name": labels[label_idx], "Confidence": instances[0]["Confidence"], "Instances": instances, - "Parents": [] + "Parents": [], } detection["Confidence"] = detection["Instances"][0]["Confidence"] rv.append(detection) return {"Labels": rv[0:max_labels] if max_labels else rv} - diff --git a/src/darknet/sagemaker/detector/handler_service.py b/src/darknet/sagemaker/detector/handler_service.py index b87db17..41a16bc 100644 --- a/src/darknet/sagemaker/detector/handler_service.py +++ b/src/darknet/sagemaker/detector/handler_service.py @@ -12,8 +12,11 @@ class HandlerService(DefaultHandlerService): - The ``initialize`` method is invoked at model server start up. Based on: https://github.com/awslabs/mxnet-model-server/blob/master/docs/custom_service.md """ + def __init__(self): self._initialized = False - transformer = Transformer(default_inference_handler=DefaultDarknetDetectorInferenceHandler()) + transformer = Transformer( + default_inference_handler=DefaultDarknetDetectorInferenceHandler() + ) super(HandlerService, self).__init__(transformer=transformer) From 62d5e634aa9b4e42c6012df6150572a2b28f7fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 02:05:56 -0400 Subject: [PATCH 08/16] Fix flake8 --- src/darknet/sagemaker/__main__.py | 2 +- src/darknet/sagemaker/classifier/default_inference_handler.py | 4 +--- src/darknet/sagemaker/default_inference_handler.py | 3 --- src/darknet/sagemaker/detector/default_inference_handler.py | 2 -- 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/darknet/sagemaker/__main__.py b/src/darknet/sagemaker/__main__.py index cb005a5..9ea1b9c 100644 --- a/src/darknet/sagemaker/__main__.py +++ b/src/darknet/sagemaker/__main__.py @@ -2,7 +2,7 @@ from subprocess import CalledProcessError from sagemaker_inference import model_server -from .classifier import handler_service as classifier_service +# TODO: from .classifier import handler_service as classifier_service from .detector import handler_service as detector_service diff --git a/src/darknet/sagemaker/classifier/default_inference_handler.py b/src/darknet/sagemaker/classifier/default_inference_handler.py index e03efbd..8b6f7b7 100644 --- a/src/darknet/sagemaker/classifier/default_inference_handler.py +++ b/src/darknet/sagemaker/classifier/default_inference_handler.py @@ -1,5 +1,3 @@ -import PIL.Image as Image - from typing import Tuple, List from sagemaker_inference.errors import UnsupportedFormatError @@ -18,7 +16,7 @@ def default_predict_fn(self, data, model: Tuple[Network, List[str]]): """ network, labels = model max_labels = data.get("MaxLabels", 5) - min_confidence = data.get("MinConfidence", 55) + # TODO: min_confidence = data.get("MinConfidence", 55) if "NDArray" in data: probabilities = network.predict(data["NDArray"]) diff --git a/src/darknet/sagemaker/default_inference_handler.py b/src/darknet/sagemaker/default_inference_handler.py index 60ecb99..9a3c93c 100644 --- a/src/darknet/sagemaker/default_inference_handler.py +++ b/src/darknet/sagemaker/default_inference_handler.py @@ -1,12 +1,9 @@ import io -import json -import base64 import numpy as np import PIL.Image as Image from abc import ABC -from base64 import b64decode from glob import glob from typing import Tuple, List, Union diff --git a/src/darknet/sagemaker/detector/default_inference_handler.py b/src/darknet/sagemaker/detector/default_inference_handler.py index 647a714..2d1d110 100644 --- a/src/darknet/sagemaker/detector/default_inference_handler.py +++ b/src/darknet/sagemaker/detector/default_inference_handler.py @@ -1,7 +1,5 @@ from itertools import groupby -import PIL.Image as Image - from typing import Tuple, List from sagemaker_inference.errors import UnsupportedFormatError From 0121987811a89f20fe4bf0c470ea098eaf310b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 02:09:24 -0400 Subject: [PATCH 09/16] Update workflow to python 3.7 --- .github/workflows/pypa-conda.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypa-conda.yml b/.github/workflows/pypa-conda.yml index dc99ecc..cb06b73 100644 --- a/.github/workflows/pypa-conda.yml +++ b/.github/workflows/pypa-conda.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: [3.6] + python-version: [3.7] black-version: [19.10b] flake8-version: [3.7.9] runs-on: ${{ matrix.os }} From 2b7c9b45b4574978be4732dbb2501aa48ec9e1b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 02:14:11 -0400 Subject: [PATCH 10/16] Update GitHub workflow --- .github/workflows/pypa-conda.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pypa-conda.yml b/.github/workflows/pypa-conda.yml index cb06b73..709405a 100644 --- a/.github/workflows/pypa-conda.yml +++ b/.github/workflows/pypa-conda.yml @@ -75,7 +75,7 @@ jobs: rm -f *.tar.gz # Create the bdist wheel file - - uses: actions/setup-python@v1.1.1 + - uses: actions/setup-python@v2.2.2 - uses: actions/cache@v1 id: cache with: @@ -152,7 +152,7 @@ jobs: # # Setup the test environment, python + .whl + .whl[test] - - uses: actions/setup-python@v1.1.1 + - uses: actions/setup-python@v2.2.2 with: python-version: ${{ matrix.python-version }} - name: Get pip cache @@ -215,7 +215,7 @@ jobs: with: activate-environment: '' auto-activate-base: true - conda-build-version: 3.18 + conda-build-version: 3.21.4 condarc-file: .github/condarc.yml - run: conda install setuptools_scm conda-verify - uses: actions/cache@v1 From 4b90502abc82f65cc4fc1f9de8daf2f31e956452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 02:21:23 -0400 Subject: [PATCH 11/16] Update to setup-miniconda@v2 --- .github/workflows/pypa-conda.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypa-conda.yml b/.github/workflows/pypa-conda.yml index 709405a..08eb96b 100644 --- a/.github/workflows/pypa-conda.yml +++ b/.github/workflows/pypa-conda.yml @@ -211,7 +211,7 @@ jobs: tar -xvf *.tar.gz --strip 1 rm *.tar.gz - - uses: goanpeca/setup-miniconda@v1 + - uses: conda-incubator/setup-miniconda@v2 with: activate-environment: '' auto-activate-base: true @@ -338,7 +338,7 @@ jobs: with: path: /usr/share/miniconda/pkgs key: ${{ runner.os }}-conda-ac1.7.2 - - uses: goanpeca/setup-miniconda@v1 + - uses: conda-incubator/setup-miniconda@v2 with: activate-environment: '' auto-activate-base: true From d9171df7ceff5155687b796b4e2b93ed2d760349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 02:27:50 -0400 Subject: [PATCH 12/16] Fix the darknet import statement --- src/darknet/py/__init__.py | 2 +- src/darknet/sagemaker/default_inference_handler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/darknet/py/__init__.py b/src/darknet/py/__init__.py index 80945b2..1ad2a37 100644 --- a/src/darknet/py/__init__.py +++ b/src/darknet/py/__init__.py @@ -4,4 +4,4 @@ from .classifier import Classifier, ImageClassifier from .detector import ImageDetector -__all__ = ["Network", "Classifier", "ImageClassifier", "ImageDetector"] +__all__ = ["Classifier", "ImageClassifier", "ImageDetector"] diff --git a/src/darknet/sagemaker/default_inference_handler.py b/src/darknet/sagemaker/default_inference_handler.py index 9a3c93c..94bb866 100644 --- a/src/darknet/sagemaker/default_inference_handler.py +++ b/src/darknet/sagemaker/default_inference_handler.py @@ -11,7 +11,7 @@ from sagemaker_inference.decoder import decode from sagemaker_inference.default_inference_handler import DefaultInferenceHandler -from ..py import Network +from darknet.py.network import Network class DefaultDarknetInferenceHandler(DefaultInferenceHandler, ABC): From fc5831b4b3d61127226fdb902e059ba63c316c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 02:30:32 -0400 Subject: [PATCH 13/16] flake8 --- src/darknet/py/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/darknet/py/__init__.py b/src/darknet/py/__init__.py index 1ad2a37..a810fc5 100644 --- a/src/darknet/py/__init__.py +++ b/src/darknet/py/__init__.py @@ -1,6 +1,5 @@ """Top-level package for DarkNet OpenSource Neural Networks in Python.""" from ._version import version as __version__ # noqa: F401 -from .network import Network from .classifier import Classifier, ImageClassifier from .detector import ImageDetector From 7d1c00a04a83a324d7d2f8878e7f08d4f6d2f9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 09:33:48 -0400 Subject: [PATCH 14/16] Try build using mambaforge --- .github/workflows/pypa-conda.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypa-conda.yml b/.github/workflows/pypa-conda.yml index 08eb96b..0442a14 100644 --- a/.github/workflows/pypa-conda.yml +++ b/.github/workflows/pypa-conda.yml @@ -215,9 +215,12 @@ jobs: with: activate-environment: '' auto-activate-base: true + miniforge-variant: Mambaforge + use-mamba: true conda-build-version: 3.21.4 condarc-file: .github/condarc.yml - - run: conda install setuptools_scm conda-verify + - run: | + mamba install setuptools_scm conda-verify boa - uses: actions/cache@v1 id: conda-pkgs-cache with: @@ -228,7 +231,7 @@ jobs: - name: Run conda build run: | mkdir conda-bld - conda build --output-folder conda-bld . + conda mambabuild --output-folder conda-bld . env: ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_API_TOKEN }} - name: Create conda-bld/manifest From 2f7898d2e1bac875bc18535bcb2ec05c884f2de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 09:55:49 -0400 Subject: [PATCH 15/16] Remove defaults from channels list --- .github/condarc.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/condarc.yml b/.github/condarc.yml index 6c32d74..85e3c8e 100644 --- a/.github/condarc.yml +++ b/.github/condarc.yml @@ -1,6 +1,5 @@ anaconda_upload: false channels: - zeroae - - defaults - conda-forge -show_channel_urls: true \ No newline at end of file +show_channel_urls: true From 59be2960b060cae54810afb17fb8f52eb16eb975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Sodr=C3=A9?= Date: Wed, 3 Nov 2021 10:05:29 -0400 Subject: [PATCH 16/16] Use mambabuild locally --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index c0919fa..d49f965 100644 --- a/Makefile +++ b/Makefile @@ -93,10 +93,10 @@ wheels: dist ## downloads wheel dependencies ls -l wheels dist-conda: ## builds conda-package - conda build --no-anaconda-upload --output-folder conda-bld \ + conda mambabuild --no-anaconda-upload --output-folder conda-bld \ -c zeroae \ -c conda-forge \ - -c anaconda . + . install: clean ## install the package to the active Python's site-packages - python setup.py install \ No newline at end of file + python setup.py install