Skip to content

Commit c7a51ff

Browse files
committed
Use fsspec urls for opening the darknet network
1 parent 723d8be commit c7a51ff

File tree

10 files changed

+172
-120
lines changed

10 files changed

+172
-120
lines changed

environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies:
3131
# Test Requirements (setup.py:test_requirements)
3232
- pytest >=3
3333
- pytest-cov
34+
- pytest-mock
3435

3536
# Documentation Requirements (setup.py:doc_requirements)
3637
- sphinx

examples/coco.ipynb

Lines changed: 21 additions & 31 deletions
Large diffs are not rendered by default.

examples/imagenet1k-intake.ipynb

Lines changed: 48 additions & 54 deletions
Large diffs are not rendered by default.

examples/imagenet1k.ipynb

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"cells": [
33
{
44
"cell_type": "code",
5-
"execution_count": 10,
5+
"execution_count": 1,
66
"metadata": {
77
"collapsed": true
88
},
@@ -18,27 +18,25 @@
1818
},
1919
{
2020
"cell_type": "code",
21-
"execution_count": 3,
21+
"execution_count": 2,
2222
"metadata": {
2323
"pycharm": {
2424
"name": "#%%\n"
2525
}
2626
},
2727
"outputs": [],
2828
"source": [
29-
"github_opts = dict(org=\"pjreddie\", repo=\"darknet\", sha=\"master\")\n",
30-
"darknet_gh_url = \"github://{org}:{repo}@master\".format(**github_opts)\n",
31-
"filecache_opts = dict(cache_storage=f\"{os.environ['HOME']}/.cache/darknet.py\")"
29+
"darknet_gh_url = \"github://pjreddie:darknet@master\""
3230
]
3331
},
3432
{
3533
"cell_type": "code",
36-
"execution_count": 4,
34+
"execution_count": 3,
3735
"outputs": [],
3836
"source": [
3937
"# Load the ImageNet 1k labels/metadata\n",
40-
"with fsspec.open(f\"{darknet_gh_url}/data/imagenet.shortnames.list\") as f:\n",
41-
" labels = [line.decode().rstrip() for line in f.readlines()[:1000]]"
38+
"with fsspec.open(f\"{darknet_gh_url}/data/imagenet.shortnames.list\", mode=\"rt\") as f:\n",
39+
" labels = [line.rstrip() for line in f.readlines()[:1000]]"
4240
],
4341
"metadata": {
4442
"collapsed": false,
@@ -49,29 +47,22 @@
4947
},
5048
{
5149
"cell_type": "code",
52-
"execution_count": 6,
50+
"execution_count": 5,
5351
"metadata": {
5452
"pycharm": {
5553
"name": "#%%\n"
5654
}
5755
},
5856
"outputs": [],
5957
"source": [
60-
"config_of = fsspec.open(f\"filecache::github://cfg/darknet53_448.cfg\",\n",
61-
" filecache=filecache_opts, github=github_opts)\n",
62-
"weights_of = fsspec.open(\"filecache::https://pjreddie.com/media/files/darknet53_448.weights\",\n",
63-
" filecache=filecache_opts)\n",
64-
"with config_of as config:\n",
65-
" with weights_of as weights:\n",
66-
" # Load the Classifier\n",
67-
" n = ImageClassifier(labels=labels,\n",
68-
" config_file=config.name,\n",
69-
" weights_file=weights.name)"
58+
"n = ImageClassifier(labels=labels,\n",
59+
" config_url=f\"{darknet_gh_url}/cfg/darknet53_448.cfg\",\n",
60+
" weights_url=\"https://pjreddie.com/media/files/darknet53_448.weights\")"
7061
]
7162
},
7263
{
7364
"cell_type": "code",
74-
"execution_count": 7,
65+
"execution_count": 6,
7566
"metadata": {
7667
"pycharm": {
7768
"name": "#%%\n"
@@ -82,7 +73,7 @@
8273
"data": {
8374
"text/plain": "[('malamute', 0.98354006),\n ('Eskimo dog', 0.0042837244),\n ('Siberian husky', 0.0031863458),\n ('Tibetan mastiff', 0.0030448402),\n ('Great Pyrenees', 0.0022190544)]"
8475
},
85-
"execution_count": 7,
76+
"execution_count": 6,
8677
"metadata": {},
8778
"output_type": "execute_result"
8879
}
@@ -94,13 +85,13 @@
9485
},
9586
{
9687
"cell_type": "code",
97-
"execution_count": 12,
88+
"execution_count": 7,
9889
"outputs": [
9990
{
10091
"data": {
10192
"text/plain": "[('malamute', 0.98354006),\n ('Eskimo dog', 0.0042837244),\n ('Siberian husky', 0.0031863458),\n ('Tibetan mastiff', 0.0030448402),\n ('Great Pyrenees', 0.0022190544)]"
10293
},
103-
"execution_count": 12,
94+
"execution_count": 7,
10495
"metadata": {},
10596
"output_type": "execute_result"
10697
}
@@ -119,7 +110,7 @@
119110
},
120111
{
121112
"cell_type": "code",
122-
"execution_count": 13,
113+
"execution_count": 8,
123114
"metadata": {
124115
"pycharm": {
125116
"name": "#%%\n"
@@ -130,7 +121,7 @@
130121
"data": {
131122
"text/plain": "[('malamute', 0.98354006),\n ('Eskimo dog', 0.0042837244),\n ('Siberian husky', 0.0031863458),\n ('Tibetan mastiff', 0.0030448402),\n ('Great Pyrenees', 0.0022190544)]"
132123
},
133-
"execution_count": 13,
124+
"execution_count": 8,
134125
"metadata": {},
135126
"output_type": "execute_result"
136127
}
@@ -150,7 +141,7 @@
150141
},
151142
{
152143
"cell_type": "code",
153-
"execution_count": 14,
144+
"execution_count": 9,
154145
"metadata": {
155146
"pycharm": {
156147
"name": "#%%\n"
@@ -161,7 +152,7 @@
161152
"data": {
162153
"text/plain": "[('bald eagle', 0.55666465),\n ('vulture', 0.21876547),\n ('kite', 0.18937683),\n ('ruddy turnstone', 0.004589723),\n ('ruffed grouse', 0.0032499917)]"
163154
},
164-
"execution_count": 14,
155+
"execution_count": 9,
165156
"metadata": {},
166157
"output_type": "execute_result"
167158
}
@@ -172,7 +163,7 @@
172163
},
173164
{
174165
"cell_type": "code",
175-
"execution_count": 15,
166+
"execution_count": 10,
176167
"metadata": {
177168
"pycharm": {
178169
"name": "#%%\n"
@@ -183,7 +174,7 @@
183174
"data": {
184175
"text/plain": "[('electric guitar', 0.98759043),\n ('acoustic guitar', 0.009553942),\n ('banjo', 0.0011607071),\n ('pick', 0.0007309786),\n ('stage', 0.00058993115)]"
185176
},
186-
"execution_count": 15,
177+
"execution_count": 10,
187178
"metadata": {},
188179
"output_type": "execute_result"
189180
}

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
# fmt: off
3636
"pytest>=3",
3737
"pytest-cov",
38+
"pytest-mock",
3839
# fmt: on
3940
]
4041

src/darknet/py/classifier.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ class ClassifierBase(ABC):
1010
network: Network
1111
labels: list
1212

13-
def __init__(self, labels, config_file, weights_file, **kwargs):
14-
self.network = Network(config_file, weights_file, **kwargs)
13+
def __init__(self, labels, config_url, weights_url, **kwargs):
14+
self.network = Network.open(config_url, weights_url, **kwargs)
1515

1616
self.labels = range(self.network.output_size()) if labels is None else labels
1717
if len(self.labels) != self.network.output_size():

src/darknet/py/detector.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ class ImageDetector(object):
88

99
_last_image_size = None
1010

11-
def __init__(self, labels, config_file, weights_file, **kwargs):
12-
self.network = Network(config_file, weights_file, **kwargs)
11+
def __init__(self, labels, config_url, weights_url, **kwargs):
12+
self.network = Network.open(config_url, weights_url, **kwargs)
1313
self.labels = labels
1414

1515
def detect(self, image, **kwargs):

src/darknet/py/network.pyx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ cimport numpy as np
66
cimport libdarknet as dn
77

88
from libc.stdlib cimport free
9+
from .util import fsspec_cache_open
910

1011
np.import_array()
1112

@@ -24,6 +25,13 @@ cdef class Metadata:
2425
cdef class Network:
2526
cdef dn.network* _c_network
2627

28+
@staticmethod
29+
def open(config_url, weights_url):
30+
with fsspec_cache_open(config_url, mode="rt") as config:
31+
with fsspec_cache_open(weights_url, mode="rb") as weights:
32+
return Network(config.name, weights.name)
33+
34+
2735
def __cinit__(self, config_file, weights_file):
2836
clear = 1
2937
self._c_network = dn.load_network(config_file.encode(), weights_file.encode(), clear)
@@ -96,7 +104,7 @@ cdef class Network:
96104
elif nms_type == "sort":
97105
dn.do_nms_sort(detections, num_dets, detections[0].classes, nms_threshold)
98106
else:
99-
raise ValueError(f"non-maximum-supression type {nms_type} is not one of {['obj', 'sort']}")
107+
raise ValueError(f"non-maximum-suppression type {nms_type} is not one of {['obj', 'sort']}")
100108

101109
rv = [
102110
(j, detections[i].prob[j],

src/darknet/py/util.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,55 @@
1+
import os
2+
import fsspec
3+
14
import numpy as np
25
from PIL import Image
36
from PIL import ImageDraw
47

5-
import fsspec
8+
9+
def fsspec_cache_open(urlpath: str,
10+
mode="rb",
11+
compression=None,
12+
encoding="utf8",
13+
errors=None,
14+
protocol=None,
15+
newline=None,
16+
**kwargs) -> fsspec.core.OpenFile:
17+
chain = urlpath.split("::")
18+
19+
if chain[0].startswith("github"):
20+
chain[0], kwargs = fsspec_split_github_url(chain[0], kwargs)
21+
22+
# Because darknet is written in C, we need real file names for it to open
23+
if chain[0] not in {"filecache", "simplecache"}:
24+
first_scheme = chain[0].split("://")[0]
25+
urlpath = f"filecache::{urlpath}"
26+
filecache = dict(cache_storage=f"{os.environ['HOME']}/.cache/darknet.py")
27+
kwargs = {
28+
"filecache": filecache,
29+
first_scheme: kwargs
30+
}
31+
32+
return fsspec.open(urlpath, mode, compression, encoding, errors, protocol, newline, **kwargs)
33+
34+
35+
def fsspec_split_github_url(github_url: str, kwargs: dict) -> (str, dict):
36+
# TODO: Remove this once fsspec > 0.7.5
37+
from urllib.parse import urlparse
38+
39+
rv = github_url
40+
github_url = urlparse(github_url)
41+
keys = {"org", "repo", "sha"}
42+
# If that metadata is not passed as kwargs, we need to extract it
43+
# netloc = "{org}:{repo}@{sha}"
44+
kwargs = kwargs or dict()
45+
if (keys & kwargs.keys()) != keys:
46+
org, repo, sha = github_url.username, github_url.password, github_url.hostname
47+
if org is None or repo is None or sha is None:
48+
raise ValueError(f"The github url {github_url} does not match `github://<org>:<repo>@<sha>/path`")
49+
kwargs.update(dict(org=org, repo=repo, sha=sha))
50+
rv = github_url.geturl().replace(f"{github_url.netloc}/", "")
51+
52+
return rv, kwargs
653

754

855
def image_to_3darray(image, target_shape):

tests/unit/test_util.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import fsspec
2+
import darknet.py.util as darknet_util
3+
4+
def test_fsspec_split_github_url():
5+
url, kwargs = darknet_util.fsspec_split_github_url("github://org:repo@sha/path/to/file", None)
6+
assert url == "github://path/to/file"
7+
assert kwargs["org"] == "org"
8+
assert kwargs["repo"] == "repo"
9+
assert kwargs["sha"] == "sha"
10+
11+
12+
def test_fsspec_cache_open(mocker):
13+
split_spy = mocker.spy(darknet_util, "fsspec_split_github_url")
14+
fsspec_open_mock = mocker.patch.object(fsspec, "open", autospec=True)
15+
16+
of = darknet_util.fsspec_cache_open("github://org:repo@sha/path/to/file")
17+
assert fsspec_open_mock.return_value == of
18+
19+
fsspec_open_mock.assert_called_once()
20+
split_spy.assert_called_once()

0 commit comments

Comments
 (0)