def spring_project(
        adata, project_dir, embedding_method,
        subplot_name = None,
        cell_groupings=None, custom_color_tracks=None,
        total_counts_key = 'n_counts', overwrite = False
        ):
    """Exports to a SPRING project directory [Weinreb17]_.

    Visualize annotation present in `adata`. By default, export all gene expression data
    from `adata.raw` and categorical and continuous annotations present in `adata.obs`.

    See `SPRING <https://github.com/AllonKleinLab/SPRING>`__ or [Weinreb17]_ for details.

    Parameters
    ----------
    adata : :class:`~anndata.AnnData`
        Annotated data matrix: `adata.uns['neighbors']` needs to
        be present.
    project_dir : `str`
        Path to directory for exported SPRING files.
    embedding_method: `str`
        Name of a 2-D embedding in `adata.obsm`
    subplot_name: `str`, optional (default: `None`)
        Name of subplot folder to be created at `project_dir+"/"+subplot_name`
    cell_groupings : `str`, `list` of `str`, optional (default: `None`)
        Instead of importing all categorical annotations when `None`,
        pass a list of keys for `adata.obs`.
    custom_color_tracks : `str`, `list` of `str`, optional (default: `None`)
        Specify specific `adata.obs` keys for continuous coloring.
    total_counts_key: `str`, optional (default: "n_counts")
        Name of key for total transcript counts in `adata.obs`.
    overwrite: `boolean`, optional (default: `False`)
        When `True`, existing counts matrices in `project_dir` are overwritten.

    Examples
    --------
    See this `tutorial <https://github.com/theislab/scanpy_usage/tree/master/171111_SPRING_export>`__.
    """

    # need to get nearest neighbors first
    if 'neighbors' not in adata.uns:
        raise ValueError('Run `sc.pp.neighbors` first.')

    # check that requested 2-D embedding has been generated
    if embedding_method not in adata.obsm_keys():
        if 'X_' + embedding_method in adata.obsm_keys():
            embedding_method = 'X_' + embedding_method
        else:
            if embedding_method in adata.uns:
                embedding_method = 'X_' + embedding_method + '_' + adata.uns[embedding_method]['params']['layout']
            else:
                raise ValueError('Run the specified embedding method `%s` first.' %embedding_method)

    coords = adata.obsm[embedding_method]

    # Make project directory and subplot directory (subplot has same name as project)
    # For now, the subplot is just all cells in adata
    project_dir = project_dir.rstrip('/') + '/'
    if subplot_name is None:
        subplot_dir = project_dir + project_dir.split('/')[-2] + '/'
    else:
        subplot_dir = project_dir + subplot_name + '/'
    if not os.path.exists(subplot_dir):
        os.makedirs(subplot_dir)
    print("Writing subplot to %s" %subplot_dir)

    # Write counts matrices as hdf5 files and npz if they do not already exist
    # or if user requires overwrite.
    # To do: check if Alex's h5sparse format will allow fast loading from just
    # one file.
    write_counts_matrices = True
    base_dir_filelist = ['counts_norm_sparse_genes.hdf5', 'counts_norm_sparse_cells.hdf5',
                         'counts_norm.npz', 'total_counts.txt', 'genes.txt']
    if all([os.path.isfile(project_dir + f) for f in base_dir_filelist]):
        if not overwrite:
            logg.warning('%s is an existing SPRING folder. A new subplot will be created, but '
                         'you must set `overwrite=True` to overwrite counts matrices.' %project_dir)
            write_counts_matrices = False
        else:
            logg.warning('Overwriting the files in %s.' %project_dir)

    # Ideally, all genes will be written from adata.raw
    if adata.raw is not None:
        E = adata.raw.X.tocsc()
        gene_list = list(adata.raw.var_names)
    else:
        E = adata.X.tocsc()
        gene_list = list(adata.var_names)

    # Keep track of total counts per cell if present
    if total_counts_key in adata.obs:
        total_counts = np.array(adata.obs[total_counts_key])
    else:
        total_counts = E.sum(1).A1

    # Write the counts matrices to project directory
    if write_counts_matrices:
        write_hdf5_genes(E, gene_list, project_dir + 'counts_norm_sparse_genes.hdf5')
        write_hdf5_cells(E, project_dir + 'counts_norm_sparse_cells.hdf5')
        write_sparse_npz(E, project_dir + 'counts_norm.npz')
        with open(project_dir + 'genes.txt', 'w') as o:
            for g in gene_list:
                o.write(g + '\n')
        np.savetxt(project_dir + 'total_counts.txt', total_counts)

    # Get categorical and continuous metadata
    categorical_extras = {}
    continuous_extras = {}
    if cell_groupings is None:
        for obs_name in adata.obs:
            if is_categorical(adata.obs[obs_name]):
                categorical_extras[obs_name] = [str(x) for x in adata.obs[obs_name]]
    else:
        if isinstance(cell_groupings, str):
            cell_groupings = [cell_groupings]
        for obs_name in cell_groupings:
            if obs_name not in adata.obs:
                logg.warning('Cell grouping "%s" is not in adata.obs' %obs_name)
            elif is_categorical(adata.obs[obs_name]):
                categorical_extras[obs_name] = [str(x) for x in adata.obs[obs_name]]
            else:
                logg.warning('Cell grouping "%s" is not a categorical variable' %obs_name)
    if custom_color_tracks is None:
        for obs_name in adata.obs:
            if not is_categorical(adata.obs[obs_name]):
                continuous_extras[obs_name] = np.array(adata.obs[obs_name])
    else:
        if isinstance(custom_color_tracks, str):
            custom_color_tracks = [custom_color_tracks]
        for obs_name in custom_color_tracks:
            if obs_name not in adata.obs:
                logg.warning('Custom color track "%s" is not in adata.obs' %obs_name)
            elif not is_categorical(adata.obs[obs_name]):
                continuous_extras[obs_name] = np.array(adata.obs[obs_name])
            else:
                logg.warning('Custom color track "%s" is not a continuous variable' %obs_name)


    # Write continuous colors
    continuous_extras['Uniform'] = np.zeros(E.shape[0])
    write_color_tracks(continuous_extras, subplot_dir+'color_data_gene_sets.csv')

    # Create and write a dictionary of color profiles to be used by the visualizer
    color_stats = {}
    color_stats = get_color_stats_genes(color_stats, E, gene_list)
    color_stats = get_color_stats_custom(color_stats, continuous_extras)
    write_color_stats(subplot_dir + 'color_stats.json', color_stats)

    # Write categorical data
    categorical_coloring_data = {}
    categorical_coloring_data = build_categ_colors(categorical_coloring_data, categorical_extras)
    write_cell_groupings(subplot_dir+'categorical_coloring_data.json', categorical_coloring_data)

    # Write graph in two formats for backwards compatibility
    edges = get_edges(adata)
    write_graph(subplot_dir + 'graph_data.json', E.shape[0], edges)
    write_edges(subplot_dir + 'edges.csv', edges)


    # Write cell filter; for now, subplots must be generated from within SPRING,
    # so cell filter includes all cells.
    np.savetxt(subplot_dir + 'cell_filter.txt', np.arange(E.shape[0]), fmt='%i')
    np.save(subplot_dir + 'cell_filter.npy', np.arange(E.shape[0]))

    # Write 2-D coordinates, after adjusting to roughly match SPRING's default d3js force layout parameters
    coords = coords - coords.min(0)[None,:]
    coords = coords * (np.array([1000, 1000]) / coords.ptp(0))[None,:] + np.array([200,-200])[None,:]
    np.savetxt(subplot_dir + 'coordinates.txt',
               np.hstack((np.arange(E.shape[0])[:,None], coords)),
               fmt='%i,%.6f,%.6f')

    # Write some useful intermediates, if they exist
    if 'X_pca' in adata.obsm_keys():
        np.savez_compressed(subplot_dir + 'intermediates.npz',
                            Epca=adata.obsm['X_pca'],
                            total_counts = total_counts)

    # Write PAGA data, if present
    if 'paga' in adata.uns:
        clusts = np.array(adata.obs[adata.uns['paga']['groups']].cat.codes)
        uniq_clusts = adata.obs[adata.uns['paga']['groups']].cat.categories
        paga_coords = [coords[clusts==i,:].mean(0) for i in range(len(uniq_clusts))]
        export_PAGA_to_SPRING(adata, paga_coords, subplot_dir + 'PAGA_data.json')

    return None
Exports to a SPRING project directory [Weinreb17]_.

    Visualize annotation present in `adata`. By default, export all gene expression data
    from `adata.raw` and categorical and continuous annotations present in `adata.obs`.

    See `SPRING <https://github.com/AllonKleinLab/SPRING>`__ or [Weinreb17]_ for details.

    Parameters
    ----------
    adata : :class:`~anndata.AnnData`
        Annotated data matrix: `adata.uns['neighbors']` needs to
        be present.
    project_dir : `str`
        Path to directory for exported SPRING files.
    embedding_method: `str`
        Name of a 2-D embedding in `adata.obsm`
    subplot_name: `str`, optional (default: `None`)
        Name of subplot folder to be created at `project_dir+"/"+subplot_name`
    cell_groupings : `str`, `list` of `str`, optional (default: `None`)
        Instead of importing all categorical annotations when `None`,
        pass a list of keys for `adata.obs`.
    custom_color_tracks : `str`, `list` of `str`, optional (default: `None`)
        Specify specific `adata.obs` keys for continuous coloring.
    total_counts_key: `str`, optional (default: "n_counts")
        Name of key for total transcript counts in `adata.obs`.
    overwrite: `boolean`, optional (default: `False`)
        When `True`, existing counts matrices in `project_dir` are overwritten.

    Examples
    --------
    See this `tutorial <https://github.com/theislab/scanpy_usage/tree/master/171111_SPRING_export>`__.