{ "info": { "author": "Andrew Ferlitsch", "author_email": "aferlitsch@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.0", "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Scientific/Engineering :: Image Recognition" ], "description": "# Gap CV\n\n ![gap](docs/img/gap.png)\n\n## Intro\n\n**Gap** is a data engineering framework for machine learning. The **GapCV** is a component of **Gap** for computer vision (CV). The component manages data preparation of images, feeding and serving neural network models, and data management of persistent storage.\n\nThe module is written in a modern *object oriented programming (OOP)* abstraction with an *imperative programming* style that fits seamlessly into ML frameworks which are moving into imperative programming, such as **Keras** and **PyTorch**. The bottom layers of the module are written in a *bare metal* style for high performance.\n\n**Gap** was inspired by a meeting of data scientists and machine learning enthusiasts in Portland, OR in May 2018. The first version of **Gap** was available during the summer and the local software community was engaged through meetups, chats, Kaggle groups, and a conference at the Oxford Suites. During the Fall, a decision was made to refactor **Gap** into an industrial grade application, spearheaded by Gap's lead, David Molina, and overseen and assisted by Andrew Ferlitsch, Google AI. \n\n## Why and Why Now\n\nDuring the Spring of 2018, many of us had observed advancements in redesign of ML frameworks (such as **Keras** and **PyTorch**) to migrate into frameworks which would have broader adoption in the software engineering community. But, the focus was primarily on the model building and not on the data engineering. Across the Internet, between blogs, tutorials and online classes, the examples for data engineering was still a wild west. To us, we saw this as a gap, and hence the name **Gap**.\n\nML practitioners today recognize the substantial component that data engineering is within the machine learning ecosystem, and the need to modernize, streamline and standardize to meet the needs of the software development community at the same pace as framework advancements are being made on the modeling components.\n\n ![MLEcoSystem](docs/img/MLEcoSystem.png)\n\n## Summary of Features\n\n### Image Types\n\nThe following image formats are supported: \n\n * JPEG and JPEG2000\n * PNG\n * TIF\n * GIF\n * BMP\n * 8 and 16 bits per pixel\n * Grayscale (single channel), RGB (three channels) and RGBA (four channels)\n \n### Image Set (Dataset) Layouts\n\nThe following image dataset layouts are supported (i.e., can be ingested by **Gap**): \n\n * On-Disk (directory, CSV and JSON)\n * In-Memory (list, numpy)\n * Remote (http)\n \nFor CSV and JSON, the image data can be embedded (in-memory), local (on-disk) or URL paths (remote).\n\n### Image Transformations\n\nThe following image transformations are supported: \n\n * Color -> Gray\n * Resizing\n * Flattening\n * Normalization and Standardization\n * Data Type Conversion: 8 and 16 bpp integer and 16, 32 and 64 bpp float\n \nTransformations can be performed when processing an image dataset (ingestion) or performed dynamically in-place when feeding (training) a neural network.\n\n### Image Augmentation\n\nThe following image augmentations are supported:\n\n * Rotation\n * Horizontal and Vertical Flip\n * Zoom\n * Brightening\n * Sharpening\n \nImage augmentation can be performed dynamically in-place during feeding (training) of a neural network.\n\n### Image Feeding\n\nThe following image feeding mechanisms are supported:\n\n * Splitting \n * Shuffling\n * Iterative\n * Generative\n * Mini-batch\n * Stratification\n * One-Hot Label Encoding\n * Streaming\n\nWhen feeding, shuffling is handled using indirect indexing, maintaining the location of data in the heap. One-hot encoding of labels is performed\ndynamically when the feeder is instantiated.\n\n### In-Memory Management\n\nThe following are supported for in-memory management:\n\n * Contiguous Memory (Numpy)\n * Streaming\n * Indirect Indexing (Shuffling w/o moving memory)\n * Data Type Reduction\n * Collection Merging\n * Asynchronous and Concurrent Processing\n \nCollections of image data, which are otherwise disjoint, can be merged efficiently and with label one-hot encoding performed dynamically for feeding neural networks from otherwise disparate sources.\n\n### Persistent Storage Management\n\nThe following are supported for on-disk management:\n\n * HDF5 Storage and Indexing\n * Metadata handling\n * Distributed\n \n## Installation\n\n### Pip Installation: \n\nThe **GapCV** framework is supported on Windows, MacOS, and Linux. It has been packaged for distribution via PyPi.\n\n 1. Install [miniconda](https://conda.io/miniconda.html).\n\n 2. Install conda virtual environment and required packages:\n + Create an environment with: `conda create -n gap python==3.5 jupyter pip` \n + Activate: `source activate gap` \n + `pip install gapcv`\n\n 3. Exiting conda virtual environment: \n + Windows: `deactivate` \n + Linux/macOS: `source deactivate`\n\n### Setup.py Installation:\n\nTo install **GapCV** via setup.py:\n\n 1. Clone from the Github repo. \n + `git clone https://github.com/gapml/CV.git`\n\n 2. Install using the **GapCV** setup file. \n + access folder `cd CV` \n + `python setup.py install`\n \n### Importing GapCV\n\nTo import GapCV into your python application, do:\n\n```python\nfrom gapcv.vision import Images\n```\n\n## Quick Start\n\nImage preparation, neural network feeding and management of image datasets is handled through the class object `Images`. We will provide here a brief discussion on the\nvarious ways of using the `Images` class.\n\nThe initializer has no required (positional) parameters. All the parameters are optional (keyword) parameters. The most frequently used parameters are:\n\n Images( name, dataset, labels, config ) \n \n name : the name of the dataset (e.g., 'cats_n_dogs')\n dataset: the dataset of images\n labels : the labels\n config : configuration settings\n\n### Preparing Datasets\n\nThe first step is to transform the images in an image dataset into machine learning ready data. How the images are transformed is dependent on the image source and the configuration settings. By default, all images are transformed to:\n\n 1. RGB image format\n 2. Resized to (128, 128)\n 3. Float32 pixel data type\n 4. Normalization\n \nIn this quick start section, we will briefly cover preparing datasets that are on-disk, or remotely stored, or in-memory.\n \n*Directory*\n\nA common format for image datasets is to stored them on disk in a directory layout. The layout consists of a root (parent) directory and one or more subdirectories. Each subdirectory is a\nclass (label), such as *cats*. Within the subdirectory are one or more images which belong to that class. Below is an example:\n\n cats_n_dogs\n / \\ \n cats dogs\n / \\\n c1.jpg ... d1.jpg ...\n \nThe following instantiation of the `Images` class object will load the images from local disk into in-memory according to the default transformation settings. Within memory, the set of transformed images will be grouped into two classes: cats, and dogs. \n\n```python\nimages = Images(dataset='cats_n_dogs')\n```\n\nOnce loaded, you can get information on the transformed data as properties of the `Images` class. Below are a few frequently used properties.\n\n```python\nprint(images.name) # will output the name of the dataset: cats_and_dogs\nprint(images.count) # will output the total number of images in both cats and dogs\nprint(images.classes) # will output the class to label mapping: { 'cats': 0, 'dogs': 1 }\nprint(images.images[0]) # will output the numpy arrays for each transformed image in the class with label 0 (cats).\nprint(images.labels[0]) # will output the label for each transformed image in the class with label 0 (cats).\n```\n\nSeveral of the builtin functions have been overridden for the `Images` class. Below are a few frequently used overridden builtin functions:\n\n```python\nprint(len(images)) # number of collections (classes)\nprint(images[0]) # same as images.images[0]\n```\n\n*List*\n\nAlternatively, local on-disk images may be specified as a list of paths, with corresponding list of labels. Below is an example where the `dataset` parameter is specified as a list of\npaths to images, and the `labels` parameter is a list of corresponding labels.\n\n```python\nimages = Images(name='cats_and_dogs', dataset=['cats/1.jpg', 'cats/2.jpg', ... 'dogs/1.jpg'], labels=[0, 0, ... 1])\n```\n\nAlternately, the image paths may be specified as remote locations using URL paths. In this case, a HTTP request will be made to fetch the contents of the image from the remote site.\n\n```python\nimages = Images(name='cats_and_dogs', dataset=['http://mysite.com/cats/1.jpg', 'http://mysite.com/cats/2.jpg', ... ], labels=[0, 0, ...])\n```\n\n*Memory*\n\nIf the dataset is already in memory, for example a curated dataset that is part of a framework (e.g., CIFAR-10 in **Keras**), the in-memory multi-dimensional numpy arrays for the curated images and labels are passed as the values to the `dataset` and `labels` parameter.\n\n```python\nfrom keras.datasets import cifar10\n\n(x_train, y_train), (x_test, y_test) = cifar10.load_data()\n\ntrain = Images('cifar10', dataset=x_train, labels=y_train)\ntest = Images('cifar10', dataset=x_test, labels=y_test)\n```\n\n*CSV*\n\nA dataset can be specified as a CSV (comma separated values) file. Both US (comma) and EU (semi-colon) standard for separators are supported. Each row in the CSV file corresponds to an image\nand corresponding label. The image may be local on-disk, remote or embedded. Below are some example CSV layouts:\n\n *local on-disk*\n label,image\n 'cat','cats/c1.jpg'\n 'dog','dogs/d1.jpg'\n ...\n \n *remote*\n label,image\n 'cat','http://mysite.com/c1.jpg'\n 'dog','http://mysite.com/d1.jpg'\n ...\n \n *embedded pixel data*\n label,name\n 'cat','[ embedded pixel data ]'\n 'dog','[ embedded pixel data ]'\n \nFor CSV, the `config` parameter is specified when instantiating the `Images` class object, to set the settings for:\n\n header # if present, CSV file has a header; otherwise it does not.\n image_col # the column index (starting at 0) of the image field.\n label_col # the column index (starting at 0) of the label field.\n \n```python\nimages = Images(dataset='cats_n_dogs.csv', config=['header', 'image_col=0', 'label_col=1'])\n```\n\nFor EU style (semi-colon) use the `sep` setting to specify the separator is a semi-colon:\n\n```python\nimages = Images(dataset='cats_n_dogs.csv', config=['header', 'image_col=0', 'label_col=1', 'sep=;'])\n```\n\n*JSON*\n\nA dataset can be specified as a JSON (Javascript Object Notation) file, where the file is laid out as an array of objects. Each object corresponds to an image and corresponding label. \nThe image may be local on-disk, remote or embedded. Below are some example JSON layouts:\n\n *local on-disk*\n [\n {'label': 'cat', 'image': 'cats/c1.jpg'},\n {'label': 'dog', 'image': 'dogs/d1.jpg'},\n ...\n ]\n \n *remote*\n [\n {'label': 'cat', 'image': 'http://mysite.com/c1.jpg'},\n {'label': 'dog', 'image': 'http://mystire.com/d1.jpg'},\n ...\n ]\n \n *embedded pixel data*\n [\n {'label': 'cat', 'image': [ embedded pixel data ]},\n {'label': 'dog', 'image': [ embedded pixel data ]},\n ...\n ]\n \nFor JSON, the `config` parameter is specified when instantiating the `Images` class object, to set the settings for:\n\n image_key # the key name of the image field.\n label_key # the key name of the label field.\n \n```python\nimages = Images(dataset='cats_n_dogs.json', config=['image_key=image', 'label_key=label'])\n```\n\n*Transformations*\n\nThe default settings of the image transformations can be overridden as `settings` to the `config` parameter:\n\n gray : process as 2D (single channel) gray scale images\n flatten : process as flatten 1D images (for DNN)\n resize=(h,w) : resize to a specific height x weight (e.g., (28,28))\n norm=pos|neg|std: normalize between 0 and 1 (pos), normalize between -1 and 1 (neg), or standardize.\n float16|float32 : pixel data type\n uint8|uint16 : pixel data type\n \nFor example, if the target neural network is a DNN and the input is a flattened gray scale 28x28 vector (e.g., mnist), one would specify:\n \n```python\nimages = Images(name='mnist', ..., config=[resize=(28,28), 'gray', 'flatten'])\n```\n\nIf the pixel data is to be standardized instead of normalized, one would specify:\n\n```python\nimages = Images(..., config=['norm=std'])\n```\n\nIf your hardware supports half precision floats (16-bit float) and your neural network is designed to not be effected by a vanishing gradient, you can reduce the in-memory size of the\nthe transformed image data by 50% by setting the pixel data type to `float16`.\n\n```python\nimages = Images(..., config=['float16'])\n```\n\nIn another example, you can do a space vs. speed tradeoff. The pixel data type can be set to `uint8` (8-bit integer). In this case, pixel normalization is deferred and performed dynamically \neach time the image is feed to the neural network. The in-memory size of the image data will be 75% smaller than the corresponding `float32` version, or 50% smaller than the corresponding\n`float16` version.\n\n```python\nimages = Images(..., config=['uint8'])\n```\n\n### Feeding Datasets\n\nThe `Images` class provides severals setter/getter properties for feeding a neural network during training. By default, the transformed (machine learning ready) image data is split into\n80% training and 20% test. Prior to splitting, the image data is randomly shuffled. Alternately, one can specify a different percentage for test and a seed for the random shuffle with the\n`split` property used as a setter.\n\n```python\n# some instantiation of an image dataset\nimages = Images(...)\n\n# set 10% as test and shuffle with random seed set to 112\nimages.split = 0.1, 112\n```\n\n*Pre-split Dataset*\n\nThe `split` property when used as a getter will return a pre-split dataset (train and test) of images and corresponding labels (in a fashion familiar to sci-learn train_test_split()). \nThe training data will have been randomly shuffled prior to the split. The image portion (X_train and X_test) is a multi-dimensional numpy array, and the label portion (Y_train and Y_test)\nis a numpy matrix which has been one-hot encoded.\n\n```python\nX_train, X_test, Y_train, Y_test = images.split\n\nprint(X_train.shape) # would output something like (10000, 128, 128, 3)\nprint(Y_train.shape) # would output something like (10000, 10)\n```\n\nIf the pixel data type is uint8 (or uint16), the pixel data will be normalized prior to returning the training and test data.\n\n*Iterative*\n\nThe `next()` operator is overridden to act as a iterator for feeding a neural network. Each invocation of `next()` will return the next image and label in the training set. Once all\nthe image data has been enumerated (i.e., epoch), the` next()` operator will return `None` and randomly reshuffle the training data for the next epoch. The image and label data are returned\nas a multi-dimensional numpy array and one-hot encoded numpy vector, respectively.\n\n```python\nfor _ in range(epochs):\n # pass thru all the training data for an epoch\n while True:\n image, label = next(images)\n if not images:\n break\n```\n\nIf the pixel data type is uint8 (or uint16), the pixel data will be normalized dynamically per invocation of the `next()` operator.\n\nIf the `config` setting `stream` is specified, the pixel data is streamed (memory-less) in from the HDF5 storage on each invocation of the `next()` operator.\n\n*Mini-batch (Generative)*\n\nThe `minibatch` property when used as a setter will set the mini-batch size for creating a generator for feeding the neural network in mini-batches. By default, the mini-batch size is 32.\n\n```python\n# set the mini-batch size to 64\nimages.minibatch = 64\n```\n\nThe `minibatch` property when used as a getter creates a generator on each invocation. The generator will return a sequence of images and labels, whose size is specified as the parameter\n(or default) to `minibatch` when specified as a setter. Each iteration of the generator will sequentially move through the training data. When the end of the training data is reached, the\ntraining data is randomly reshuffled and the `minibatch` getter is reset to start at the beginning of the training data. The image and label data are returned\nas a multi-dimensional numpy array and one-hot encoded numpy vector, respectively.\n\n```python\n# create the generator\ng = images.minibatch\n# feed in steps number of mini-batches\nfor _ in range(steps):\n x_batch, y_batch = g\n```\n\nIf the pixel data type is uint8 (or uint16), the pixel data will be normalized dynamically per creation of a mini-batch generator.\n\nIf the `config` setting `stream` is specified, each batch of pixel data is streamed (memory-less) in from the HDF5 storage per invocation of the `minibatch` property when used as a getter.\n\n*Stratified mini-batch*\n\nThe `stratified` property when used as a setter will set the mini-batch size for creating a generator for feeding the neural network in stratified mini-batches. By default, the mini-batch size is 32. A min-batch is stratified when their is a even distribution of classes within the batch.\n\n```python\n# set the stratified mini-batch size to 64\nimages.stratify = 64\n```\n\nThe `stratify` property when used as a getter creates a generator on each invocation. The generator will return a sequence of images and labels, whose size is specified as the parameter\n(or default) to `stratify` when specified as a setter. Each creation of the generator will sequentially move through the training data. When the end of the training data is reached, the\ntraining data is randomly reshuffled and the 'stratify` getter is reset to start at the beginning of the training data. The image and label data are returned\nas a multi-dimensional numpy array and one-hot encoded numpy vector, respectively.\n\n```python\n# create the generator\ng = images.stratify\n# feed in steps number of stratified mini-batches\nfor _ in range(steps):\n x_batch, y_batch = g\n```\n\nIf the pixel data type is uint8 (or uint16), the pixel data will be normalized dynamically per creation of a stratified mini-batch generator..\n\nIf the `config` setting `stream` is specified, each batch of pixel data is streamed (memory-less) in from the HDF5 storage per invocation of the `minibatch` property when used as a getter.\n\n\n### Image Augmentation\n\nImage augmentation (synthesis of new images) occurs dynamically in-place when feeding a neural network, and is initiated through the parameter `augment` when instantiating an\n`Images` class object. The settings for the `augment` parameter are:\n\n rotate=min,max : random rotation of image within range min and max.\n zoom=factor : zoom factor of n (i.e., 1.5 = 150%).\n flip=horizontal|vertical|both : flip image on horizontal, vertical or both axes.\n brightness=factor : brighten image by factor\n contrast=factor : contrast image by factor\n edge : sharpen the image\n denoise : apply de-noising filter\n \nBelow is an example of specifying image augmentation during feeding of a neural network:\n\n```python\nimages = Images(..., augment=['rotate=-90,90', 'flip=vertical'])\n```\n\nImage augmentation occurs dynamically during feeding. For each image feed, a second augmented image will follow. For example, if the training set is 1000 images, the next() operator will\nfeed 2000 images per epoch. If the mini-batch or stratify size is set to 32, the corresponding generators will feed 64 images. If multiple image augmentation settings are specified, a \nrandom selection is made of the type of augmentation per image. For example, if one specifies rotation, zoom and horizontal flip, then each time an image is augmented a random choice is\nmade between the three.\n\n### Managing Datasets (Persistent Storage)\n\nImage datasets which have been transformed into machine learning ready data can be stored and managed in persistent storage, using the HDF5 filesystem format. The following can be done:\n\n 1. Save transformed images into storage (bulk or streamed).\n 2. Load transformed images from storage (bulk or streamed).\n 3. Apply new transformations (e.g., convert to gray scale, flatten, change size, etc).\n 4. Combine collections (classes) of images.\n \n*Save to Persistent Storage*\n\nA transformed image dataset (i.e., collection) can be saved to, and subsequently retrieved from, persistent storage with the `config` setting `store`. When specified, the transformed\n(machine learning ready data) image dataset, along with associated metadata, is stored to HDF5 storage. Within the HDF5 storage, each class (label) of data is compacted and indexed into a contiguous volume within the HDF5 storage for subsequent fast retrieval.\n\n```python\n# store the transformed image dataset into HDF5 storage\nimages = Images(..., config=['store'])\n```\n\nIf the image dataset is too large to hold the entire dataset in memory, the images can alternatively be processed one at a time and streamed into the HDF5 storage. In this mode, the\nprocess only consumes memory resources for a single image. The stream mode is invoked when the `config` setting `stream` is specified.\n\n```python\n# stream (store) the transformed image dataset into HDF5 storage\nimages = Images(..., config=['stream'])\n```\n\n*Load from Persistent Storage*\n\nThe `load()` method of the `Images` class will retrieve (load) a collection of transformed (machine learning ready data) images, and associated metadata, from persistent storage. Once loaded, the collection can then be feed to a neural network for training.\n\n```python\n# load a previously preprocessed collection of images entirely in-memory\nimages = Images()\nimages.load('cats_n_dogs')\n\n# feed into a neural network\nimages.split = 0.1, 112\nX_train, X_test, Y_train, Y_test = images.split\n\n# alternately, stream a previously preprocessed collection of images\nimages = Images(config=['stream'])\nimages.load('cats_n_dogs')\n\n# feed (streaming) into a neural network\nimages.minibatch = 32\ng = images.minibatch\nfor step in range(steps):\n x_batch, y_batch = g\n```\n\n*Apply Transforms*\n\nAfter a transformed image dataset has been loaded from persistent storage, one can further re-transform the dataset to match the input requirements of another neural network, without reprocessing the original image data. The re-transforms are supported as setter properties of the `Images` class:\n\n - `gray` : Converting to Grayscale\n - 'flatten`: Flattening\n - 'resize` : Resizing\n \n```python\n# load a previously preprocessed collection of images\nimages.load('cats_n_dogs')\n# resize the transformed images to 96 x 96 (height vs. width)\nimages.resize = (96, 96)\n```\n \n*Combining Collections*\n\nExisting collections in persistent storage can be combined into a single new collection using the overridden `+=` operator. When combined, the label assignment is reassigned. For example,\nif both collections are a single class with both having the respective value 0 for the class, in the combined version, one class will be 0 and the other 1.\n\n```python\ncats = Images(name='cats', dataset=..., ...)\nprint(cats.class) # will output: {'cats': 0}\n\ndogs = Images(name='dogs', dataset=..., ...)\nprint(dogs.class) # will output: {'dogs': 0}\n\ncats += dogs\nprint(cats.class) # will output: {'cats': 0, 'dogs': 1}\n```\n\n## Reference\n\n## Testing\n\nThe **Gap** framework is developed using Test Driven Development methodology. The automated unit tests for the framework use pytest, which is a xUnit style form of testing (e.g., jUnit, nUnit, jsUnit, etc).\n\n#### Installation and Documentation\n\nThe pytest application can be installed using pip:\n\n pip install pytest\n\nOnline documentation for [pytest](https://docs.pytest.org)\n\n#### Execution\n\nThe following are the pre-built automated unit tests, which are located under the subdirectory tests:\n\n image_test.py # Tests the Image and Images Class in the Vision Module\n\nThe automated tests are executed as follows:\n \n 1. From directory root enter `cd tests`\n\n 2. Tests can be run by:\n\n pytest -v image_test.py\n\n#### Code Coverage\n\nInformation on the percent of code that is covered (and what source lines not covered) by the automated tests is obtained using pytest-cov. This version of pytest is installed using pip:\n\n pip install pytest-cov\n\n 1. From directory root enter `cd tests`\n\n 2. To run tests with coverage: \n\n pytest --cov=gapcv.vision image_test.py\n\n Statements=1364, Missed=59, Percent Covered: 96%", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/gapml/CV", "keywords": "", "license": "Apache 2.0", "maintainer": "", "maintainer_email": "", "name": "gapcv", "package_url": "https://pypi.org/project/gapcv/", "platform": "", "project_url": "https://pypi.org/project/gapcv/", "project_urls": { "Documentation": "https://gapml.github.io/CV/", "Homepage": "https://github.com/gapml/CV", "Source Code": "https://github.com/gapml/CV" }, "release_url": "https://pypi.org/project/gapcv/1.0.0/", "requires_dist": null, "requires_python": "", "summary": "CV Data Engineering Framework", "version": "1.0.0" }, "last_serial": 5293586, "releases": { "0.9.5": [ { "comment_text": "", "digests": { "md5": "c0df1b36f8d57c972bab085ab9d0d8cd", "sha256": "d8c8a983f18cf965c4c4de6e2f9e81e79c3b72836486429963e553b427077633" }, "downloads": -1, "filename": "gapcv-0.9.5.tar.gz", "has_sig": false, "md5_digest": "c0df1b36f8d57c972bab085ab9d0d8cd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16407, "upload_time": "2018-10-24T00:27:58", "url": "https://files.pythonhosted.org/packages/eb/3b/164f1505d3dd104fe02dac01cffaa07c8b1e96cefa450dabcf161a74fc58/gapcv-0.9.5.tar.gz" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "ee589ec3f60c6d6048f09632ffa05309", "sha256": "c96b50f208aa89833a5d8bd46dc99798621089b39837c31c96edd007bc4b1413" }, "downloads": -1, "filename": "gapcv-1.0.0.tar.gz", "has_sig": false, "md5_digest": "ee589ec3f60c6d6048f09632ffa05309", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 37429, "upload_time": "2019-05-20T18:10:10", "url": "https://files.pythonhosted.org/packages/4b/57/46b0539ace5d2b7f7c1ce2c24a37c8603ea9624462e815d26d191388eed1/gapcv-1.0.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "ee589ec3f60c6d6048f09632ffa05309", "sha256": "c96b50f208aa89833a5d8bd46dc99798621089b39837c31c96edd007bc4b1413" }, "downloads": -1, "filename": "gapcv-1.0.0.tar.gz", "has_sig": false, "md5_digest": "ee589ec3f60c6d6048f09632ffa05309", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 37429, "upload_time": "2019-05-20T18:10:10", "url": "https://files.pythonhosted.org/packages/4b/57/46b0539ace5d2b7f7c1ce2c24a37c8603ea9624462e815d26d191388eed1/gapcv-1.0.0.tar.gz" } ] }