diff --git a/Project.toml b/Project.toml index 9da2845c93..b2ad342f8d 100644 --- a/Project.toml +++ b/Project.toml @@ -5,7 +5,6 @@ version = "0.4.3" [deps] Animations = "27a7e980-b3e6-11e9-2bcd-0b925532e340" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" BSON = "fbb218c0-5317-5bc6-957e-2ee96dd4b1f0" CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" ColorVectorSpace = "c3611d14-8923-5661-9e6a-0046d554d3a4" @@ -14,14 +13,15 @@ DataAugmentation = "88a5189c-e7ff-4f85-ac6b-e6158070f02e" DataDeps = "124859b0-ceae-595e-8997-d05f6a7a8dfe" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DataLoaders = "2e981812-ef13-4a9c-bfa0-ab13047b12a9" +FeatureRegistries = "c6aefb4f-3ac3-4095-8805-528476b02c02" FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" FilePathsBase = "48062228-2e41-5def-b9a4-89aafe57970f" FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c" FluxTraining = "7bf95e4d-ca32-48da-9824-f0dc5310474f" Glob = "c27321d9-0574-5035-807b-f59d2c89b15c" -ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254" ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19" +ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254" IndirectArrays = "9b13fd28-a010-5f03-acff-a1bbcff69959" InlineTest = "bd334432-b1e7-49c7-a2dc-dd9149e4ebd6" JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" @@ -32,6 +32,7 @@ MosaicViews = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" Requires = "ae029012-a4dd-5104-9daa-d747884805df" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" @@ -53,14 +54,15 @@ DataAugmentation = "0.2.4" DataDeps = "0.7" DataFrames = "1" DataLoaders = "0.1" +FeatureRegistries = "0.1" FileIO = "1.7" FilePathsBase = "0.9" FixedPointNumbers = "0.8" Flux = "0.12, 0.13" FluxTraining = "0.2, 0.3" Glob = "1" -ImageInTerminal = "0.4" ImageIO = "0.6" +ImageInTerminal = "0.4" IndirectArrays = "0.5, 1" InlineTest = "0.2" JLD2 = "0.4" diff --git a/README.md b/README.md index a8fbb07624..e152bf365b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ As an example, here is how to train an image classification model: ```julia using FastAI -data, blocks = loaddataset("imagenette2-160", (Image, Label)) +data, blocks = load(datarecipes()["imagenette2-160"]) task = ImageClassificationSingle(blocks) learner = tasklearner(task, data, callbacks=[ToGPU()]) fitonecycle!(learner, 10) diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 91e6ff6b6c..0000000000 --- a/docs/api.md +++ /dev/null @@ -1,63 +0,0 @@ -# FastAI.jl interfaces - -## Training - -### High-level - -Quickly get started training and finetuning models using already implemented learning tasks and callbacks. - -{.tight} -- [`tasklearner`](#) -- [`fit!`](#) -- [`fitonecycle!`](#) -- [`finetune!`](#) -- [`BlockTask`](#) -- callbacks - -### Mid-level - -{.tight} -- [`Learner`](#) -- [`taskmodel`](#) -- [`tasklossfn`](#) - -### Low-level - -{.tight} -- [`LearningTask`](#) -- [`encode`](#) -- [`encodeinput`](#) -- `decodey` - -## Datasets - -### High-level - -Quickly download and load task data containers from the fastai dataset library. - -{.tight} -- `load -- [`FastAI.Datasets.DATASETS`](#) - -### Mid-level - -Load and transform data containers. - -{.tight} -- [`FastAI.Datasets.datasetpath`](#) -- [`FastAI.Datasets.FileDataset`](#) -- [`FastAI.Datasets.TableDataset`](#) -- [`mapobs`](#) -- [`groupobs`](#) -- [`joinobs`](#) -- [`groupobs`](#) - -### Low-level - -Full control over data containers. - -{.tight} -- [`LearnBase.getobs`](#) -- [`LearnBase.nobs`](#) - - diff --git a/docs/background/blocksencodings.md b/docs/background/blocksencodings.md index f4d1ff3508..bcc13a2c53 100644 --- a/docs/background/blocksencodings.md +++ b/docs/background/blocksencodings.md @@ -101,9 +101,8 @@ task = BlockTask( Now `encode` expects a sample and just runs the encodings over that, giving us an encoded input `x` and an encoded target `y`. -{cell=main} ```julia -data = loadfolderdata(joinpath(datasetpath("dogscats"), "train"), filterfn=isimagefile, loadfn=(loadfile, parentname)) +data = loadfolderdata(joinpath(load(datasets()["dogscats"]), "train"), filterfn=isimagefile, loadfn=(loadfile, parentname)) sample = getobs(data, 1) x, y = encodesample(task, Training(), sample) summary(x), summary(y) @@ -111,7 +110,6 @@ summary(x), summary(y) This is equivalent to: -{cell=main} ```julia x, y = encode(task.encodings, Training(), FastAI.getblocks(task).sample, sample) summary(x), summary(y) @@ -119,7 +117,6 @@ summary(x), summary(y) Image segmentation looks almost the same except we use a `Mask` block as target. We're also using `OneHot` here, because it also has an `encode` task for `Mask`s. For this task, `ProjectiveTransforms` will be applied to both the `Image` and the `Mask`, using the same random state for cropping and augmentation. -{cell=main} ```julia task = BlockTask( (Image{2}(), Mask{2}(1:10)), @@ -133,19 +130,16 @@ task = BlockTask( The easiest way to understand how encodings are applied to each block is to use [`describetask`](#) and [`describeencodings`](#) which print a table of how each encoding is applied successively to each block. Rows where a block is **bolded** indicate that the data was transformed by that encoding. -{cell=main} ```julia describetask(task) ``` The above tables make it clear what happens during training ("encoding a sample") and inference (encoding an input and "decoding an output"). The more general form [`describeencodings`](#) takes in encodings and blocks directly and can be useful for building an understanding of how encodings apply to some blocks. -{cell=main} ```julia FastAI.describeencodings(task.encodings, (Image{2}(),)) ``` -{cell=main} ```julia FastAI.describeencodings((OneHot(),), (Label(1:10), Mask{2}(1:10), Image{2}())) ``` diff --git a/docs/background/datapipelines.md b/docs/background/datapipelines.md index cbdf7360ac..f3d14893e2 100644 --- a/docs/background/datapipelines.md +++ b/docs/background/datapipelines.md @@ -26,8 +26,8 @@ using DataLoaders: batchviewcollated using FastAI using FastAI.Datasets -data = loadtaskdata(datasetpath("imagenette2-320"), ImageClassification) -task = ImageClassification(Datasets.getclassesclassification("imagenette2-320"), (224, 224)) +data, blocks = load(datarecipes()["imagenette2-320"]) +task = ImageClassificationSingle(blocks, size=(224, 224)) # maps data processing over `data` taskdata = taskdataset(data, task, Training()) @@ -68,7 +68,8 @@ using FastAI using FastAI.Datasets using FluxTraining: step! -data = loaddataset("imagenette2-320", (Image, Label)) + +data, blocks = load(datarecipes()["imagenette2-320"]) task = ImageClassificationSingle(blocks) learner = tasklearner(task, data) @@ -130,13 +131,14 @@ If the data loading is still slowing down training, you'll probably have to spee For many computer vision tasks, you will resize and crop images to a specific size during training for GPU performance reasons. If the images themselves are large, loading them from disk itself can take some time. If your dataset consists of 1920x1080 resolution images but you're resizing them to 256x256 during training, you're wasting a lot of time loading the large images. *Presizing* means saving resized versions of each image to disk once, and then loading these smaller versions during training. We can see the performance difference using ImageNette since it comes in 3 sizes: original, 360px and 180px. ```julia -data_orig, _ = loaddataset("imagenette2", (Image, Label)) + +data_orig = load(datarecipes()["imagenette2"]) @time eachobsparallel(data_orig, buffered = false) -data_320px, _ = loaddataset("imagenette2-320", (Image, Label)) +data_320px = load(datarecipes()["imagenette2-320"]) @time eachobsparallel(data_320px, buffered = false) -data_160px, _ = loaddataset("imagenette2-160", (Image, Label)) +data_160px = load(datarecipes()["imagenette2-160"]) @time eachobsparallel(data_160px, buffered = false) ``` diff --git a/docs/data_containers.md b/docs/data_containers.md index 069462dcc3..30bb26b504 100644 --- a/docs/data_containers.md +++ b/docs/data_containers.md @@ -13,8 +13,7 @@ ENV["DATADEPS_ALWAYS_ACCEPT"] = "true" {cell=main, output=false} ```julia using FastAI -import FastAI: Image -data, _ = loaddataset("imagenette2-160", (Image, Label)) +data, _ = load(findfirst(datarecipes(datasetid="imagenette2-160"))) ``` A data container is any type that holds observations of data and allows us to load them with `getobs` and query the number of observations with `nobs`. In this case, each observation is a tuple of an image and the corresponding class; after all, we want to use it for image classification. @@ -31,15 +30,15 @@ image nobs(data) ``` -[`loaddataset`](#) makes it easy to a load a data container that is compatible with some block types, but to get a better feel for what it does, let's look under the hood by creating the same data container using some mid-level APIs. +`load(`[`datasets`](#)`[id])` makes it easy to a load a data container that is compatible with some block types, but to get a better feel for what it does, let's look under the hood by creating the same data container using some mid-level APIs. ## Creating data containers from files -Before we recreate the data container, [`datasetpath`](#) downloads a dataset and returns the path to the extracted files. +Before we recreate the data container, we'll download the dataset and get the path where the files are saved to: {cell=main} ```julia -dir = datasetpath("imagenette2-160") +dir = load(datasets()["imagenette2-160"]) ``` Now we'll start with [`FileDataset`](#) which creates a data container (here a `Vector`) of files given a path. We'll use the path of the downloaded dataset: @@ -127,12 +126,16 @@ Using this official split, it will be easier to compare the performance of your ## Dataset recipes -We saw above how different image classification datasets can be loaded with the same logic as long as they are in a common format. To encapsulate the logic for loading common dataset formats, FastAI.jl has `DatasetRecipe`s. When we used [`finddatasets`](#) in the [discovery tutorial](discovery.md), it returned pairs of a dataset name and a `DatasetRecipe`. For example, `"imagenette2-160"` has an associated [`ImageFolders`](#) recipe and we can load it using [`loadrecipe`] and the path to the downloaded dataset: +We saw above how different image classification datasets can be loaded with the same logic as long as they are in a common format. To encapsulate the logic for loading common dataset formats, FastAI.jl has [`DatasetRecipe`](#)s. When we used [`datarecipes`](#) in the [discovery tutorial](discovery.md), it showed us such recipes that allow loading a dataset for a specific task. For example, `"imagenette2-160"` has an associated [`ImageFolders`](#) recipe which we can load by getting the entry and calling `load` on it: {cell=main} ```julia -name, recipe = finddatasets(blocks=(Image, Label), name="imagenette2-160")[1] -data, blocks = loadrecipe(recipe, datasetpath(name)) +entry = datarecipes()["imagenette2-160"] +``` + +{cell=main} +```julia +data, blocks = load(entry) ``` These recipes also take care of loading the data block information for the dataset. Read the [discovery tutorial](discovery.md) to find out more about that. \ No newline at end of file diff --git a/docs/discovery.md b/docs/discovery.md index 4e6fd682cd..598d4dd5b7 100644 --- a/docs/discovery.md +++ b/docs/discovery.md @@ -6,16 +6,16 @@ For finding both, we can make use of `Block`s. A `Block` represents a kind of da ## Finding a dataset -To find a dataset with compatible samples, we can pass the types of these blocks to [`finddatasets`](#) which will return a list of dataset names and recipes to load them in a suitable way. +To find a dataset with compatible samples, we can pass the types of these blocks as a filter to [`datasets`](#) which will show us only dataset recipes for loading those blocks. {cell=main} ```julia using FastAI import FastAI: Image -finddatasets(blocks=(Image, Mask)) +datarecipes(blocks=(Image, Mask)) ``` -We can see that the `"camvid_tiny"` dataset can be loaded so that each sample is a pair of an image and a segmentation mask. Let's use [`loaddataset`](#) to load a [data container](data_containers.md) and concrete blocks. +We can see that the `"camvid_tiny"` dataset can be loaded so that each sample is a pair of an image and a segmentation mask. Let's use a data recipe to load a [data container](data_containers.md) and concrete blocks. {cell=main, result=false, output=false style="display:none;"} ```julia @@ -24,7 +24,7 @@ ENV["DATADEPS_ALWAYS_ACCEPT"] = "true" {cell=main, output=false} ```julia -data, blocks = loaddataset("camvid_tiny", (Image, Mask)) +data, blocks = load(findfirst(datarecipes(id="camvid_tiny", blocks=(Image, Mask)))) ``` As with every data container, we can load a sample using `getobs` which gives us a tuple of an image and a segmentation mask. @@ -35,7 +35,7 @@ image, mask = sample = getobs(data, 1) size.(sample), eltype.(sample) ``` -`loaddataset` also returned `blocks` which are the concrete `Block` instances for the dataset. We passed in _types_ of blocks (`(Image, Mask)`) and get back _instances_ since the specifics of some blocks depend on the dataset. For example, the returned target block carries the labels for every class that a pixel can belong to. +Loading the dataset recipe also returned `blocks`, which are the concrete [`Block`] instances for the dataset. We passed in _types_ of blocks (`(Image, Mask)`) and get back _instances_ since the specifics of some blocks depend on the dataset. For example, the returned target block carries the labels for every class that a pixel can belong to. {cell=main} ```julia @@ -55,8 +55,8 @@ checkblock((inputblock, targetblock), (image, mask)) In short, if you have a learning task in mind and want to load a dataset for that task, then 1. define the types of input and target block, e.g. `blocktypes = (Image, Label)`, -2. use [`finddatasets`](#)`(blocks=blocktypes)` to find compatbile datasets; and -3. run [`loaddataset`](#)`(datasetname, blocktypes)` to load a data container and the concrete blocks +2. use `filter(`[`datarecipes`](#)`(), blocks=blocktypes)` to find compatbile dataset recipes; and +3. run `load(`[`datarecipes`](#)`()[id])` to load a data container and the concrete blocks ### Exercises @@ -66,14 +66,14 @@ In short, if you have a learning task in mind and want to load a dataset for tha ## Finding a learning task -Armed with a dataset, we can go to the next step: creating a learning task. Since we already have blocks defined, this amounts to defining the encodings that are applied to the data before it is used in training. Here, FastAI.jl already defines some convenient constructors for learning tasks and you can find them with [`findlearningtasks`](#). Here we can pass in either block types as above or the block instances we got from `loaddataset`. +Armed with a dataset, we can go to the next step: creating a learning task. Since we already have blocks defined, this amounts to defining the encodings that are applied to the data before it is used in training. Here, FastAI.jl already defines some convenient constructors for learning tasks and you can find them with [`learningtasks`](#). Here we can pass in either block types as above or the block instances: {cell=main} ```julia -findlearningtasks(blocks) +learningtasks(blocks=blocks) ``` -Looks like we can use the [`ImageSegmentation`](#) function to create a learning task for our learning task. Every function returned can be called with `blocks` and, optionally, some keyword arguments for customization. +Looks like we can use the [`ImageSegmentation`](#) function to create a learning task. Every function returned can be called with `blocks` and, optionally, some keyword arguments for customization. {cell=main} ```julia diff --git a/docs/fastai_api_comparison.md b/docs/fastai_api_comparison.md index 7d1f6a1547..a0cbf0f52b 100644 --- a/docs/fastai_api_comparison.md +++ b/docs/fastai_api_comparison.md @@ -6,7 +6,7 @@ FastAI.jl is in many ways similar to the original Python [fastai](docs.fast.ai), FastAI.jl's own data block API makes it possible to derive every part of a high-level interface with a unified API across tasks. Instead it suffices to create a learning task and based on the blocks and encodings specified the proper model builder, loss function, and visualizations are implemented (see below). For a high-level API, a complete `Learner` can be constructed using [`tasklearner`](#) without much boilerplate. There are some helper functions for creating these learning tasks, for example [`ImageClassificationSingle`](#) and [`ImageSegmentation`](#). -FastAI.jl additionally has a unified API for registering and discovering functionality across applications also based on the data block abstraction. `finddatasets` and `loaddataset` let you quickly load common datasets matching some data modality and `findlearningtask` lets you find learning task helpers for common tasks. See [the discovery tutorial](discovery.md) for more info. +FastAI.jl additionally has a unified API for registering and discovering functionality across applications also based on the data block abstraction. [`datasets`](#) and [`datarecipes`](#) let you quickly load common datasets matching some data modality and [`learningtasks`] lets you find learning task helpers for common tasks. See [the discovery tutorial](discovery.md) for more info. ### Vision @@ -122,7 +122,7 @@ Metrics are handled by the [`Metrics`](#) callback which takes in reducing metri ### fastai.data.external -FastAI.jl makes all the same datasets available in `fastai.data.external` available. See `FastAI.Datasets.DATASETS` for a list of all datasets and use [`datasetpath`](#)`(name)` to download and extract a dataset. +FastAI.jl makes all the same datasets available in `fastai.data.external` available. See [`datasets`](#) for a list of all datasets that can be downloaded. ### funcs_kwargs and DataLoader, fastai.data.core diff --git a/docs/howto/augmentvision.md b/docs/howto/augmentvision.md index 2bc21f8af9..01a88d0151 100644 --- a/docs/howto/augmentvision.md +++ b/docs/howto/augmentvision.md @@ -15,7 +15,7 @@ using FastAI import FastAI: Image import CairoMakie; CairoMakie.activate!(type="png") -data, blocks = loaddataset("imagenette2-160", (Image, Label)) +data, blocks = load(datarecipes()["imagenette2-160"]) task = BlockTask( blocks, ( diff --git a/docs/howto/findfunctionality.md b/docs/howto/findfunctionality.md new file mode 100644 index 0000000000..091b0dc5f4 --- /dev/null +++ b/docs/howto/findfunctionality.md @@ -0,0 +1,46 @@ +# How to find functionality + +For some kinds of functionality, FastAI.jl provides feature registries that allow you to search for and use features. The following registries currently exist: + +- [`datasets`](#) to download and unpack datasets, +- [`datarecipes`](#) to load datasets into [data containers](/documents/docs/data_containers.md) that are compatible with a learning task; and +- [`learningtasks`](#) to find learning tasks that are compatible with a dataset + +To load functionality: + +1. Get an entry using its ID + {cell} + ```julia + using FastAI + entry = datasets()["mnist_var_size_tiny"] + ``` +2. And load it + {cell} + ```julia + load(entry) + ``` + + +## Datasets + +{cell} +```julia +using FastAI +datasets() +``` + +## Data recipes + +{cell} +```julia +using FastAI +datarecipes() +``` + +## Learning tasks + +{cell} +```julia +using FastAI +learningtasks() +``` diff --git a/docs/introduction.md b/docs/introduction.md index 8d445ffd27..4f18bc6546 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -12,7 +12,7 @@ On the [quickstart page](../notebooks/quickstart.ipynb), we showed how to train ```julia using FastAI -data, blocks = loaddataset("imagenette2-160", (Image, Label)) +data, blocks = load(datarecipes()["imagenette2-160"]) task = ImageClassificationSingle(blocks) learner = tasklearner(task, data, callbacks=[ToGPU()]) fitonecycle!(learner, 10) @@ -30,7 +30,7 @@ ENV["DATADEPS_ALWAYS_ACCEPT"] = "true" {cell=main, output=false} ```julia -data, blocks = loaddataset("imagenette2-160", (Image, Label)) +data, blocks = load(datarecipes()["imagenette2-160"]) ``` This line downloads and loads the [ImageNette](https://github.com/fastai/imagenette) image classification dataset, a small subset of ImageNet with 10 different classes. `data` is a [data container](data_containers.md) that can be used to load individual observations, here of images and the corresponding labels. We can use `getobs(data, i)` to load the `i`-th observation and `nobs` to find out how many observations there are. diff --git a/docs/learning_methods.md b/docs/learning_methods.md index dd9541090b..c8320777eb 100644 --- a/docs/learning_methods.md +++ b/docs/learning_methods.md @@ -21,7 +21,7 @@ Before we get started, let's load up a [data container](data_containers.md) that using FastAI, FastAI.DataAugmentation, Colors import FastAI: Image data = Datasets.loadfolderdata( - datasetpath("imagenette2-160"), + load(datasets()["imagenette2-160"]), filterfn=isimagefile, loadfn=(loadfile, parentname)) ``` @@ -172,7 +172,7 @@ model = Chain( Models.xresnet18(), Chain( AdaptiveMeanPool((1,1)), - flatten, + Flux.flatten, Dense(512, length(task.classes)), ) ) diff --git a/docs/project.jl b/docs/project.jl index 5c9f67bdb1..262426430f 100644 --- a/docs/project.jl +++ b/docs/project.jl @@ -1,6 +1,7 @@ using Pollen using Pkg using Crayons +using ImageShow Crayons.COLORS[:nothing] = 67 ENV["DATADEPS_ALWAYS_ACCEPT"] = "true" @@ -23,7 +24,7 @@ project = Project( StaticResources(), DocumentGraph(), SearchIndex(), - SaveAttributes((:title,)), + SaveAttributes((:title,), useoutputs=true), LoadFrontendConfig(Pkg.pkgdir(m)) ], ) diff --git a/docs/toc.json b/docs/toc.json index b6ac6bc880..99c8d92da9 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -15,6 +15,7 @@ }, "How-to": { "Train a model": "documents/notebooks/training.ipynb", + "Find functionality": "documents/docs/howto/findfunctionality.md", "Load and save models": "documents/notebooks/serialization.ipynb", "Log to TensorBoard": "documents/notebooks/training.ipynb" }, diff --git a/notebooks/registries.ipynb b/notebooks/registries.ipynb new file mode 100644 index 0000000000..2c9eaae543 --- /dev/null +++ b/notebooks/registries.ipynb @@ -0,0 +1,882 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 69, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "_show (generic function with 1 method)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "using FastAI, ImageShow\n", + "_show(reg) = show(IOContext(stdout, :displaysize => (60, 80)), reg)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Feature registries in FastAI.jl" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Datasets" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1mRegistry(Datasets, 67 entries)\u001b[0m\n", + "\n", + " \u001b[1m ID \u001b[0m\u001b[1m Name \u001b[0m\u001b[1m Size \u001b[0m\u001b[1m Is downloaded \u001b[0m\u001b[1m Tags \u001b[0m\u001b[1m Description \u001b[0m\u001b[1m Dataset loader \u001b[0m\u001b[1m Package \u001b[0m\n", + " \u001b[90m :id \u001b[0m\u001b[90m :name \u001b[0m\u001b[90m :size \u001b[0m\u001b[90m :downloaded \u001b[0m\u001b[90m :tags \u001b[0m\u001b[90m :description \u001b[0m\u001b[90m :loader \u001b[0m\u001b[90m :package \u001b[0m\n", + "\n", + " fastai/CUB_200_2011 \u001b[0m CUB_200_2011 \u001b[0m 1GiB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/bedroom \u001b[0m bedroom \u001b[0m 4.25GiB\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/caltech_101 \u001b[0m caltech_101 \u001b[0m 126MiB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/cifar10 \u001b[0m cifar10 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/cifar100 \u001b[0m cifar100 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/food-101 \u001b[0m food-101 \u001b[0m 5.3GB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m 101 food categories, with 101,000 ima… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagenette-160 \u001b[0m imagenette-160 \u001b[0m 1.45GiB\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m A subset of 10 easily classified clas… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagenette-320 \u001b[0m imagenette-320 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m A subset of 10 easily classified clas… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagenette \u001b[0m imagenette \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m A subset of 10 easily classified clas… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagenette2-160 \u001b[0m imagenette2-160 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[32m✔ \u001b[0m String[]\u001b[0m A subset of 10 easily classified clas… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagenette2-320 \u001b[0m imagenette2-320 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m A subset of 10 easily classified clas… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagenette2 \u001b[0m imagenette2 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m A subset of 10 easily classified clas… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagewang-160 \u001b[0m imagewang-160 \u001b[0m 182MiB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagewang-320 \u001b[0m imagewang-320 \u001b[0m 639MiB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagewang \u001b[0m imagewang \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagewoof-160 \u001b[0m imagewoof-160 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m A subset of 10 harder to classify cla… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagewoof-320 \u001b[0m imagewoof-320 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m A subset of 10 harder to classify cla… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagewoof \u001b[0m imagewoof \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m A subset of 10 harder to classify cla… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagewoof2-160 \u001b[0m imagewoof2-160 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[32m✔ \u001b[0m String[]\u001b[0m A subset of 10 harder to classify cla… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagewoof2-320 \u001b[0m imagewoof2-320 \u001b[0m 313MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m A subset of 10 harder to classify cla… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagewoof2 \u001b[0m imagewoof2 \u001b[0m 1.25GiB\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m A subset of 10 harder to classify cla… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/mnist_png \u001b[0m mnist_png \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/mnist_var_size_tiny \u001b[0m mnist_var_size_tiny \u001b[0m \u001b[90mmissing\u001b[0m \u001b[32m✔ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/oxford-102-flowers \u001b[0m oxford-102-flowers \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/oxford-iiit-pet \u001b[0m oxford-iiit-pet \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/stanford-cars \u001b[0m stanford-cars \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/ag_news_csv \u001b[0m ag_news_csv \u001b[0m 11MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/amazon_review_full_csv \u001b[0m amazon_review_full_csv \u001b[0m 600MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/amazon_review_polarity_csv \u001b[0m amazon_review_polarity_csv \u001b[0m 600MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/dbpedia_csv \u001b[0m dbpedia_csv \u001b[0m 65MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/giga-fren \u001b[0m giga-fren \u001b[0m 2.4GB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imdb \u001b[0m imdb \u001b[0m 140MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/sogou_news_csv \u001b[0m sogou_news_csv \u001b[0m 360MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/wikitext-103 \u001b[0m wikitext-103 \u001b[0m 181MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/wikitext-2 \u001b[0m wikitext-2 \u001b[0m 4MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/yahoo_answers_csv \u001b[0m yahoo_answers_csv \u001b[0m 305MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/yelp_review_full_csv \u001b[0m yelp_review_full_csv \u001b[0m 187MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/yelp_review_polarity_csv \u001b[0m yelp_review_polarity_csv \u001b[0m 158MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/biwi_head_pose \u001b[0m biwi_head_pose \u001b[0m 430MiB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/camvid \u001b[0m camvid \u001b[0m 571MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/pascal-voc \u001b[0m pascal-voc \u001b[0m 4.3GB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/pascal_2007 \u001b[0m pascal_2007 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/pascal_2012 \u001b[0m pascal_2012 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/siim_small \u001b[0m siim_small \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/skin-lesion \u001b[0m skin-lesion \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/tcga-small \u001b[0m tcga-small \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/adult_sample \u001b[0m adult_sample \u001b[0m 3.8MB \u001b[0m \u001b[32m✔ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/biwi_sample \u001b[0m biwi_sample \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/camvid_tiny \u001b[0m camvid_tiny \u001b[0m \u001b[90mmissing\u001b[0m \u001b[32m✔ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/dogscats \u001b[0m dogscats \u001b[0m 800MiB \u001b[0m \u001b[32m✔ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/human_numbers \u001b[0m human_numbers \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imdb_sample \u001b[0m imdb_sample \u001b[0m 4KB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/mnist_sample \u001b[0m mnist_sample \u001b[0m 3MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/mnist_tiny \u001b[0m mnist_tiny \u001b[0m 300KB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/movie_lens_sample \u001b[0m movie_lens_sample \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/planet_sample \u001b[0m planet_sample \u001b[0m 14.8MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/planet_tiny \u001b[0m planet_tiny \u001b[0m 1MB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/coco_sample \u001b[0m coco_sample \u001b[0m 3GB \u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/coco-train2017 \u001b[0m coco-train2017 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/coco-val2017 \u001b[0m coco-val2017 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/coco-test2017 \u001b[0m coco-test2017 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/coco-unlabeled2017 \u001b[0m coco-unlabeled2017 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/coco-image_info_test2017 \u001b[0m coco-image_info_test2017 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/coco-image_info_unlabeled2017 \u001b[0m coco-image_info_unlabeled2017 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/coco-annotations_trainval2017 \u001b[0m coco-annotations_trainval2017 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/coco-stuff_annotations_trainval2\u001b[0m… coco-stuff_annotations_trainval2017 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/coco-panoptic_annotations_trainv\u001b[0m… coco-panoptic_annotations_trainval2017\u001b[0m \u001b[90mmissing\u001b[0m \u001b[31m⨯ \u001b[0m String[]\u001b[0m missing DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n" + ] + } + ], + "source": [ + "datasets() |> _show" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can get more information on a specific dataset:" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RegistryEntry(\n", + "\u001b[1m \u001b[0m\u001b[1m \u001b[0m\u001b[1m \u001b[0m\u001b[1m \u001b[0m\n", + " id = fastai/imagewoof2-160 \u001b[0m \u001b[90m(String) \u001b[0m\n", + " \u001b[0m\n", + " name = imagewoof2-160 \u001b[0m \u001b[90m(String) \u001b[0m\n", + " \u001b[0m\n", + " size = \u001b[90mmissing \u001b[0m \u001b[90m(String) \u001b[0m\n", + " \u001b[0m\n", + " downloaded = \u001b[32m✔ \u001b[0m \u001b[90m(Bool) \u001b[0m\n", + " \u001b[0m\n", + " tags = String[] \u001b[0m \u001b[90m(Vector{String}) \u001b[0m\n", + " \u001b[0m\n", + " \u001b[0m\n", + " description = A subset of 10 harder to classify \u001b[0m \u001b[90m(String) \u001b[0m\n", + " classes from Imagenet (all dog \u001b[0m\n", + " breeds): Australian terrier, Border\u001b[0m\n", + " terrier, Samoyed, beagle, Shih-Tzu,\u001b[0m\n", + " English foxhound, Rhodesian \u001b[0m\n", + " ridgeback, dingo, golden retriever,\u001b[0m\n", + " Old English sheepdog \u001b[0m\n", + " \u001b[0m\n", + " loader = DataDepLoader(...) \u001b[0m \u001b[90m(FastAI.Datasets.DatasetLoader)\u001b[0m\n", + " package = FastAI \u001b[0m \u001b[90m(Module) \u001b[0m\n", + " \u001b[0m\n", + " \u001b[0m\n", + "\n", + ")" + ] + } + ], + "source": [ + "datasets()[\"fastai/imagewoof2-160\"] |> _show" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And load it, triggering a lazy download:" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"/Users/lorenz/.julia/datadeps/fastai-imagewoof2-160\"" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "load(datasets()[\"fastai/imagewoof2-160\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dataset recipes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Of course, to load datasets into a format that we can work with, FastAI.jl has so-called \"dataset recipes\"." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1mRegistry(Dataset recipes, 29 entries)\u001b[0m\n", + "\n", + " \u001b[1m ID \u001b[0m\u001b[1m Dataset ID \u001b[0m\u001b[1m Block types \u001b[0m\u001b[1m Description \u001b[0m\u001b[1m Is downloaded \u001b[0m\u001b[1m Package \u001b[0m\u001b[1m Recipe \u001b[0m\n", + " \u001b[90m :id \u001b[0m\u001b[90m :datasetid \u001b[0m\u001b[90m :blocks \u001b[0m\u001b[90m :description \u001b[0m\u001b[90m :downloaded \u001b[0m\u001b[90m :package \u001b[0m\u001b[90m :recipe \u001b[0m\n", + "\n", + " vision/CUB_200_2011 \u001b[0m fastai/CUB_200_2011 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagenette \u001b[0m fastai/imagenette \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagenette2 \u001b[0m fastai/imagenette2 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewang-320 \u001b[0m fastai/imagewang-320 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/mnist_sample \u001b[0m fastai/mnist_sample \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/pascal_2007 \u001b[0m fastai/pascal_2007 \u001b[0m (Image{2}, LabelMulti) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageTableMultiLabel(...)\n", + " \u001b[0m\n", + "\n", + " vision/camvid \u001b[0m fastai/camvid \u001b[0m (Image{2}, Mask{2}) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageSegmentationFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/camvid_tiny \u001b[0m fastai/camvid_tiny \u001b[0m (Image{2}, Mask{2}) missing \u001b[32m✔ \u001b[0m FastAI.Vision \u001b[0m ImageSegmentationFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagenette-160 \u001b[0m fastai/imagenette-160 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagenette-320 \u001b[0m fastai/imagenette-320 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewoof2-160 \u001b[0m fastai/imagewoof2-160 \u001b[0m (Image{2}, Label) missing \u001b[32m✔ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/cifar100 \u001b[0m fastai/cifar100 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewang-160 \u001b[0m fastai/imagewang-160 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/mnist_var_size_tiny\u001b[0m fastai/mnist_var_size_tiny\u001b[0m (Image{2}, Label) missing \u001b[32m✔ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/cifar10 \u001b[0m fastai/cifar10 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/mnist_png \u001b[0m fastai/mnist_png \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/caltech_101 \u001b[0m fastai/caltech_101 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/food-101 \u001b[0m fastai/food-101 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagenette2-320 \u001b[0m fastai/imagenette2-320 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewoof2 \u001b[0m fastai/imagewoof2 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewoof2-320 \u001b[0m fastai/imagewoof2-320 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewoof-160 \u001b[0m fastai/imagewoof-160 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewoof-320 \u001b[0m fastai/imagewoof-320 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewang \u001b[0m fastai/imagewang \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/mnist_tiny \u001b[0m fastai/mnist_tiny \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewoof \u001b[0m fastai/imagewoof \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagenette2-160 \u001b[0m fastai/imagenette2-160 \u001b[0m (Image{2}, Label) missing \u001b[32m✔ \u001b[0m FastAI.Vision \u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " tabular/adult_sample \u001b[0m fastai/adult_sample \u001b[0m TableRow missing \u001b[32m✔ \u001b[0m FastAI.Tabular\u001b[0m TableDatasetRecipe(...)\n", + " \u001b[0m\n", + "\n", + " tabular/imdb_sample \u001b[0m fastai/imdb_sample \u001b[0m TableRow missing \u001b[31m⨯ \u001b[0m FastAI.Tabular\u001b[0m TableDatasetRecipe(...)\n", + " \u001b[0m\n", + "\n" + ] + } + ], + "source": [ + "datarecipes() |> _show" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can likewise look at an entry:" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RegistryEntry(\n", + "\u001b[1m \u001b[0m\u001b[1m \u001b[0m\u001b[1m \u001b[0m\u001b[1m \u001b[0m\n", + " id = vision/imagewoof2-160\u001b[0m \u001b[90m(String) \u001b[0m\n", + " \u001b[0m\n", + " datasetid = fastai/imagewoof2-160\u001b[0m \u001b[90m(String) \u001b[0m\n", + " \u001b[0m\n", + " blocks = (Image{2}, Label) \u001b[0m \u001b[90m(Any) \u001b[0m\n", + " \u001b[0m\n", + " description = \u001b[90mmissing \u001b[0m \u001b[90m(String) \u001b[0m\n", + " downloaded = \u001b[32m✔ \u001b[0m \u001b[90m(Bool) \u001b[0m\n", + " \u001b[0m\n", + " package = FastAI.Vision \u001b[0m \u001b[90m(Module) \u001b[0m\n", + " \u001b[0m\n", + " \u001b[0m\n", + " recipe = ImageFolders(...) \u001b[0m \u001b[90m(FastAI.Datasets.DatasetRecipe)\u001b[0m\n", + "\n", + ")" + ] + } + ], + "source": [ + "(recipe = datarecipes()[\"vision/imagewoof2-160\"]) |> _show" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And load it, giving us a ready-to-use data container and blocks:" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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LisiGsextear8/lsZgBEpNa6LIuqppSYubXWe2fmcRwB9N4jQlUyrxBwD/cAmICcButxOl7cHS/IA2VpIokoELDaeisUPqScksDDe9NRAVyv11rrtN2P4/j566fNZsPM8zzv94OqEtE0Tb/9+mG/389l2W63Zv3Pf/7z4/HxdDqFtePJbbUsS+89rY7P58vlEhHjOJ5Op967u//yy192h7s3b96Y2fl8bq0Bv192W2YehqH3DiCnwcMApDQQUUTUWtMqIsxsHAYiKkuZpqm11nufpqmUQkTjNHmbRSQi3B1wIhKRiEgey030YRgS8bIsz8/P5/PZnEnSjaoSx02t1cxEiFciQisAEZGHHBFm5u4AmNndzWy320UErdKq1uruYPq73vvlcrler5vNZrfb5Zzv7u6+fPny9PT0+vVrMzufzzlnFRH6J/ydmQGglbtHhLsDMIskqszBLCAHzBoz4HHj3a7Xa5mXCOLg1ux8vm42m/v7ewCn0ykipg2r6m63K6U4or8IIso5bzbjcu1m1lqrtZpZznkcNtM0LctiBnfvvTOzCAFea40IAKq63W7dPedcay9lXpZlf7e92+2JopRSW2FiVa3VzYyZU0oioqrMHBHMHBH0TyLC3RFhZrwCQC/AzLSKwA0RicgwDOM4ni8F8QIArQAws3uPCAARQYR/xsyx8hUAIlJVACLCzERkZqpKRBC+Xq/LsrQVACKKiFrrPM999fXrVyLilZoZgIggImYWEVUFwMwAmNndbeWrMONMykJEACLCPdwazInIzJZS53kWEVXtvbfWUkoiMs9z7+HutQUzm9k4jnkcNpvN5XJ+fn7up34+n60VohiGpKp206OUdrnM2+3WrN2YGzMTaUS0Vs7n83a73U2badqKkKqWZb5ceilFZ06iOSsAJgGi9y4izEwrZiYiABEhIhFBKxGJCDNzdwbcvfdOL4SZATAzEQGICFsBEJFhGOalg5mI8B0zxwu6wSoiABARgIhgZiIyM3c3s4hgZiICEBFm5u5YRcQyz601Zt5ut8MwzPPs7s/Pz69evaq1qiozL8siIrvdjog0Itw9IgAQkZm11gBst1taiQgRAYgXFHCAIgjxAm5gANxaozBzX+ZiZptpOwxj877f7prV6/U6jqPqUGudl0ciymnKOWtOzLwss7u31kopSSjnJJLixqW1djyer9fr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23We67TM87ocBzmk1Hld18vlzsymaaq1unvvvZSyLMt6d1dKmabJzAB4RJIimplS0lTIFBGD0+pU13m63/ft+fXl5frS+yHSgat7IHKe19E1nHQqp3Tut1SZa7nMy/0030Wk0BI6/Gitt9bdXU9Cs2KlFEDc0z3N6t3l8f6ejw+f7h4+vLw8/bz+bKrX6+uXL19eX5+VnKbp69evpaiKuA8A7iYi8R1IllLqSU6qOk2TnwDIKTP9FBEA9CQiEeHuY4yIIKmqeRpjHMfh7vu+qypJVRORUso8z0LNzIhw90xmJk52f3+f77y79xEJiEqptbbW5mktJXRTMyPThNNUgPjDd79z96enp3Vda63/9m//drlc5nk1s9YaALOxrpdPnz65u5nVMquxHYPkZb3/8PHBtFoR0wqEjxzekULJESFUUQWTZKZOtdSyfvxQb7c3K08q83H0TO/dj+O4XW9zLUXvRLnMU60VwOvLPs/ruw8fPt9dHnp3QAgBpNa677fjONy99w6AZCnTp08/3a7b7Xb7669f3pbdrBCl935//zii7/t+HF1EpmnxfhxHf319WZalFh1jZIaqikjEcPeIkBPJzOy9jzEul4u7RwQAkhGRmQAiIjP5AwB3771v25aZtVaSIpKZY4zee2aOMTJzmiYzExFVnef52HtE+CmTAHiyABORkEz66AivtU7TT9u2ff369eXtzWqts3/9+lW1mODu7q4oP3z45N5V9eXlpdbq7rXW6/Xq7rXMdxfx6PO0qpR5WjNTxEx1frgrRed5rdUigHxHFStzyZzc071DAkIA7j7cSak2ldO63F/WT/fr4+vrdYwWAXfHz+HumalKKwKgtfaP//hft237+OHz/d3HeV6nhe9KmUopo20vL0/X67W1drvdjuOodf7555+nd3V+eXl7eXn58OHT29v1y2//8fjp8e7u7vHjL//+p//117/+9Xa7uvsy1X3fHx8fRSS8x3eeme7e2j68iUBVI+I4DgDzPK/rSjIzIyIzRQQAycwEovfm7r1b5qKqvffWWqab2RjtdovMHGP03scYPOlpnudSCskxRu8dJxGJQES4e2YaIR6eAWqZ1Nx7P462bc/Pz601EVGy7UfvXaVYEeRorV2v11KKanl6erndbn/99bdPHz+b1VolYkyTAyilmJmqiohZrdVqnc2klMlMMkkmqapULUBEYGShDyAyKRK1qJlNZTKzeZ5F5O7Sp7quy8u+b8fRjuPw1jOTglJKrSaCiBhjXK/XZbmUMhGiarXM67rO86z0aZpqfW5tX9d13/fMfHl5US2PDx/Wdb3eXv/1X/9V1URsjPHx4+NPv/zU2/Ht25eXl6fW9rt1/vDhYds2IFSKCI/juF6vt9vtODYrUqvxZGbTNM3zXErJTJ4y093j5O4Axhh5EhFVdfcxhohkprtHRP4gIgBUtdZaSiHp7hHh7gAzMyL8u4wId89M6x7ePcNrtalWdxmttTFaa2Y2LYuZeXKE4xSBgFy3407Lx88/P728JPUf/+u/rPcPl8ullNL7McZQFVWNiMxUVbNSitU6lWJmRYQimhkASYioCDNh4dluIyIDWspcpmW+TNNiZkyQNB6xBINzqUftbdrbcWSmCEsptdo7z3j3+PgxIrpn82EUM7vcPzzeP6QfeJfcj21dfd/3p6ev3759M6t3l/t1nZ+env70pz99ePz04cOnl7+8fPn66//93/97LeV3v/vdPM/fvn0Zrf3227fHx0f3rqq1Fnff99vT09fW94eHu2kqqgpAVZcTyd57ZpIUEQDu3k8kM5On1pqIAMhMEcnMMUae+ENmmlmttZRC0t3HGO4utDj5dxkRmQnAxhiREAqpkczkO1Wd59nMyjSpKlJGBiBU9MZSlNTe+6dPP5E8jmOe533fP358LKU8Pz9fb6/lFBHuDiAiAIhYrUutlaSZuXtEAOAJgJjSO3MAISy1zPO8LstFVY+tuffe0j1FbJqWaVqAu5fn58wAIAJV5TsQwN3d3XF034/MFJqICU20tLaXMl0u9yRbPyKilELy6ekrgE+fPvV+HMcmGqXyt29PJP/f//H/vD6/tLav67qs04cPD6Uw3qUraKZmEjkih4jM83y5XOZ5johSyjzPpZTe+xgjIjITAEkRMTOSx3GIiKqKSGZGBEkRwSl/wIn/pziRNDMfiZOIkARAEoAVmyipBDJaa73t7m5mHz58wHeEUKVUobv33muZlvVx345tv5mWh4cPvfda68MDPn58VNVMRsDM1nVW1Yhw923b9n1HikqpZSaJdyIZIzNJIYjvdF0vrffRu9BoU7KEg4BQg2FWlxlTqREjMyk5jiNyxHcjcoTHcI93RAaXZZnnVa0C0nt/e3uju2qZ53WM0VrLzFrrhw8fvnz58m//9j++ffuyLMvd/fLL7z7/9PnzMY51ues+/vjHf/3zn//8+Pj4L//tn/+vf/lvHz9+/Pb0lSMz3SM9ugimqajqfJqmyd1rrapKMjPjlJn4QU+9N1VRFZwygxSSEQFAhAAzE0CeSImIMUZmAkJST4ORJ3wn+MHu7u5UlYh9v922fdubRKjpw92du7fWI0LNVHVHezdGn3wSESTdne8gvY2HhwcRQ0qt8zxdRGk2l6KqZYwWIWOEWS1lUp0yfYyIyEwlU6SIgNRkLPPSffSjRUBo7t4x3EMpQi2Trusc3m+3t9v2NlovVTMxvoN/Nzw8g2/P3y6X+8+PP/300y+i5e3ttm99349azNsYPiLCT2Z2d7eS+e9/vt1ub6Xo/f395bJc7ubHx7vWnKSIDG/X2+vXr799fLif5prpZEaOfR/HsYnIuq7lhFM5kYwInEQkf8APIkISfyciMhMAfwAQEQAiQoQR0VqT70xVSWamquKU3zFOmWnTeinCjOHe9T8J1ZiZJEUkM0mKiJ0CuR1NVef10nt3d6uTqoqV/WiAqJX7xw9jtD7G0dqyEKBaKXU2K2rVI4+jRSSQpJiaWjFTESXh6KrKOo8R4YjIIEiCkukgRSSD7n4cR2vb/d2KGIcgMbpnhLuPiIwIkmWqy7qKmHtCSlUbvb1d327bGxDTNIHpbiJYL3Op4h4ArEjEaG0HY5rK23W73l7f3l5E8C4RY7TjOIAI9+PY3olwXRczW5ZFVUVkmqZSCoCIIAlATgAy093zVErBDyTzFBGqilOecOIpItydpEioau8dQLGJpIiQzKSfIsKEmkICqsXqNEUQIYLn1xdVBRAO54AaIBR7uL+8vD5l5jRNvffjOJaTqh7HiBjzd3q73bbt2+12i8hSSmaaFTMjOUa/3W5mRlIVpJlprZOqkvj68ioigCBFMhlJo6qaWsRwdyCG9xGdhJnVaXF3xcbhmX0MH314xjotqprurTWRQMrd3d3j3f3T128v356Ova+X+eHhYfhxHBsQ356+/PTTz6r69vYWjnfuLgkIR++ttXmef/nll59++ikzf/31V1UtRcN927beu5lO02RWLpdLZpYyvTOrYzT3JBEBVZpVVQIyRnPPiCEiER6RJEjJDHfnKTMjIjNx4klExhjuTjIzI0ZEuHspTVXNTFWREid3t0wS4og+oqejqGkRAdq0tYaIWmYrS4IeGVBamZZLvIPUee2e7r7vu5llJjOO7QZgjKGUqVTvocxlmWWR23b98tuvZC7LIkIzk+9SmCpERmv95dvLNE2lFEKValrTsfdjeqwAXq5vZpjm8rbdbte3zz//fG0ASp3munwAvry93lobtVYh1zKZSDsONdY6zZdLXebHx8cYvRQd3o7jaG33cBEQ2pt3ZC1rWad25EtsKvOf/vSn3jsjq9Y//PK73/3088vr8zRNf/3rX83kXeQAIGZ1/s4DpdQ6LaK67W3fb2Z1Xee7+9L7sR8901WLmVgxoJBMeDgiRzgSrpkAjuOIfBcAVFVEgMjku0y+U9XMaK2BUUrx2I82MqiqpIwR70TESIoIYFDJpEeIgJSPnz/dbrf9dnige5iZlmkxI+RyuY8Yt9vtOA6Spgag9w5AkDwBUFLJpIqIWS1VwHQ/xhiRQ2h9HKq61MXM3Pu+t+M4YjhKmhQzEyogY0Rr++srIoaZicS2bRE+LUuZpuZZ5/lhvbNCRr6+vfTeYzgsxxitNbFjShOrSMmgiKiqWQVjDGTmGAOITE7TbFbdHanuGeHu7Y9//J/Lstzf33/+/Pmnn34aY7y8vLj7NE1mVoq+E4WeIoJQ9+y9R0RrbYwgPQKZCYhIZlJEAMlMgO4DQGYCokpAwcDJ3cfoEZGZEUFmJkiKSEQA+Q4MIDJ9jO7umQQjQ8YY7iki5u6qzEwAIgKoFlWzeZoBAWyMYTRVBSQij94eLw8iSxvOfVdVRG63o1QlCaSIZGa8A5Lcti3Tp2la1rXWWgqP44gct9ut925m8zwDuN1uLy9v+76bCcnMdHcIycyMzGytkTkvs/vx/HLNzPv7+1LKPMu8rnd3d6K5z3Ot1cwiRyBba9u2QYrKVACeIgKAiCjU3TPT3QFERK11WS79XQuSrfW3t7eHh4d5ni+X5XK5LMvy/PwM4NOnT2YSEZlOUhTv8iQUkpnZex9jRASAiMhMkiICgCSA/BsCIIXfJd4R76ZpGmOQcPf8G2YmSRHJZJ4AZGZE5AlgZkbEGMM9RcRaaxEjMyOi1ipSVYWSt9sNwHRSKIDj6O1db8dxzPO8rivCb7fbtl1vt9tjuQcQGZnJk6qKiDvNDEBmikit1d1bH9frtfc+TdOyLD6uz8/Pb283AJfLo4j03jPTtNRazYxMkh5DBL1Ha63Wend3F8C6rrVOAFprWzsyYGag+Mg2utx2LfN6QVV7R3KM4d/14cO/ywyIUsTe1VpVdZ4IyOvrdYzxD//wDxGR6Q8PD7Xatm2//PLLP/3TP2X66+vry8tTay0ieFJVpNgpTiRFJDMjAoCI4Ic8qSpJ/E1kZjgSXkpRVaCoapzcPSJUS+YAwt3zBEhEiEhmkkpoMgDwZO4eMfIdWKalFEWM3tt+9Fpr0SIiqgVAJns/Kuvtduu9T9NUpjluW/ORQncnKe8oKtQTyc+fl4gREWOM9t3t7e3tent9e3uLCAC3283H9vr66p7rupqZiGQSgJ1IAhoRvfeI0ftBclmWWqf96CQBjDFab+GAipaa6X10eLo7UkoptVYRQXzn7mOM1tu73ntEqNk0TSJGcp7nYpXkGGOaSu89Ii6X5dOnT/NczQzMh4eH49hutxt+yEySIpJBPYkIAHcn6e4A+EOeAPCE/00AJ4nUMQYAEVU1IMcYADJTRMws08cYEYFTBESEBKEAMpOkqtg7d1dVEqpWSlFlZEZkrVOtVcA4lVLmeQ4kmS+vz9u2uXtmuruZTdM0WrciqlprLSp2Ui21zr0f1+v1tm39u23f9+M4WmuqGhG32210jDGmaXkHQETMqqoWqyRba/u+99G2bXPvqjpP6zyvmUSwbXtESq0iMs/z5XLvre/HJiJIpqiZTdNUSkGM7egew737aYzh7pkk5HK51FpVtdZarI4xVHVZFtUSEes611pV9XK5RPpxHE9PT9fr1d1LKaIYY2QmgMzkKTMBiEhmjjFERFVJ4gcRIZkn/ECKKkgebSMpJyDNLL+DiJB0t8wjMwFJeERkZkQgk6S7R4Sq2rvrdlvXdZ6rqrwDMqlidRKrtWbmcRzubmYJIVmKLesFlNaajy4iy3Ihc88s03RZ52maRARAZnrGtm29H9vJ3c1kWRY17vtuZiTHGJk6z/OyrLVWEYoImUBEekbux3a9vfW/OdZ1vb+/L1a325GZx+EAO6DKUsqyLLdbzXaICYD/ryx4UY7cSJIF6h6RCaCqSLak1YzZ/P/nrZ5kPQBkRPgW0eLeXrvntNamaZr7ZGbrun18fOzb+ng8IgKAu/feSc5zf3t7M7P+1HpEvL+/Px631tq//vXvqnJnRGzbw8xK9ccff9xuH/f7vSp7795YB5KtNTMDkJlVxS/6AkASADNz90zVQRJQPABw6/pUmUkSoJn3TjMDYGYAJJEEoENVQXjKTMCaT1Nf2u12671fLqfWGkpStTa1Nq23u7HRGB6lfBp7Ph4P2PmptfbXX3/F2HvvFMYYy7KcTqeXy8s0t6ratm3f98w0eOaIgyTSW2vg1A9mlpnufj6fl+VkZr03AGOMzDRrANZ1HWNEhJmR7L2/vLxF7I/H1lqTFBEbZGZSGVvvc2sPwCS59dYm9w5p27b77brdb4/HI2u4e++dpBmWZTmfX6TsvZvZ7Xb7+++/t22b5/l+vy7LeZqm+CSS+7b/9ttvyzIB0CGzngC01oy9taYv7s5DZkrCFx7MzN3jS1WSlIxU7z0/qRQk7UA44QBIAiaBBGRSkMRBAukkWmvTNLXL6+tyPvc+48nhBgpVdbq8lDJHqDiitj0ALOdTay0z3P2nn3663fr7+3tltNZ67z/98nMz/+233/bHerlceu9jjMzKHACmaeKnfKoqM2sHM5v6+XQ6mbWI2LYNQB7I3d1J9e5vby8R4f42z/P9fo+I3md3v13f27ll5rZtvfuyLO0+3x+be5/n+eeff/7Pf/5zubz8/ffff/7+x/X2wcppblW2bdsYw92X5fLycm6tST7Gfrvd/vjz99v96u5m1lojebvdtm0jlZljjN774/EAMM+zmZUCAMnMtIYxBgAzI5mZdei9A5Dk7lUVEZLMDADJ1pqZZXpVZKYk0gGRbDaB9ZSZlUCx994OEVaVkirRmqvk7nk4nU7LsmzbaKfTqfcuCQfJIFQhM4GiaNbcJe16KrbWqvBEsrXWe0/C3V9fXwF7PB5jDHwxs23bL5dzVf3513a73VoDyREjIi6Xy+vrq5mpvD5FZm7bGhFjDADn83meexUilJmSALRPE2BjjBjZex9jdLbT6QTU+/Xjfl/n+dTa9NNPP/3883/13td1vd/vY4yqYpWgJwBmNk3T+Xy+XF7NbN/Xx2O73W7ruo4xAGQmgKoaY6zrKuU8z4J671UhicSPSOIHkgBIAhARANydpCQAkqrKzPCFn9xM3wEE9AQIB5LuTjqqAOgTJJGsKh3wnQwwUu18PptZipLMDDBUjajKIkVStCf3XhWSYEb2qhJhZr13O1wul/v9fv24RdQ0dTYXrbd5w06yqrZtG2O01jPzer2OMaqKhxGRme7d3SXt+76uq5kty2R2whNrjA2AuwNorUGMkVXDgHV9CPP5fH6s4/ff/rxer/NlOZ9efvn137/8179odr++Px4PZTgkk5RVeJqmaXmaz9M0PR6P2+32/v5+vb5v2yaJFA1jDMB4qFJEmLO1VtUzU8p6UkkiCYCkJAA6VJWkqpKEg5mRBCCpqkhBBEiYGZ7ILgkofFFRAuGgzIwkAJIAJAEgWVX6AQAemplVFQAeAIuIMdJAGABKhPjk7jhIyswxRlW11nrvdng8Hvf7vfe+LIu7R0SqWmsfHx/X63WMsSxL7/bx8XG9Xqdpqqp1XUnGgJnNc1uWJXNUVURIWVWRu6SqykgApI+RvRXprU2ZitpJOhgR67ru+269vVzeLpfL29vbsizrum7blpn2ndu+55O7n06n8/lM8nHfrtf7x/X2/v6+76uEfnD3fd/d+9PlchljqyqhzKy1JmmMIamUVUVSP6gfSMpMACT9wAOeRBxIAjQzHTKTJGBSSqoqAKSbNQCSIANMCgAkJVWVmSRChi9tZGSmwadpMmuVyFREzfMMlJT1BMHo3swMh6rKTNH73Kdm7j4iI0vEtMy9z2OM+/0eEbO3+/2+7/uyLO5+v7/f7/fe+8vLi7tXVWttmrq7t9b8MM+zlPuBd5mZlFlJ+Bjjfr9DnOeTWesdsa+Xy2Wapsinen1968v8+u3tfD4vyymz1nXb9x1ZyqpMc1aFVL0vl8tlmc/7Ho/HdV3Xx+MxxgDQWutTa82BwheSAEiaeWutKiTVd0pJJDPTrSTVITMlVRUOkqpKEgB+wQ8IAwQWANJJAZCsalQVP0FSHXjQwd0lSJAolf4ftMysKtL4BK8aGaWC0UFTgoKdBtgAAAfiSURBVAw+Gc3YWpPSzNzdzEhOT80APB4PM5vn2d3HGLfb7X6/VZWfzu7+9vY2zf7x8fHXX38B+PXXX6tKEgB3bz5JGmNERFW5+zRNEbGPNe+jNfuOxNO+7xCB1tvczR+l7o3k2AdKp5fL03xavn37RrP1kPvY9z3GiAgRktx9nvs8ndy9at/3eFLRrJFs3d0JlKR5niSt6zrGiNjdfTnNT+t6l1QHEE86RISk+iIJB5JVlQczA0ASMPxfeoIAkDRzoL7LTDMDKiKqVIUnM5MolSQcJAHQJ1apqtpTRNBMRGZGREJsDoCkudOmKgOT1FOUzMy99w6gWmtSRsS2bSTdfYxxH+PxeJTQ20TyfD631kqjqnrv5/P5119//f333yMiD0QCIN0+dZJSkqyqzJFpJF9e3lprKh8j9z1a242NZpLGGKyU1Fpjb26d8ObT9nS7bdsWEWOMyuy9F7bp0/IEYNu2fd+rKqKeAJiZu5vBne5uZpkZMSJCEgCSZoYvJEECIAkgIiTVQRIAkgDMTFJVjTFImhkAM4EADCgA+g56soMkAFWowhOpiKiSVDxIqioz45eq0peqar13ABIBlKKqALi7JIJ0czSmCoCqxIhorRlpZgAkZdS6rtu26amwj+3xeETEsizn07kbAWSNbd3N7Jdffn15OT9dP+7Qlk8hIs37+Xx5etxvUlaVN5JCVY6o1PTtl6kvKmZuGRWxZ+8ge+9SQZrnGRMLItlai4j1/tjWNWLkp+FuyzLtoWWZTqeLu0fs67pu+1qKMbbIvSrcnaR76917nzNGFUj27mQjCSAz+Q8HkmyCCBIeOfSJVQEYUICTAkzKTJkh04Eie1W4NaAA6H9BTzwAxEH/YFZWFUkAhAOQKAkwQHiSAalPJVX7648/p2lqbdrXDbDmU2utqlrvmTkq8FQYVQaQ9fry7XG/joxlWQDc79d9XSXePu6Xy2meZ5Imc/fW3cz2fe29m7XT5fz67e368f54PCriNJ3MWkSBNPM2T30+Tcu5Cvf7dd/3qpqai6piUt2bslTe6MXYxwMeyzSTWJZTASm5d6jc+8v5NWNk5uPx2La1E2/fXihU1evLz+aQuO97Zlbt0sjarrc/Wmun0zxNU2tT732aFnfuXKtKEmgkAWRmRPQ+u/fTKSP2iMjMqpLEJxNEb66iIJWEzJQ3ttbAitgjC9oAvLxwmiaSGVlVZta8A4gISQDc+zyb2Xha1/U0L1KS9oQnmZG9d8LNjGRVZaakfV/dvWVmVUkCYGY0GR0HSXWQknRzeuO+be4+TRPJfd/HyH3fq+p8Ps/z4k4ABj6ZE4A1FxH72PcddRtjb/De5m3bxhiV7PPU59M0TfQ2soyNdJJ4qhRSJRQq063RrLUmVSgi1g3ROZtZa63wZLFvY4xt26QUsvfuDR1GU8UYA5kJuLmexhiP9f60rvfSMPdlWU6nk/ssKUbtW9FKqFJBMDMAOgDgwb0D5q5DZlrWyE/1JInfGQADBBAUQFCQRezu3lpzdx4kVZUOJAHwBxF7VQEwM3dvrQHoba4qfsEXSW2M4Z+6u5N0d6OTlASgqiKCVP/UWrd13+e5m9nj8bjdbtu2SQLw+vraWgOKpNP0hHpyd5KZuW3b/free+vLqbV2u90iAnIzm+d5miaQEdH4CYD+D2QmUd6au1d5ZkaMymzz5O59mlKKqH3fR25m9vPP37J6thGZFEmYWWsUrKoiY4zt8bTetm3LzNfX13mez6fzNE2SjzEiMnLvnaWUBEASyarSDwCYGf5hrVkk933PTEk4kDQz/P9k+767d3cn6e4kM7MOJAHYwd0jAkBmVhVJAO7eeyc5TdMYA4AkACQlVRWAFhGZKQmAH4xOct93kmbGTzAzklXVezezbdve39/XdZ2maZ7Pkk7zAqAqJKEUEZmVmXTYobW27/uyzO4OQBJJM38iKYkAycr6ThIOZgYhIqAQDGY8QKgqfamqMca+79vI3vs8/ztyr6pt2/Yob2zGg23btm73fV/HGCqcTqfeX15fXwFTYYwRMTJTRXfPHEI98YukqspMADrgCyl3F7y1JsnMJPELvkjCP5hZmRkRduAXHcwMgH0hqdITAJLu3loDYGYkdagvkjKzVZUkAPaF4FNVuXtrTRJQJPNpjE5u23a73fZ9b62dz+e5+xiDZFVJAiApIkaMzHRY732e52ma/vz9v0+nU/O277uZdetuk5llpqRG672PbeShqiSRNHcjI0I1SqQ7IJLuTiAixhgiU5/cfba2LMt6GGPs+84oS6TRzLJiXdfI3cxOpxO5tO7zPEuqqojY930MPbl1N9aTUhJJMwOgAwD9AP8o0kj23t29qjKzqiSRxEESvkgwM0kRQdLde+/2pap0IGlmJAG4OwCSZiaJZB0kVVUe6oukJgkAfwBBUlX13mmsKglmljn2fYfZtj32fe+9n8/n0+mkHFWVQlVJJamq9n0fsZM00cz6obV2uVwQeb3e3Z2E0UlWlSSXANQXSQD4BNIsIoqWBe/d3Wh0cwI1aoyREt1bm87nM71/+/bt999/i9xjDHtyChkRkq63LSL65K+vb5fLpSpG7JLe39/xJGamZJIyU3jSdwAkAZAEwN0lVZWkqsI/mJk0mZm7S4pDVeEgiSQASTiYGYDMlASgtcaDuwOoA0kAPBhZVTjwICkzJdUPJNXhfwAmuCpH7qkbOQAAAABJRU5ErkJggg==", + "text/plain": [ + "166×160 Array{RGB{N0f8},2} with eltype ColorTypes.RGB{FixedPointNumbers.N0f8}:\n", + "\u001b[0m\u001b[38;5;16;48;5;236m▀\u001b[38;5;235;48;5;102m▀\u001b[38;5;59;48;5;102m▀\u001b[38;5;235;48;5;102m▀\u001b[38;5;58;48;5;102m▀\u001b[38;5;58;48;5;102m▀\u001b[38;5;59;48;5;102m▀\u001b[38;5;235;48;5;102m▀\u001b[38;5;59;48;5;102m▀\u001b[38;5;59;48;5;241m▀\u001b[38;5;59;48;5;243m▀\u001b[38;5;235;48;5;242m▀\u001b[38;5;235;48;5;240m▀\u001b[38;5;236;48;5;102m▀\u001b[38;5;237;48;5;102m▀\u001b[38;5;59;48;5;145m▀\u001b[38;5;59;48;5;145m▀\u001b[38;5;237;48;5;242m▀\u001b[38;5;236;48;5;241m▀\u001b[38;5;235;48;5;102m▀\u001b[38;5;233;48;5;235m▀\u001b[0m\n", + "\u001b[0m\u001b[38;5;59;48;5;59m▀\u001b[38;5;243;48;5;144m▀\u001b[38;5;102;48;5;244m▀\u001b[38;5;102;48;5;244m▀\u001b[38;5;144;48;5;243m▀\u001b[38;5;244;48;5;243m▀\u001b[38;5;144;48;5;245m▀\u001b[38;5;102;48;5;145m▀\u001b[38;5;102;48;5;244m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;242;48;5;102m▀\u001b[38;5;242;48;5;239m▀\u001b[38;5;240;48;5;59m▀\u001b[38;5;101;48;5;59m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;238;48;5;59m▀\u001b[38;5;237;48;5;235m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;234;48;5;16m▀\u001b[0m\n", + "\u001b[0m\u001b[38;5;59;48;5;59m▀\u001b[38;5;108;48;5;102m▀\u001b[38;5;145;48;5;102m▀\u001b[38;5;145;48;5;244m▀\u001b[38;5;245;48;5;245m▀\u001b[38;5;144;48;5;244m▀\u001b[38;5;245;48;5;244m▀\u001b[38;5;244;48;5;102m▀\u001b[38;5;243;48;5;102m▀\u001b[38;5;102;48;5;243m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;59;48;5;101m▀\u001b[38;5;236;48;5;59m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;235;48;5;235m▀\u001b[38;5;59;48;5;235m▀\u001b[38;5;59;48;5;101m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;16;48;5;58m▀\u001b[0m\n", + "\u001b[0m\u001b[38;5;59;48;5;237m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;242;48;5;102m▀\u001b[38;5;144;48;5;138m▀\u001b[38;5;243;48;5;243m▀\u001b[38;5;243;48;5;244m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;242;48;5;242m▀\u001b[38;5;242;48;5;241m▀\u001b[38;5;101;48;5;101m▀\u001b[38;5;101;48;5;101m▀\u001b[38;5;101;48;5;95m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;58;48;5;58m▀\u001b[38;5;59;48;5;58m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;101;48;5;101m▀\u001b[38;5;95;48;5;101m▀\u001b[38;5;58;48;5;59m▀\u001b[0m\n", + "\u001b[0m\u001b[38;5;59;48;5;237m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;244;48;5;243m▀\u001b[38;5;244;48;5;102m▀\u001b[38;5;242;48;5;238m▀\u001b[38;5;242;48;5;239m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;101;48;5;101m▀\u001b[38;5;95;48;5;95m▀\u001b[38;5;95;48;5;59m▀\u001b[38;5;95;48;5;59m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;235;48;5;59m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;101;48;5;59m▀\u001b[38;5;101;48;5;59m▀\u001b[38;5;59;48;5;58m▀\u001b[0m\n", + "\u001b[0m\u001b[38;5;238;48;5;238m▀\u001b[38;5;108;48;5;144m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;95m▀\u001b[38;5;242;48;5;240m▀\u001b[38;5;240;48;5;241m▀\u001b[38;5;237;48;5;102m▀\u001b[38;5;59;48;5;102m▀\u001b[38;5;241;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;59;48;5;102m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;59;48;5;59m▀\u001b[38;5;59;48;5;102m▀\u001b[38;5;101;48;5;102m▀\u001b[38;5;101;48;5;102m▀\u001b[38;5;59;48;5;59m▀\u001b[0m\n", + "\u001b[0m\u001b[38;5;238;48;5;238m▀\u001b[38;5;244;48;5;144m▀\u001b[38;5;243;48;5;144m▀\u001b[38;5;244;48;5;246m▀\u001b[38;5;241;48;5;244m▀\u001b[38;5;239;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;138;48;5;102m▀\u001b[38;5;242;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;101;48;5;101m▀\u001b[38;5;59;48;5;102m▀\u001b[38;5;59;48;5;102m▀\u001b[38;5;102;48;5;242m▀\u001b[38;5;102;48;5;243m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;101;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;59;48;5;59m▀\u001b[0m\n", + "\u001b[0m\u001b[38;5;238;48;5;239m▀\u001b[38;5;245;48;5;145m▀\u001b[38;5;246;48;5;145m▀\u001b[38;5;145;48;5;145m▀\u001b[38;5;244;48;5;242m▀\u001b[38;5;102;48;5;65m▀\u001b[38;5;101;48;5;102m▀\u001b[38;5;108;48;5;144m▀\u001b[38;5;107;48;5;107m▀\u001b[38;5;101;48;5;101m▀\u001b[38;5;101;48;5;101m▀\u001b[38;5;239;48;5;101m▀\u001b[38;5;102;48;5;241m▀\u001b[38;5;243;48;5;102m▀\u001b[38;5;108;48;5;102m▀\u001b[38;5;243;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;59;48;5;59m▀\u001b[0m\n", + "\u001b[0m\u001b[38;5;59;48;5;102m▀\u001b[38;5;245;48;5;145m▀\u001b[38;5;145;48;5;145m▀\u001b[38;5;243;48;5;102m▀\u001b[38;5;101;48;5;102m▀\u001b[38;5;101;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;144;48;5;102m▀\u001b[38;5;101;48;5;102m▀\u001b[38;5;101;48;5;101m▀\u001b[38;5;101;48;5;102m▀\u001b[38;5;101;48;5;102m▀\u001b[38;5;102;48;5;243m▀\u001b[38;5;102;48;5;242m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;237;48;5;237m▀\u001b[0m\n", + "\u001b[0m\u001b[38;5;102;48;5;102m▀\u001b[38;5;145;48;5;188m▀\u001b[38;5;145;48;5;152m▀\u001b[38;5;145;48;5;145m▀\u001b[38;5;245;48;5;145m▀\u001b[38;5;245;48;5;145m▀\u001b[38;5;145;48;5;245m▀\u001b[38;5;246;48;5;245m▀\u001b[38;5;145;48;5;145m▀\u001b[38;5;243;48;5;145m▀\u001b[38;5;245;48;5;245m▀\u001b[38;5;102;48;5;244m▀\u001b[38;5;242;48;5;144m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;102;48;5;102m▀\u001b[38;5;237;48;5;236m▀\u001b[0m\n", + "\u001b[0m\u001b[38;5;102;48;5;59m▀\u001b[38;5;152;48;5;60m▀\u001b[38;5;152;48;5;60m▀\u001b[38;5;146;48;5;59m▀\u001b[38;5;145;48;5;238m▀\u001b[38;5;245;48;5;237m▀\u001b[38;5;245;48;5;59m▀\u001b[38;5;244;48;5;237m▀\u001b[38;5;145;48;5;237m▀\u001b[38;5;145;48;5;59m▀\u001b[38;5;245;48;5;237m▀\u001b[38;5;244;48;5;237m▀\u001b[38;5;108;48;5;237m▀\u001b[38;5;243;48;5;59m▀\u001b[38;5;102;48;5;236m▀\u001b[38;5;102;48;5;236m▀\u001b[38;5;102;48;5;236m▀\u001b[38;5;102;48;5;236m▀\u001b[38;5;102;48;5;236m▀\u001b[38;5;102;48;5;59m▀\u001b[38;5;236;48;5;234m▀\u001b[0m" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "data, blocks = load(recipe)\n", + "getobs(data, 1)[1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Learning tasks" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, learning tasks can also be listed:" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1mRegistry(Learning tasks, 6 entries)\u001b[0m\n", + "\n", + " \u001b[1m ID \u001b[0m\u001b[1m Name \u001b[0m\u001b[1m Block types \u001b[0m\u001b[1m Category \u001b[0m\u001b[1m Description \u001b[0m\u001b[1m Learning task \u001b[0m\u001b[1m Package \u001b[0m\n", + " \u001b[90m :id \u001b[0m\u001b[90m :name \u001b[0m\u001b[90m :blocks \u001b[0m\u001b[90m :category \u001b[0m\u001b[90m :description \u001b[0m\u001b[90m :constructor \u001b[0m\u001b[90m :package \u001b[0m\n", + "\n", + " vision/imageclfmulti \u001b[0m Image classification (multi-label) \u001b[0m (Image, LabelMulti) supervised\u001b[0m Multi-label image classification task… ImageClassificationMulti (generic funct\u001b[0m… FastAI.Vision \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " vision/imagekeypoint \u001b[0m Image keypoint regression \u001b[0m (Image, Keypoints) supervised\u001b[0m Keypoint regression task with a fixed… ImageKeypointRegression (generic functi\u001b[0m… FastAI.Vision \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " vision/imagesegmentation\u001b[0m Image segmentation \u001b[0m (Image, Mask) supervised\u001b[0m Semantic segmentation task in which e… ImageSegmentation (generic function wit\u001b[0m… FastAI.Vision \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " vision/imageclfsingle \u001b[0m Image classification (single-label) \u001b[0m (Image, Label) supervised\u001b[0m Single-label image classification tas… ImageClassificationSingle (generic func\u001b[0m… FastAI.Vision \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " tabular/clfsingle \u001b[0m Tabular classification (single-label)\u001b[0m (TableRow, Label) supervised\u001b[0m Task where a table row with categoric… TabularClassificationSingle (generic fu\u001b[0m… FastAI.Tabular\u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " tabular/regression \u001b[0m Tabular regression \u001b[0m (TableRow, Continuous) supervised\u001b[0m Task where a number of continuous var… TabularClassificationSingle (generic fu\u001b[0m… FastAI.Tabular\u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n" + ] + } + ], + "source": [ + "learningtasks() |> _show" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RegistryEntry(\n", + "\u001b[1m \u001b[0m\u001b[1m \u001b[0m\u001b[1m \u001b[0m\u001b[1m \u001b[0m\n", + " id = vision/imageclfsingle \u001b[0m \u001b[90m(String)\u001b[0m\n", + " \u001b[0m\n", + " name = Image classification (single-label) \u001b[0m \u001b[90m(String)\u001b[0m\n", + " \u001b[0m\n", + " blocks = (Image, Label) \u001b[0m \u001b[90m(Any) \u001b[0m\n", + " \u001b[0m\n", + " category = supervised \u001b[0m \u001b[90m(String)\u001b[0m\n", + " \u001b[0m\n", + " description = Single-label image classification \u001b[0m \u001b[90m(String)\u001b[0m\n", + " task where every image has a single \u001b[0m\n", + " class label associated with it. \u001b[0m\n", + " \u001b[0m\n", + " constructor = ImageClassificationSingle (generic function with 3 methods)\u001b[0m \u001b[90m(Any) \u001b[0m\n", + " \u001b[0m\n", + " \u001b[0m\n", + " package = FastAI.Vision \u001b[0m \u001b[90m(Module)\u001b[0m\n", + " \u001b[0m\n", + " \u001b[0m\n", + "\n", + ")" + ] + } + ], + "source": [ + "learningtasks()[\"vision/imageclfsingle\"] |> _show" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "SupervisedTask(Image{2} -> Label{String})" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "task = load(learningtasks()[\"vision/imageclfsingle\"])(blocks; size = (200, 200))" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Learner()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "learner = tasklearner(task, data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Finding features" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Aside from listing a big table with features, we can also find entries that are relevant to us.\n", + "\n", + "For example, **find all the datasets that have already been downloaded:**" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1mRegistry(Datasets, 2 entries)\u001b[0m\n", + "\n", + " \u001b[1m ID \u001b[0m\u001b[1m Name \u001b[0m\u001b[1m Size \u001b[0m\u001b[1m Is downloaded \u001b[0m\u001b[1m Tags \u001b[0m\u001b[1m Description \u001b[0m\u001b[1m Dataset loader \u001b[0m\u001b[1m Package \u001b[0m\n", + " \u001b[90m :id \u001b[0m\u001b[90m :name \u001b[0m\u001b[90m :size \u001b[0m\u001b[90m :downloaded \u001b[0m\u001b[90m :tags \u001b[0m\u001b[90m :description \u001b[0m\u001b[90m :loader \u001b[0m\u001b[90m :package \u001b[0m\n", + "\n", + " fastai/imagenette2-160\u001b[0m imagenette2-160\u001b[0m \u001b[90mmissing\u001b[0m \u001b[32m✔ \u001b[0m String[]\u001b[0m A subset of 10 easily classified clas… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " fastai/imagewoof2-160 \u001b[0m imagewoof2-160 \u001b[0m \u001b[90mmissing\u001b[0m \u001b[32m✔ \u001b[0m String[]\u001b[0m A subset of 10 harder to classify cla… DataDepLoader(...) FastAI \u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n" + ] + } + ], + "source": [ + "datasets(downloaded=true, description=!ismissing) |> _show" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Find all dataset recipes with classification targets:**" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1mRegistry(Dataset recipes, 24 entries)\u001b[0m\n", + "\n", + " \u001b[1m ID \u001b[0m\u001b[1m Dataset ID \u001b[0m\u001b[1m Block types \u001b[0m\u001b[1m Description \u001b[0m\u001b[1m Is downloaded \u001b[0m\u001b[1m Package \u001b[0m\u001b[1m Recipe \u001b[0m\n", + " \u001b[90m :id \u001b[0m\u001b[90m :datasetid \u001b[0m\u001b[90m :blocks \u001b[0m\u001b[90m :description \u001b[0m\u001b[90m :downloaded \u001b[0m\u001b[90m :package \u001b[0m\u001b[90m :recipe \u001b[0m\n", + "\n", + " vision/CUB_200_2011 \u001b[0m fastai/CUB_200_2011 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagenette \u001b[0m fastai/imagenette \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagenette2 \u001b[0m fastai/imagenette2 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewang-320 \u001b[0m fastai/imagewang-320 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/mnist_sample \u001b[0m fastai/mnist_sample \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagenette-160 \u001b[0m fastai/imagenette-160 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagenette-320 \u001b[0m fastai/imagenette-320 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewoof2-160 \u001b[0m fastai/imagewoof2-160 \u001b[0m (Image{2}, Label) missing \u001b[32m✔ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/cifar100 \u001b[0m fastai/cifar100 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewang-160 \u001b[0m fastai/imagewang-160 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/mnist_var_size_tiny\u001b[0m fastai/mnist_var_size_tiny\u001b[0m (Image{2}, Label) missing \u001b[32m✔ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/cifar10 \u001b[0m fastai/cifar10 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/mnist_png \u001b[0m fastai/mnist_png \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/caltech_101 \u001b[0m fastai/caltech_101 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/food-101 \u001b[0m fastai/food-101 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagenette2-320 \u001b[0m fastai/imagenette2-320 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewoof2 \u001b[0m fastai/imagewoof2 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewoof2-320 \u001b[0m fastai/imagewoof2-320 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewoof-160 \u001b[0m fastai/imagewoof-160 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewoof-320 \u001b[0m fastai/imagewoof-320 \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewang \u001b[0m fastai/imagewang \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/mnist_tiny \u001b[0m fastai/mnist_tiny \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagewoof \u001b[0m fastai/imagewoof \u001b[0m (Image{2}, Label) missing \u001b[31m⨯ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n", + " vision/imagenette2-160 \u001b[0m fastai/imagenette2-160 \u001b[0m (Image{2}, Label) missing \u001b[32m✔ \u001b[0m FastAI.Vision\u001b[0m ImageFolders(...)\n", + " \u001b[0m\n", + "\n" + ] + } + ], + "source": [ + "datarecipes(blocks=(Any, Label)) |> _show" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Find all learning tasks with image inputs:**" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1mRegistry(Learning tasks, 4 entries)\u001b[0m\n", + "\n", + " \u001b[1m ID \u001b[0m\u001b[1m Name \u001b[0m\u001b[1m Block types \u001b[0m\u001b[1m Category \u001b[0m\u001b[1m Description \u001b[0m\u001b[1m Learning task \u001b[0m\u001b[1m Package \u001b[0m\n", + " \u001b[90m :id \u001b[0m\u001b[90m :name \u001b[0m\u001b[90m :blocks \u001b[0m\u001b[90m :category \u001b[0m\u001b[90m :description \u001b[0m\u001b[90m :constructor \u001b[0m\u001b[90m :package \u001b[0m\n", + "\n", + " vision/imageclfmulti \u001b[0m Image classification (multi-label) \u001b[0m (Image, LabelMulti) supervised\u001b[0m Multi-label image classification task… ImageClassificationMulti (generic funct\u001b[0m… FastAI.Vision\u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " vision/imagekeypoint \u001b[0m Image keypoint regression \u001b[0m (Image, Keypoints) supervised\u001b[0m Keypoint regression task with a fixed… ImageKeypointRegression (generic functi\u001b[0m… FastAI.Vision\u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " vision/imagesegmentation\u001b[0m Image segmentation \u001b[0m (Image, Mask) supervised\u001b[0m Semantic segmentation task in which e… ImageSegmentation (generic function wit\u001b[0m… FastAI.Vision\u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n", + " vision/imageclfsingle \u001b[0m Image classification (single-label)\u001b[0m (Image, Label) supervised\u001b[0m Single-label image classification tas… ImageClassificationSingle (generic func\u001b[0m… FastAI.Vision\u001b[0m\n", + " \u001b[0m \u001b[0m\n", + "\n" + ] + } + ], + "source": [ + "learningtasks(blocks=(Image, Any)) |> _show" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Outlook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This work will support other future efforts:\n", + "\n", + "- **Domain libraries**: make it easy for third-party libraries to contribute features (datasets, recipes, models, tasks, encodings...) and easy for users to discover these features.\n", + "- **No-code interfaces**: having a consistent way to search for features and relating them to relevant `Block`s makes it possible to build no-code, dropdown-based interfaces to choose an appropriate dataset, find a learning task, or build a model for a task; and finally, train a model." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.8.0-beta3", + "language": "julia", + "name": "julia-1.8" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.8.0-beta3" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/FastAI.jl b/src/FastAI.jl index c2a4906da5..d7f370e1db 100644 --- a/src/FastAI.jl +++ b/src/FastAI.jl @@ -96,8 +96,10 @@ include("datasets/Datasets.jl") @reexport using .Datasets -include("fasterai/taskregistry.jl") -include("fasterai/defaults.jl") +include("Registries/Registries.jl") +@reexport using .Registries + + diff --git a/src/Registries/Registries.jl b/src/Registries/Registries.jl new file mode 100644 index 0000000000..23b3bf434b --- /dev/null +++ b/src/Registries/Registries.jl @@ -0,0 +1,66 @@ +module Registries + +using ..FastAI +using ..FastAI.Datasets: DatasetLoader, DataDepLoader, isavailable, loaddata, typify + +import Markdown +using DataDeps +using FeatureRegistries +using FeatureRegistries: Registry, Field +using InlineTest + + +_formatblock(t::Type{<:Tuple}) = _formatblock(Tuple(t.types)) +_formatblock(t::Tuple) = map(_formatblock, t) +_formatblock(T::Type) = T +_formatblock(::T) where T = T + +function blocktypesmatch( + BSupported::Type, + BWanted::Type) + # true if both types are part of the same type tree + return BSupported <: BWanted || BWanted <: BSupported +end +function blocktypesmatch(B1::Type{<:Tuple}, B2::Type{<:Tuple}) + all(blocktypesmatch(b1, b2) for (b1, b2) in zip(B1.types, B2.types)) +end + + +blocktypesmatch(BSupported::Type, ::Type{Any}) = true +blocktypesmatch(BSupported::Type{Any}, ::Type) = true +blocktypesmatch(BSupported::Type{Any}, ::Type{Any}) = true +blocktypesmatch(bsupported, bwanted) = blocktypesmatch(typify(bsupported), typify(bwanted)) + +@testset "`blocktypesmatch`" begin + @test blocktypesmatch(FastAI.Image, FastAI.Image{2}) + @test blocktypesmatch(FastAI.Image, FastAI.Image) + @test blocktypesmatch(FastAI.Image{2}, FastAI.Image) + @test blocktypesmatch(Tuple{FastAI.Image}, Tuple{FastAI.Image}) + @test blocktypesmatch(Tuple{FastAI.Image{2}}, Tuple{FastAI.Image}) + @test blocktypesmatch(Tuple{FastAI.Image{2}}, Any) + @test blocktypesmatch(FastAI.Image{2}(), FastAI.Image{2}) + @test blocktypesmatch(FastAI.Image, FastAI.Image{2}()) + @test blocktypesmatch((FastAI.Image{2}(), FastAI.Label(1:10)), (FastAI.Image, FastAI.Label)) + @test blocktypesmatch((FastAI.Image{2}(), FastAI.AbstractBlock), (FastAI.Image, FastAI.Label)) + @test !blocktypesmatch(FastAI.Image{2}(), Label(1:10)) +end + + +include("datasets.jl") +include("tasks.jl") +include("recipes.jl") + + +export datasets, + learningtasks, + datarecipes, + find, + info, + load + + +function __init__() + _registerdatasets(DATASETS) +end + +end diff --git a/src/Registries/datasets.jl b/src/Registries/datasets.jl new file mode 100644 index 0000000000..a9ee2aabc8 --- /dev/null +++ b/src/Registries/datasets.jl @@ -0,0 +1,135 @@ + +function _datasetregistry(; name = "Datasets") + registry = Registry( + ( + id = Field( + String, + name = "ID", + formatfn=FeatureRegistries.string_format), + description = Field( + String; + name = "Description", + optional = true, + description = "More information about the dataset", + formatfn=FeatureRegistries.md_format), + size = Field( + String; + name = "Size", + description = "Download size of the dataset", + optional = true), + tags = Field( + Vector{String}; + name = "Tags", + defaultfn = (row, key) -> String[]), + package = Field( + Module; + name = "Package", + formatfn = FeatureRegistries.code_format), + downloaded = Field( + Bool; + name = "Is downloaded", + description = """ + Whether the dataset has been downloaded and is available locally. + Updates after session restart. + """, + computefn = (row, key) -> isavailable(row.loader)), + loader = Field( + DatasetLoader; + name = "Dataset loader", + formatfn = x -> FeatureRegistries.type_format), + ); + name, + loadfn = function (row) + if row.loader isa DataDepLoader && startswith(row.loader.datadep, "fastai-") + # Change download format for fastai datasets without having to redownload + # The download process was slightly changed; this saves having to + # redownload to update. + dir = Datasets.loaddata(row.loader) + if isdir(joinpath(dir, row.id)) + cd(dir) do + temp = mktempdir() + mv(joinpath(dir, row.id), temp, force=true) + mv(temp, pwd(), force=true) + end + end + end + loaddata(row.loader) + end, + description = """ + A registry for datasets. `load`ing an entry will download a dataset (if it + hasn't been already), and return a path to where the files were downloaded. + + ```julia + path = load(datasets()[id]) + ``` + + See `datarecipes` to load these datasets in a format compatible with learning + tasks. + """ + ) + return registry +end + + +const DATASETS = _datasetregistry() + + + +""" + datasets(; filters...) + +Show a registry of available datasets. Pass in filters as keyword +arguments to look at a subset. + +See also [finding functionality](/documents/docs/discovery.md), [`learningtasks`](#), +and [`datarecipes`](#). For more information on registries, see +[FeatureRegistries.jl](https://github.com/lorenzoh/FeatureRegistries.jl). + + +## Examples + +Show all available learning tasks: + +{cell} +```julia +using FastAI +datasets() +``` + +Download a dataset: + +{cell} +```julia +path = load(datasets()["imagenette2-160"]) +``` + +Get an explanation of fields in the dataset registry: + +{cell} +```julia +info(datasets()) +``` + +Show all datasets with `"image"` in their name: + +{cell} +```julia +datasets(id="image") +``` +""" +datasets(; kwargs...) = isempty(kwargs) ? DATASETS : filter(DATASETS; kwargs...) + + +function _registerdatasets(registry::Registry) + for config in FastAI.Datasets.DATASETCONFIGS + id = config.datadepname + haskey(registry, id) && continue + push!(registry, ( + id, + description = config.description == "" ? missing : config.description, + loader = DataDepLoader("fastai-$(config.datadepname)"), + size=config.size === "???" ? missing : config.size, + package = FastAI, + )) + end +end diff --git a/src/Registries/models.jl b/src/Registries/models.jl new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Registries/recipes.jl b/src/Registries/recipes.jl new file mode 100644 index 0000000000..f7b97bb5d9 --- /dev/null +++ b/src/Registries/recipes.jl @@ -0,0 +1,114 @@ + +function _datareciperegistry(datasetregistry; name = "Dataset recipes") + Registry( + (; + id = Field(String, name = "ID", formatfn = x -> sprint(show, x)), + blocks = Field( + Any, + name = "Block types", + description = "Types of blocks of the data container that this recipe loads.", + filterfn = blocktypesmatch, + formatfn = b -> FeatureRegistries.code_format(_formatblock(b))), + description = Field( + String, + name = "Description", + optional = true, + description = "More information about the dataset recipe", + formatfn=FeatureRegistries.md_format), + downloaded = Field( + Bool, + name = "Is downloaded", + description = """ + Whether the dataset this recipe is based has been downloaded and is + available locally. + """, + computefn = (row, key) -> isavailable(datasetregistry[row.datasetid].loader)), + datasetid = Field( + String, + name = "Dataset ID", + description = "ID of the dataset this recipe is based on.", + computefn = function (row, key) + val = row[key] + if !haskey(datasetregistry, val) + throw(ArgumentError("Could not find dataset with ID \"$val\"!")) + end + return val + end, + ), + package = Field( + Module, + name = "Package"), + recipe = Field( + Datasets.DatasetRecipe, + name = "Recipe", + formatfn = FeatureRegistries.type_format + ) + ); + name, + loadfn = function loadrecipeentry(row) + dataset = load(datasetregistry[row.datasetid]) + Datasets.loadrecipe(row.recipe, dataset) + end, + description = """ + A registry for dataset recipes. `load`ing an entry will download the + corresponding dataset (see `datasets`) and return `data, blocks`, a + data container and the `Block`s of the observations. + + ```julia + data, blocks = load(datarecipes()[id]) + ``` + + See `learningtasks` to find compatible learning tasks. + """ + ) +end + +const DATARECIPES = _datareciperegistry(DATASETS) + + + +""" + datarecipes(; filters...) + +Show a registry of available dataset recipes. A dataset recipe defines how +to load a dataset into a suitable format for use with a learning task. + +Pass in filters as keyword arguments to look at a subset. + +See also [finding functionality](/documents/docs/discovery.md), [`datasets`](#), +and [`learningtasks`](#). For more information on registries, see +[FeatureRegistries.jl](https://github.com/lorenzoh/FeatureRegistries.jl). + +## Examples + +Show all available dataset recipes: + +{cell} +```julia +using FastAI +datarecipes() +``` + +Show all recipes for datasets that have "image" in their name: + +{cell} +```julia +datarecipes(datasetid="image") +``` + +Show all data recipes usable for classification tasks, that is where the +target block is a [`Label`](#): + +{cell} +```julia +datarecipes(blocks=(Any, Label)) +``` + +Get an explanation of fields in the dataset recipe registry: + +{cell} +```julia +info(datarecipes()) +``` +""" +datarecipes(; kwargs...) = isempty(kwargs) ? DATARECIPES : filter(DATARECIPES; kwargs...) diff --git a/src/Registries/tasks.jl b/src/Registries/tasks.jl new file mode 100644 index 0000000000..4322d818ca --- /dev/null +++ b/src/Registries/tasks.jl @@ -0,0 +1,98 @@ +function _taskregistry(; name = "Learning tasks") + registry = Registry( + (; + id = Field(String, name = "ID", formatfn = x -> sprint(show, x)), + name = Field( + String, + name = "Name", + description = "The name of the learning task", + computefn = (row, key) -> get(row, key, row.id)), + blocks = Field( + Any, + name = "Block types", + description = "Types of the blocks that are compatible with this task", + filterfn = blocktypesmatch, + formatfn = b -> FeatureRegistries.code_format(_formatblock(b))), + category = Field( + String, + name = "Category", + description = "Kind of task, e.g. \"supervised\""), + description = Field( + String, + name = "Description", + optional = true, + description = "More information about the learning task", + formatfn=FeatureRegistries.md_format), + constructor = Field( + Any, + name = "Learning task", + description = "Function instance to create a corresponding learning task.", + formatfn = FeatureRegistries.code_format), + package = Field( + Module, + name = "Package", + formatfn = FeatureRegistries.code_format), + ); + name, + loadfn = row -> row.constructor, + description = """ + A registry for learning tasks. `load`ing an entry will return a function + that can be used to construct a `LearningTask` given `blocks`. + + ```julia + taskfn = load(learningtasks(id)) + task = taskfn(blocks; kwargs...) + ``` + + Inspect `?taskfn` for documentation on the arguments that the function accepts. + + See `datarecipes` to load these datasets in a format compatible with learning + tasks. + """ + ) +end + +const TASKS = _taskregistry() + +""" + learningtasks(; filters...) + +Show a registry of available learning tasks. Pass in filters as keyword +arguments to look at a subset. + +See also [finding functionality](/documents/docs/discovery.md), [`datasets`](#), +and [`datarecipes`](#). For more information on registries, see +[FeatureRegistries.jl](https://github.com/lorenzoh/FeatureRegistries.jl). + +## Examples + +Show all available learning tasks: + +{cell} +```julia +using FastAI +learningtasks() +``` + +Show all computer vision tasks: + +{cell} +```julia +learningtasks(package=FastAI.Vision) +``` + +Show all classification tasks, i.e. where the target block is a [`Label`](#): + +{cell} +```julia +learningtasks(blocks=(Any, Label)) +``` + +Get an explanation of fields in the learning task registry: + +{cell} +```julia +info(learningtasks()) +``` +""" +learningtasks(; kwargs...) = isempty(kwargs) ? TASKS : filter(TASKS; kwargs...) diff --git a/src/Tabular/Tabular.jl b/src/Tabular/Tabular.jl index f2d416cf3e..944e40a99a 100644 --- a/src/Tabular/Tabular.jl +++ b/src/Tabular/Tabular.jl @@ -11,7 +11,7 @@ using ..FastAI: # visualization ShowText, # other - Context, Training, Validation, FASTAI_METHOD_REGISTRY, registerlearningtask! + Context, Training, Validation # for tests using ..FastAI: testencoding @@ -43,6 +43,8 @@ include("encodings/tabularpreprocessing.jl") include("models.jl") + +const _tasks = Dict{String, Any}() include("tasks/classification.jl") include("tasks/regression.jl") include("recipes.jl") @@ -50,6 +52,11 @@ include("recipes.jl") function __init__() _registerrecipes() + foreach(values(_tasks)) do t + if !haskey(learningtasks(), t.id) + push!(learningtasks(), t) + end + end @require Makie="ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" begin import .Makie import .Makie: @recipe, @lift diff --git a/src/Tabular/recipes.jl b/src/Tabular/recipes.jl index 22ffb76b6b..f9c7ae9bb9 100644 --- a/src/Tabular/recipes.jl +++ b/src/Tabular/recipes.jl @@ -84,9 +84,6 @@ function Datasets.loadrecipe(recipe::TableRegressionRecipe, args...; kwargs...) end - - - # Utils @@ -115,7 +112,15 @@ const RECIPES = Dict{String,Vector{Datasets.DatasetRecipe}}( function _registerrecipes() for (name, recipes) in RECIPES, recipe in recipes - Datasets.registerrecipe!(Datasets.FASTAI_DATA_REGISTRY, name, recipe) + if !haskey(datarecipes(), name) + push!(datarecipes(), ( + id = name, + datasetid = name, + blocks = Datasets.recipeblocks(recipe), + package = @__MODULE__, + recipe = recipe, + )) + end end end diff --git a/src/Tabular/tasks/classification.jl b/src/Tabular/tasks/classification.jl index 54ef530b89..e467c54dd9 100644 --- a/src/Tabular/tasks/classification.jl +++ b/src/Tabular/tasks/classification.jl @@ -41,6 +41,19 @@ function TabularClassificationSingle( return TabularClassificationSingle(blocks, (tabledata, nothing)) end +_tasks["tabularclfsingle"] = ( + id = "tabular/clfsingle", + name = "Tabular classification (single-label)", + constructor = TabularClassificationSingle, + blocks = (TableRow, Label), + category = "supervised", + description = """ + Task where a table row with categorical and continuous variables is classified + as one of a number of classes. + """, + package=@__MODULE__, +) + # ## Tests diff --git a/src/Tabular/tasks/regression.jl b/src/Tabular/tasks/regression.jl index e17fc4eb2b..0bebdd3cce 100644 --- a/src/Tabular/tasks/regression.jl +++ b/src/Tabular/tasks/regression.jl @@ -38,6 +38,19 @@ function TabularRegression( end +_tasks["tabularregression"] = ( + id = "tabular/regression", + name = "Tabular regression", + constructor = TabularClassificationSingle, + blocks = (TableRow, Continuous), + category = "supervised", + description = """ + Task where a number of continuous variables are regressed from a table row + with categorical and continuous variables. + """, + package=@__MODULE__, +) + # ## Tests @testset "TabularRegression [task]" begin diff --git a/src/Textual/Textual.jl b/src/Textual/Textual.jl index e0afcb575a..5ec089bcad 100644 --- a/src/Textual/Textual.jl +++ b/src/Textual/Textual.jl @@ -11,7 +11,7 @@ using ..FastAI: # visualization ShowText, # other - Context, Training, Validation, FASTAI_METHOD_REGISTRY, registerlearningtask! + Context, Training, Validation import Requires: @require @@ -28,4 +28,3 @@ end export Paragraph end - diff --git a/src/Textual/recipes.jl b/src/Textual/recipes.jl index 081d7f5bd0..1817da7870 100644 --- a/src/Textual/recipes.jl +++ b/src/Textual/recipes.jl @@ -2,7 +2,7 @@ TextFolders(textfile; labelfn = parentname, split = false) Recipe for loading a single-label text classification dataset -stored in hierarchical folder format. +stored in hierarchical folder format. """ Base.@kwdef struct TextFolders <: Datasets.DatasetRecipe labelfn = parentname @@ -38,7 +38,15 @@ const RECIPES = Dict{String,Vector{Datasets.DatasetRecipe}}( function _registerrecipes() for (name, recipes) in RECIPES, recipe in recipes - Datasets.registerrecipe!(Datasets.FASTAI_DATA_REGISTRY, name, recipe) + if !haskey(datarecipes(), name) + push!(datarecipes(), ( + id = name, + datasetid = name, + blocks = Datasets.recipeblocks(recipe), + package = @__MODULE__, + recipe = recipe, + )) + end end end @@ -48,4 +56,4 @@ end @testset "TextFolders [Recipe]" begin @test length(finddatasets(name="imdb")) >= 1 -end \ No newline at end of file +end diff --git a/src/Vision/Vision.jl b/src/Vision/Vision.jl index 8f93a7ebd2..13cc2149c7 100644 --- a/src/Vision/Vision.jl +++ b/src/Vision/Vision.jl @@ -36,7 +36,7 @@ using ..FastAI: ShowText, # other Context, Training, Validation, Inference, - FASTAI_METHOD_REGISTRY, registerlearningtask!, Datasets + Datasets import Flux import FastAI.Datasets @@ -84,6 +84,8 @@ include("encodings/projective.jl") include("models/Models.jl") include("models.jl") + +const _tasks = Dict{String, Any}() include("tasks/utils.jl") include("tasks/classification.jl") include("tasks/segmentation.jl") @@ -95,6 +97,11 @@ include("tests.jl") function __init__() _registerrecipes() + foreach(values(_tasks)) do t + if !haskey(FastAI.learningtasks(), t.id) + push!(FastAI.learningtasks(), t) + end + end @require Makie="ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" begin import .Makie import .Makie: @recipe, @lift diff --git a/src/Vision/makie.jl b/src/Vision/makie.jl index 61800d2afd..d1548e97a0 100644 --- a/src/Vision/makie.jl +++ b/src/Vision/makie.jl @@ -74,7 +74,6 @@ function cleanaxis(f; kwargs...) ax.yzoomlock = true ax.xrectzoom = false ax.yrectzoom = false - ax.panbutton = nothing ax.xpanlock = true ax.ypanlock = true ax.bottomspinevisible = false diff --git a/src/Vision/models/Models.jl b/src/Vision/models/Models.jl index af5c6165c3..c65408d640 100644 --- a/src/Vision/models/Models.jl +++ b/src/Vision/models/Models.jl @@ -17,6 +17,6 @@ include("xresnet.jl") include("unet.jl") -export xresnet18, xresnet50, UNetDynamic, TabularModel +export xresnet18, xresnet50, UNetDynamic end diff --git a/src/Vision/recipes.jl b/src/Vision/recipes.jl index ad7bafe44d..b934aa3c48 100644 --- a/src/Vision/recipes.jl +++ b/src/Vision/recipes.jl @@ -139,8 +139,17 @@ const RECIPES = Dict{String,Vector{Datasets.DatasetRecipe}}( function _registerrecipes() for (name, recipes) in RECIPES, recipe in recipes - Datasets.registerrecipe!(Datasets.FASTAI_DATA_REGISTRY, name, recipe) + if !haskey(datarecipes(), name) + push!(datarecipes(), ( + id = name, + datasetid = name, + blocks = Datasets.recipeblocks(recipe), + package = @__MODULE__, + recipe = recipe, + )) + end end + end diff --git a/src/Vision/tasks/classification.jl b/src/Vision/tasks/classification.jl index f61a156e2c..412f30159e 100644 --- a/src/Vision/tasks/classification.jl +++ b/src/Vision/tasks/classification.jl @@ -44,7 +44,19 @@ function ImageClassificationSingle(size::NTuple{N,Int}, classes::AbstractVector; return ImageClassificationSingle(blocks, size=size) end -registerlearningtask!(FASTAI_METHOD_REGISTRY, ImageClassificationSingle, (Image, Label)) + +_tasks["imageclfsingle"] = ( + id = "vision/imageclfsingle", + name = "Image classification (single-label)", + constructor = ImageClassificationSingle, + blocks = (Image, Label), + category = "supervised", + description = """ + Single-label image classification task where every image has a single + class label associated with it. + """, + package=@__MODULE__, +) # --- @@ -93,7 +105,20 @@ function ImageClassificationMulti(size::NTuple{N,Int}, classes::AbstractVector; end -registerlearningtask!(FASTAI_METHOD_REGISTRY, ImageClassificationMulti, (Image, LabelMulti)) + +_tasks["imageclfmulti"] = ( + id = "vision/imageclfmulti", + name = "Image classification (multi-label)", + constructor = ImageClassificationMulti, + blocks = (Image, LabelMulti), + category = "supervised", + description = """ + Multi-label image classification task where every image can + have multiple class labels associated with it. + """, + package=@__MODULE__, +) + # ## Tests diff --git a/src/Vision/tasks/keypointregression.jl b/src/Vision/tasks/keypointregression.jl index 8c0dd92b16..9881e73c7a 100644 --- a/src/Vision/tasks/keypointregression.jl +++ b/src/Vision/tasks/keypointregression.jl @@ -30,7 +30,19 @@ function ImageKeypointRegression(size::NTuple{N,Int}, nkeypoints::Int; kwargs... return ImageKeypointRegression(blocks; size = size, kwargs...) end -registerlearningtask!(FASTAI_METHOD_REGISTRY, ImageKeypointRegression, (Image, Keypoints)) + + +_tasks["imagekeypoint"] = ( + id = "vision/imagekeypoint", + name = "Image keypoint regression", + constructor = ImageKeypointRegression, + blocks = (Image, Keypoints), + category = "supervised", + description = """ + Keypoint regression task with a fixed number of keypoints to be detected. + """, + package=@__MODULE__, +) # ## Tests diff --git a/src/Vision/tasks/segmentation.jl b/src/Vision/tasks/segmentation.jl index 714f17ea14..2fb802ace8 100644 --- a/src/Vision/tasks/segmentation.jl +++ b/src/Vision/tasks/segmentation.jl @@ -41,7 +41,21 @@ function ImageSegmentation(size::NTuple{N,Int}, classes::AbstractVector; kwargs. return ImageSegmentation(blocks; size=size, kwargs...) end -registerlearningtask!(FASTAI_METHOD_REGISTRY, ImageSegmentation, (Image, Mask)) + +_tasks["imagesegmentation"] = ( + id = "vision/imagesegmentation", + name = "Image segmentation", + constructor = ImageSegmentation, + blocks = (Image, Mask), + category = "supervised", + description = """ + Semantic segmentation task in which every pixel in an image is + classified. + """, + package=@__MODULE__, +) + + # ## Tests diff --git a/src/datasets/Datasets.jl b/src/datasets/Datasets.jl index 8dc54d3f68..7b96857a29 100644 --- a/src/datasets/Datasets.jl +++ b/src/datasets/Datasets.jl @@ -45,8 +45,10 @@ include("transformations.jl") include("load.jl") include("recipe.jl") -include("registry.jl") -include("fastairegistry.jl") + +include("deprecations.jl") + +include("loaders.jl") export @@ -76,7 +78,7 @@ export grandparentname, # datasets - DATASETS, + #DATASETS, loadfolderdata, datasetpath, diff --git a/src/datasets/deprecations.jl b/src/datasets/deprecations.jl new file mode 100644 index 0000000000..fc59d58dd7 --- /dev/null +++ b/src/datasets/deprecations.jl @@ -0,0 +1,89 @@ + + +""" + finddatasets(name=nothing, blocks=Any) + +Find preconfigured dataset recipes for datasets that match block +types `blocks` in all data sources (if `name == nothing`) or dataset source +with `name`. `blocks` can be given as a type or a nested tuple of block types. + +!!! warning "Deprecated" + + This function is deprecated and will be removed in a future version + of FastAI.jl. Use `filter(datarecipes(); blocks, id = name)` instead. + +Return a vector of `Pair`s `datasetname => recipe` + +#### Examples + +Loading a result + +```julia +datasetname, recipe = finddatasets(blocks=(Image, Label))[1] +data, blocks = loadrecipe(recipe, datasetpath(datasetname)) +``` + +Example searches + +``` +# Single-label image classification +finddatasets(blocks=(Image, Label)) + +# Single-label classification from any data +finddatasets(blocks=(Any, Label)) + +# Datasets with images as input data +finddatasets(blocks=(Image, Any)) + +# All ways to load `pascal2007` +finddatasets(name="pascal2007") +``` +""" +finddatasets(; name = "", blocks = Any) = datarecipes(; blocks, id = name) + +""" + loaddataset(name[, blocks = Any]) -> (data, blocks) + +Load dataset `name` with a recipe that matches block types +`blocks`. The first matching recipe is selected and loaded. + +!!! warning "Deprecated" + + This function is deprecated and will be removed in a future version + of FastAI.jl. Use `findfirst(datarecipes(); name, blocks)` instead. + +## Examples + +Load a data container suitable for single-label image classification: +```julia +data, blocks = loaddataset("imagenette2-160", (Image, Label)) +``` + +Load dataset with any recipe: +```julia +data, blocks = loaddataset(name) +``` +""" +loaddataset(name::String, blocks = Any) = load(findfirst(datarecipes(); id = name, blocks)) + +""" + listdatasources() + +!!! warning "Deprecated" + + This function is deprecated and will be removed in a future version + of FastAI.jl. + +List the dataset sources registered in `registry` (defaults to +`FastAI.defaultdataregistry()`). +""" +listdatasources() = getfield(datasets(), :data).id + + +""" + datasetpath(name) + +(Down)load registered dataset source named `name` from the dataset registry. +Use [`listdatasources`](#) for a list of all dataset sources. +""" +datasetpath(name::String) = load(datasets()[name]) diff --git a/src/datasets/fastaidatasets.jl b/src/datasets/fastaidatasets.jl index f411957472..47359ace18 100644 --- a/src/datasets/fastaidatasets.jl +++ b/src/datasets/fastaidatasets.jl @@ -48,9 +48,9 @@ const DATASETCONFIGS = [ FastAIDataset("imagewoof-160", "imageclas", "a0d360f9d8159055b3bf2b8926a51d19b2f1ff98a1eef6034e4b891c59ca3f1a", description=DESCRIPTIONS["imagewoof"]), FastAIDataset("imagewoof-320", "imageclas", description=DESCRIPTIONS["imagewoof"]), FastAIDataset("imagewoof", "imageclas", description=DESCRIPTIONS["imagewoof"]), - FastAIDataset("imagewoof2-160", "imageclas", "663c22f69c2802d85e2a67103c017e047096702ffddf9149a14011b7002539bf", description=DESCRIPTIONS["imagewoof"]), + FastAIDataset("imagewoof2-160", "imageclas", "b5ffa16037e07f60882434f55b7814a3d44483f2a484129f251604bc0d0f8172", description=DESCRIPTIONS["imagewoof"]), FastAIDataset("imagewoof2-320", "imageclas", "7db6120fdb9ae079e26346f89e7b00d7f184f8137791609b97fd0405d3f92305", description=DESCRIPTIONS["imagewoof"], size="313MB"), - FastAIDataset("imagewoof2", "imageclas", description=DESCRIPTIONS["imagewoof"]), + FastAIDataset("imagewoof2", "imageclas", "de3f58c4ea3e042cf3f8365fbc699288cfe1d8c151059040d181c221bd5a55b8", description=DESCRIPTIONS["imagewoof"], size="1.25GiB"), FastAIDataset("mnist_png", "imageclas", "9e18edaa3a08b065d8f80a019ca04329e6d9b3e391363414a9bd1ada30563672"), FastAIDataset("mnist_var_size_tiny", "imageclas", "8a0f6ca04c2d31810dc08e739c7fa9b612e236383f70dd9fc6e5a62e672e2283"), FastAIDataset("oxford-102-flowers", "imageclas"), @@ -127,7 +127,13 @@ function DataDeps.DataDep(d::FastAIDataset) """, "$(ROOT_URL)$(d.subfolder)/$(d.name).$(d.extension)", d.checksum, - post_fetch_method=DataDeps.unpack, + post_fetch_method=function (f) + DataDeps.unpack(f) + extracted = readdir(pwd())[1] + temp = mktempdir() + mv(extracted, temp, force=true) + mv(temp, pwd(), force=true) + end, ) end diff --git a/src/datasets/fastairegistry.jl b/src/datasets/fastairegistry.jl deleted file mode 100644 index 13e1ca02c8..0000000000 --- a/src/datasets/fastairegistry.jl +++ /dev/null @@ -1,11 +0,0 @@ - -""" - const FASTAI_DATA_REGISTRY - -The default `DataRegistry` containing every dataset in -the fastai dataset collection. -""" -const FASTAI_DATA_REGISTRY = DatasetRegistry( - Dict(d => () -> datasetpath(d) for d in DATASETS), - Dict{String,Vector{DatasetRecipe}}(), -) diff --git a/src/datasets/loaders.jl b/src/datasets/loaders.jl new file mode 100644 index 0000000000..f1dd260092 --- /dev/null +++ b/src/datasets/loaders.jl @@ -0,0 +1,53 @@ + +# ## `DatasetLoader` interface + +""" + abstract type DatasetLoader + +A `DatasetLoader` defines how a dataset can made available and loaded. +See [`DataDepLoader`](#) as an example. + +A `DatasetLoader` has to implement the following functions: + +- [`loaddata`](#) +- [`makeavailable`](#) +- [`isavailable`](#) +""" +abstract type DatasetLoader end + +function makeavailable end +function isavailable end +function loaddata end + + +# ## Loader for `DataDep`s + + +""" + struct DataDepLoader(datadep) <: DatasetLoader + +A dataset loader that uses DataDeps.jl to load datasets. +The DataDep has to be registered before creating the loader, and will +error otherwise. +""" +struct DataDepLoader <: DatasetLoader + datadep::String + function DataDepLoader(datadep) + if !haskey(DataDeps.registry, datadep) + throw(ArgumentError("DataDep \"$datadep\" does not exist.")) + end + return new(datadep) + end +end + + +isavailable(loader::DataDepLoader) = !isnothing(DataDeps.try_determine_load_path(loader.datadep, @__FILE__)) + +function makeavailable(loader::DataDepLoader) + return DataDeps.resolve(loader.datadep, @__FILE__) +end + +function loaddata(loader::DataDepLoader) + isavailable(loader) || makeavailable(loader) + return DataDeps.resolve(loader.datadep, @__FILE__) +end diff --git a/src/datasets/registry.jl b/src/datasets/registry.jl index c9c5cf36c6..161cd96079 100644 --- a/src/datasets/registry.jl +++ b/src/datasets/registry.jl @@ -1,175 +1,4 @@ -abstract type AbstractDatasetRegistry end - - -""" - DatasetRegistry() - -A store for information on datasets and dataset recipes for loading those datasets. -""" -Base.@kwdef struct DatasetRegistry <: AbstractDatasetRegistry - datasets::Dict{String,Any} = Dict{String,Any}() - recipes::Dict{String,Vector{DatasetRecipe}} = Dict{String,Vector{DatasetRecipe}}() -end - -## Queries - -""" - listdatasources([registry]) - -List the dataset sources registered in `registry` (defaults to -`FastAI.defaultdataregistry()`). -""" -listdatasources(reg::DatasetRegistry) = collect(keys(reg.datasets)) - - -""" - datasetpath([registry], name) - -(Down)load registered dataset source named `name` from the dataset registry. -Use [`listdatasources`](#) for a list of all dataset sources. -""" -datasetpath(reg::DatasetRegistry, name::String) = reg.datasets[name]() - -""" - finddatasets([registry]; name=nothing, blocks=Any) - -Find preconfigured dataset recipes for datasets that match block -types `blocks` in all data sources (if `name == nothing`) or dataset source -with `name`. `blocks` can be given as a type or a nested tuple of block types. - -Return a vector of `Pair`s `datasetname => recipe` - -#### Examples - -Loading a result - -```julia -datasetname, recipe = finddatasets(blocks=(Image, Label))[1] -data, blocks = loadrecipe(recipe, datasetpath(datasetname)) -``` - -Example searches - -``` -# Single-label image classification -finddatasets(blocks=(Image, Label)) - -# Single-label classification from any data -finddatasets(blocks=(Any, Label)) - -# Datasets with images as input data -finddatasets(blocks=(Image, Any)) - -# All ways to load `pascal2007` -finddatasets(name="pascal2007") -``` -""" -function finddatasets(reg::DatasetRegistry; name=nothing, blocks=Any)::Vector{Pair{String,DatasetRecipe}} - results = collect(Iterators.flatten(((k => v) for v in vs) for (k, vs) in reg.recipes)) - results = filter(((d, recipe),) -> typify(recipeblocks(recipe)) <: typify(blocks), results) - if !isnothing(name) - results = filter(((d, r),) -> d == name, results) - end - return results -end - - -""" - loaddataset(name[, blocks = Any]) -> (data, blocks) - -Load dataset `name` with a recipe that matches block types -`blocks`. The first matching recipe is selected and loaded. - -## Examples - -Load a data container suitable for single-label image classification: -```julia -data, blocks = loaddataset("imagenette2-160", (Image, Label)) -``` - -Load dataset with any recipe: -```julia -data, blocks = loaddataset(name) -``` -""" -function loaddataset(reg::DatasetRegistry, name::String, blocks=Any) - results = finddatasets(reg; name=name, blocks=blocks) - isempty(results) && error("Could not find a recipe for dataset \"$name\" that matches block types `$blocks`") - name_, recipe = results[1] - @assert name == name_ - return loadrecipe(recipe, datasetpath(reg, name)) -end - -## Registration - -""" - registerdataset!([registry,] name, loadfn) - -Register a dataset in `registry::DatasetRegistry` with a `name` and a -function `loadfn()` that downloads it and returns a path. -""" -function registerdataset!(reg::DatasetRegistry, dataset::String, loadfn) - dataset ∈ keys(reg.datasets) && error( - "Dataset name $(dataset) is already registered") - reg.datasets[dataset] = loadfn - return reg -end - - -""" - registerrecipe!([registry,] name, recipe) - -Register a recipe in `registry::DatasetRegistry` for dataset `name`. -""" -function registerrecipe!(reg::DatasetRegistry, dataset::String, recipe::DatasetRecipe) - dataset ∈ keys(reg.datasets) || error("Dataset $dataset not found.") - recipes = get!(reg.recipes, dataset, DatasetRecipe[]) - push!(recipes, recipe) - return reg -end - -function blocksmatch(recipe::DatasetRecipe, TBlocks) - @show recipeblocks(recipe), TBlocks - return recipeblocks(recipe) <: TBlocks -end # ## Tests - - - -@testset "DatasetRegistry" begin - reg = Datasets.DatasetRegistry() - @testset "registerdataset!" begin - @test_nowarn Datasets.registerdataset!( - reg, "mnist_var_size_tiny", () -> datasetpath("mnist_var_size_tiny")) - - - # Reregistering should error - @test_throws ErrorException Datasets.registerdataset!( - reg, "mnist_var_size_tiny", () -> datasetpath("mnist_var_size_tiny")) - end - - @testset "listdatasources" begin - @test length(Datasets.listdatasources(reg)) == 1 - end - - @testset "datasetpath" begin - @test_nowarn datasetpath(reg, "mnist_var_size_tiny") - end - - @testset "registerrecipe!" begin - @test_nowarn Datasets.registerrecipe!( - reg, "mnist_var_size_tiny", Vision.ImageFolders()) - end - - @testset "finddatasets" begin - @test finddatasets(reg) |> length == 1 - @test finddatasets(reg, name="mnist_var_size_tiny") |> length == 1 - @test finddatasets(reg, blocks=Tuple{Image, Label}) |> length == 1 - @test finddatasets(reg, blocks=Tuple{Image, LabelMulti}) |> length == 0 - @test finddatasets(reg, name="mnist_var_size_tiny", blocks=Tuple{Image, Label}) |> length == 1 - @test finddatasets(reg, name="mnist", blocks=Tuple{Image, Label}) |> length == 0 - end -end diff --git a/src/deprecations.jl b/src/deprecations.jl index 93d4252496..76c3746182 100644 --- a/src/deprecations.jl +++ b/src/deprecations.jl @@ -1,3 +1,4 @@ +# From renaming method -> task Base.@deprecate methodmodel(args...; kwargs...) taskmodel(args...; kwargs...) Base.@deprecate methoddataset(args...; kwargs...) taskdataset(args...; kwargs...) Base.@deprecate methoddataloaders(args...; kwargs...) taskdataloaders(args...; kwargs...) @@ -8,3 +9,49 @@ Base.@deprecate findlearningmethods(args...; kwargs...) findlearningtasks(args.. Base.@deprecate methodlearner(args...; kwargs...) tasklearner(args...; kwargs...) Base.@deprecate savemethodmodel(args...; kwargs...) savetaskmodel(args...; kwargs...) Base.@deprecate loadmethodmodel(args...; kwargs...) loadtaskmodel(args...; kwargs...) + + + +""" + findlearningtasks(blocks) + +Find learning tasks compatible with block types `blocks`. + +!!! warning "Deprecated" + + This function is deprecated and will be removed in a future version + of FastAI.jl. Use `learningtasks(; blocks)` instead. + + +#### Examples + +```julia-repl +julia> findlearningtasks((Image, Label)) +[ImageClassificationSingle,] + +julia> findlearningtasks((Image, Any)) +[ImageClassificationSingle, ImageClassificationMulti, ImageSegmentation, ImageKeypointRegression, ...] +``` +""" +findlearningtasks(blocktypes) = learningtasks(blocks=blocktypes).data.constructor + + +@testset "Datasets [registry]" begin + + @testset "listdatasources" begin + @test length(listdatasources()) > 1 + end + + @testset "datasetpath" begin + @test_nowarn datasetpath("mnist_var_size_tiny") + end + + @testset "finddatasets" begin + @test finddatasets() |> length >= 1 + @test finddatasets(name="mnist_var_size_tiny") |> length >= 1 + @test finddatasets(blocks=Tuple{Image, Label}) |> length >= 1 + @test finddatasets(blocks=Tuple{Image, LabelMulti}) |> length >= 0 + @test finddatasets(name="mnist_var_size_tiny", blocks=Tuple{Image, Label}) |> length >= 1 + @test finddatasets(name="mnist", blocks=Tuple{Image, Label}) |> length >= 0 + end +end diff --git a/src/fasterai/defaults.jl b/src/fasterai/defaults.jl deleted file mode 100644 index 7e6697c470..0000000000 --- a/src/fasterai/defaults.jl +++ /dev/null @@ -1,9 +0,0 @@ -defaultdataregistry() = Datasets.FASTAI_DATA_REGISTRY -defaulttaskregistry() = FASTAI_METHOD_REGISTRY - - -Datasets.listdatasources() = listdatasources(defaultdataregistry()) -Datasets.finddatasets(; kwargs...) = finddatasets(defaultdataregistry(); kwargs...) -Datasets.loaddataset(args...; kwargs...) = loaddataset(defaultdataregistry(), args...; kwargs...) - -findlearningtasks(args...; kwargs...) = findlearningtasks(defaulttaskregistry(), args...; kwargs...) diff --git a/src/fasterai/taskregistry.jl b/src/fasterai/taskregistry.jl deleted file mode 100644 index 7faf12264d..0000000000 --- a/src/fasterai/taskregistry.jl +++ /dev/null @@ -1,70 +0,0 @@ - -Base.@kwdef struct LearningTaskRegistry - tasks::Dict{Any,Any} = Dict() -end - -""" - registerlearningtask!(registry, taskfn, blocktypes) - -Register a learning task constructor `taskfn` as compatible with -`blocktypes` in `registry::LearningTaskRegistry`. - -`blocks` should be the least specific set of types that a `taskfn` -can handle. `taskfn` needs to have a task that takes concrete block -instances as the only non-keyword argument, i.e. `taskfn(blocks; kwargs...)`. -""" -function registerlearningtask!(reg::LearningTaskRegistry, f, blocktypes) - reg.tasks[f] = typify(blocktypes) - return reg -end - -""" - findlearningtasks(blocktypes) - findlearningtasks(registry, blocktypes) - -Find learning tasks compatible with block types `TBlocks` in -`registry::LearningTaskRegistry`. - -#### Examples - -```julia-repl -julia> findlearningtasks((Image, Label)) -[ImageClassificationSingle,] - -julia> findlearningtasks((Image, Any)) -[ImageClassificationSingle, ImageClassificationMulti, ImageSegmentation, ImageKeypointRegression, ...] -``` -""" -function findlearningtasks(reg::LearningTaskRegistry, blocktypes=Any) - return [taskfn for (taskfn, taskblocks) in reg.tasks if blocktypesmatch(taskblocks, blocktypes)] -end - - -function blocktypesmatch( - BSupported::Type{<:AbstractBlock}, - BWanted::Type{<:AbstractBlock}) - BWanted <: BSupported -end -function blocktypesmatch(B1::Type{<:Tuple}, B2::Type{<:Tuple}) - all(blocktypesmatch(b1, b2) for (b1, b2) in zip(B1.types, B2.types)) -end - -blocktypesmatch(BSupported::Type, _::Type{Any}) = true -blocktypesmatch(bsupported, bwanted) = blocktypesmatch(typify(bsupported), bwanted) -blocktypesmatch(BSupported::Type, bwanted) = blocktypesmatch(BSupported, typify(bwanted)) - -@testset "`blocktypesmatch`" begin - @test blocktypesmatch(FastAI.Image, FastAI.Image{2}) - @test blocktypesmatch(FastAI.Image, FastAI.Image) - @test !blocktypesmatch(FastAI.Image{2}, FastAI.Image) - @test blocktypesmatch(Tuple{FastAI.Image}, Tuple{FastAI.Image}) - @test !blocktypesmatch(Tuple{FastAI.Image{2}}, Tuple{FastAI.Image}) - @test blocktypesmatch(Tuple{FastAI.Image{2}}, Any) - @test blocktypesmatch(FastAI.Image{2}(), FastAI.Image{2}) - @test blocktypesmatch(FastAI.Image, FastAI.Image{2}()) - @test blocktypesmatch((FastAI.Image, FastAI.Label), (FastAI.Image{2}(), FastAI.Label(1:10))) - @test blocktypesmatch((FastAI.Image, FastAI.Label), (FastAI.Image{2}(), Any)) -end - - -const FASTAI_METHOD_REGISTRY = LearningTaskRegistry() diff --git a/src/interpretation/makie/showmakie.jl b/src/interpretation/makie/showmakie.jl index b00f0aa31b..47ed1ca08f 100644 --- a/src/interpretation/makie/showmakie.jl +++ b/src/interpretation/makie/showmakie.jl @@ -154,7 +154,6 @@ function cleanaxis(f; kwargs...) ax.yzoomlock = true ax.xrectzoom = false ax.yrectzoom = false - ax.panbutton = nothing ax.xpanlock = true ax.ypanlock = true ax.bottomspinevisible = false diff --git a/test/Project.toml b/test/Project.toml index cfa6b3e4dd..582105a1e9 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -20,3 +20,7 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" + + +[compat] +Makie = "0.16" \ No newline at end of file diff --git a/test/fasterai.jl b/test/fasterai.jl index 1211dddbc7..147cf03883 100644 --- a/test/fasterai.jl +++ b/test/fasterai.jl @@ -3,9 +3,9 @@ @testset "FasterAI" begin @test length(listdatasources()) > 10 - @test !isempty(finddatasets(blocks=(Image, Label))) - @test !isempty(finddatasets(blocks=(Image, LabelMulti))) - @test !isempty(finddatasets(blocks=(Image, Mask))) + @test length(finddatasets(blocks=(Image, Label))) > 0 + @test length(finddatasets(blocks=(Image, LabelMulti))) > 0 + @test length(finddatasets(blocks=(Image, Mask))) > 0 @test ImageClassificationSingle ∈ findlearningtasks((Image, Label)) @test ImageClassificationMulti ∈ findlearningtasks((Image, LabelMulti))