Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def create_app():
from api.resources.gaia import gaia
from api.resources.rnaseq_gene_expression import rnaseq_gene_expression
from api.resources.microarray_gene_expression import microarray_gene_expression
from api.resources.umap_gene_expression import umap_gene_expression
from api.resources.proxy import bar_proxy
from api.resources.thalemine import thalemine
from api.resources.snps import snps
Expand All @@ -98,6 +99,7 @@ def create_app():
bar_api.add_namespace(gaia)
bar_api.add_namespace(rnaseq_gene_expression)
bar_api.add_namespace(microarray_gene_expression)
bar_api.add_namespace(umap_gene_expression)
bar_api.add_namespace(bar_proxy)
bar_api.add_namespace(thalemine)
bar_api.add_namespace(snps)
Expand Down
19 changes: 19 additions & 0 deletions api/models/arabidopsis_NIE_umap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from api import db


class UmapCoords(db.Model):
__bind_key__ = "arabidopsis_NIE_umap"
__tablename__ = "umap_coords"

cell_id: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True)
umap_1: db.Mapped[float] = db.mapped_column(db.Float, nullable=False)
umap_2: db.Mapped[float] = db.mapped_column(db.Float, nullable=False)
cell_type: db.Mapped[str] = db.mapped_column(db.String(128), nullable=False)


class UmapExpression(db.Model):
__bind_key__ = "arabidopsis_NIE_umap"
__tablename__ = "umap_expression"

gene_id: db.Mapped[str] = db.mapped_column(db.String(32), nullable=False, primary_key=True)
expression: db.Mapped[dict] = db.mapped_column(db.JSON, nullable=False)
53 changes: 38 additions & 15 deletions api/models/efp_dynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,43 @@
from api import db
from api.utils.bar_utils import load_combined_master

# Optional sample_data columns, keyed by the schema_variants role that declares them.
# A database only gets one of these if its assigned variant declares a column with that
# role, so every variant predating this mapping generates exactly the columns it did
# before. Each value is a factory: mapped_column objects cannot be shared between models.
_OPTIONAL_COLUMNS_BY_ROLE = {
"value_std": lambda: db.mapped_column(db.Float, nullable=True),
}

def _sample_data_model(database):

def _optional_columns(variant):
"""Extra mapped columns this variant's sample_data declares, keyed by column name."""
columns = variant.get("tables", {}).get("sample_data", {}).get("columns", {})
return {
column: _OPTIONAL_COLUMNS_BY_ROLE[spec["role"]]()
for column, spec in columns.items()
if spec.get("role") in _OPTIONAL_COLUMNS_BY_ROLE
}


def _sample_data_model(database, variant):
class_name = "".join(part.capitalize() for part in database.split("_")) + "SampleData"
return type(
class_name,
(db.Model,),
{
"__bind_key__": database,
"__tablename__": "sample_data",
"data_probeset_id": db.mapped_column(db.String(255), primary_key=True),
"data_bot_id": db.mapped_column(db.String(255), primary_key=True),
"data_signal": db.mapped_column(db.Float, primary_key=True),
},
)


SAMPLE_DATA_MODELS = {database: _sample_data_model(database) for database in load_combined_master()["databases"]}
attributes = {
"__bind_key__": database,
"__tablename__": "sample_data",
"data_probeset_id": db.mapped_column(db.String(255), primary_key=True),
"data_bot_id": db.mapped_column(db.String(255), primary_key=True),
"data_signal": db.mapped_column(db.Float, primary_key=True),
}
attributes.update(_optional_columns(variant))
return type(class_name, (db.Model,), attributes)


_MASTER = load_combined_master()

# A few catalogued databases name a schema_variant that is not in schema_variants; they
# fall back to {} and so generate the three required columns only, exactly as before.
SAMPLE_DATA_MODELS = {
database: _sample_data_model(database, _MASTER["schema_variants"].get(info["schema_variant"], {}))
for database, info in _MASTER["databases"].items()
}
16 changes: 14 additions & 2 deletions api/resources/gene_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,31 @@ def get(self, database, gene_id):

query_id = rows[0][0]

# only databases whose schema_variant declares a standard deviation column have
# data_signal_std mapped; every other database keeps the two-key row shape
has_signal_std = hasattr(model, "data_signal_std")
columns = [model.data_bot_id, model.data_signal]
if has_signal_std:
columns.append(model.data_signal_std)

rows = db.session.execute(
db.select(model.data_bot_id, model.data_signal).where(func.upper(model.data_probeset_id) == query_id.upper())
db.select(*columns).where(func.upper(model.data_probeset_id) == query_id.upper())
).all()

if len(rows) == 0:
return BARUtils.error_exit("There are no data found for the given gene"), 400

if has_signal_std:
data = [{"name": name, "value": str(value), "value_std": str(std)} for name, value, std in rows]
else:
data = [{"name": name, "value": str(value)} for name, value in rows]

res = {
"gene_id": gene_id,
"probset_id": query_id,
"database": database,
"record_count": len(rows),
"data": [{"name": name, "value": str(value)} for name, value in rows],
"data": data,
}

return BARUtils.success_exit(res)
78 changes: 78 additions & 0 deletions api/resources/umap_gene_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from flask_restx import Namespace, Resource
from markupsafe import escape
from api import db
from api.models.arabidopsis_NIE_umap import UmapCoords as ArabidopsisNIEUmapCoords
from api.models.arabidopsis_NIE_umap import UmapExpression as ArabidopsisNIEUmapExpression
from api.utils.bar_utils import BARUtils, load_combined_master

umap_gene_expression = Namespace(
"UMAP Gene Expression",
description="UMAP coordinates and single cell gene expression data from the BAR Databases",
path="/umap_gene_expression",
)


class UMAPUtils:
@staticmethod
def get_tables(database):
"""This function sets the tables and species for a UMAP database
:param database: name of BAR database
:return: dict with the coordinates table, expression table and species
"""
# Set database
if database == "arabidopsis_NIE_umap":
coords_table = ArabidopsisNIEUmapCoords
expression_table = ArabidopsisNIEUmapExpression
species = "arabidopsis"

else:
return {"success": False, "error": "Invalid database", "error_code": 400}

return {"success": True, "coords_table": coords_table, "expression_table": expression_table, "species": species}


@umap_gene_expression.route("/<string:database>")
class GetUMAPCoordinates(Resource):
@umap_gene_expression.param("database", _in="path", default="arabidopsis_NIE_umap")
def get(self, database=""):
"""This end point returns the UMAP coordinates of every cell"""
database = escape(database)

tables = UMAPUtils.get_tables(database)
if not tables["success"]:
return BARUtils.error_exit(tables["error"]), tables["error_code"]

table = tables["coords_table"]
rows = db.session.execute(db.select(table.cell_id, table.umap_1, table.umap_2, table.cell_type)).all()

data = {}
for row in rows:
data[row[0]] = {"umap_1": row[1], "umap_2": row[2], "cell_type": row[3]}

return BARUtils.success_exit(data)


@umap_gene_expression.route("/<string:database>/<string:gene_id>")
class GetUMAPGeneExpression(Resource):
@umap_gene_expression.param("database", _in="path", default="arabidopsis_NIE_umap")
@umap_gene_expression.param("gene_id", _in="path", default="At1g01010")
def get(self, database="", gene_id=""):
"""This end point returns the sparse UMAP gene expression data, keyed by cell id"""
database = escape(database)
gene_id = escape(gene_id)

tables = UMAPUtils.get_tables(database)
if not tables["success"]:
return BARUtils.error_exit(tables["error"]), tables["error_code"]

pattern = load_combined_master()["gene_id_patterns"][tables["species"]]
if not BARUtils.is_valid_gene_id(pattern, gene_id):
return BARUtils.error_exit("Invalid gene id"), 400

table = tables["expression_table"]
rows = db.session.execute(db.select(table.expression).where(table.gene_id == gene_id)).scalars().all()

if len(rows) == 0:
return BARUtils.error_exit("There are no data found for the given gene"), 400

return BARUtils.success_exit(rows[0])
2 changes: 2 additions & 0 deletions config/BAR_API.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_BINDS = {
'annotations_lookup': 'mysql://root:root@localhost/annotations_lookup',
'arabidopsis_ecotypes': 'mysql://root:root@localhost/arabidopsis_ecotypes',
'arabidopsis_NIE_pseudobulk': 'mysql://root:root@localhost/arabidopsis_NIE_pseudobulk',
'arabidopsis_NIE_umap': 'mysql://root:root@localhost/arabidopsis_NIE_umap',
'arachis': 'mysql://root:root@localhost/arachis',
'cannabis': 'mysql://root:root@localhost/cannabis',
'canola_nssnp' : 'mysql://root:root@localhost/canola_nssnp',
Expand Down
64 changes: 64 additions & 0 deletions config/databases/arabidopsis_NIE_pseudobulk_dump.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
-- MySQL dump 10.13 Distrib 9.4.0, for Linux (x86_64)
--
-- Host: localhost Database: arabidopsis_NIE_pseudobulk
-- ------------------------------------------------------
-- Server version 9.4.0

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Current Database: `arabidopsis_NIE_pseudobulk`
--

CREATE DATABASE /*!32312 IF NOT EXISTS*/ `arabidopsis_NIE_pseudobulk` /*!40100 DEFAULT CHARACTER SET latin1 */ /*!80016 DEFAULT ENCRYPTION='N' */;

USE `arabidopsis_NIE_pseudobulk`;

--
-- Table structure for table `sample_data`
--

DROP TABLE IF EXISTS `sample_data`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sample_data` (
`data_probeset_id` varchar(16) NOT NULL,
`data_signal` float DEFAULT '0',
`data_signal_std` float DEFAULT '0',
`data_bot_id` varchar(64) NOT NULL,
KEY `data_probeset_id` (`data_probeset_id`,`data_bot_id`,`data_signal`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `sample_data`
--

LOCK TABLES `sample_data` WRITE;
/*!40000 ALTER TABLE `sample_data` DISABLE KEYS */;
INSERT INTO `sample_data` VALUES ('AT1G01010',0.0346533,0.224356,'D0_Mesophyll'),('AT1G01010',0.0251518,0.185286,'D0_Sieve element_responsive'),('AT1G01010',0.0297745,0.203837,'D0_Guard'),('AT1G01010',0.0550332,0.260401,'D0_Defense state'),('AT1G01010',0.0424581,0.239094,'D0_Epidermal'),('AT1G01010',0.0163655,0.146177,'D0_Phloem Parenchyma'),('AT1G01010',0.0389615,0.234467,'D0_Metabolic stress state'),('AT1G01010',0.0292404,0.198967,'D0_Phloem companion'),('AT1G01010',0.0510094,0.226118,'D0_Trichome'),('AT1G01010',0.027103,0.181704,'D0_Dividing'),('AT1G01010',0.0692417,0.276578,'D0_Stress responsive'),('AT1G01010',0.0110158,0.119154,'D0_Sugar metabolic state'),('AT1G01010',0.0565404,0.272046,'D0_Immune active'),('AT1G01010',0.0513775,0.273271,'D0_Hydathode'),('AT1G01010',0.045102,0.247266,'D0_Vascular'),('AT1G01010',0.0256331,0.169289,'D0_Myrosin'),('AT1G01010',0.0274436,0.177235,'W0_Vascular'),('AT1G01010',0.024174,0.172252,'W0_Mesophyll'),('AT1G01010',0.0213485,0.15354,'W0_Phloem Parenchyma'),('AT1G01010',0.0286998,0.178188,'W0_Dividing'),('AT1G01010',0.0278313,0.182473,'W0_Epidermal'),('AT1G01010',0.0563447,0.251333,'W0_Immune active'),('AT1G01010',0.0343054,0.201204,'W0_Sieve element_responsive'),('AT1G01010',0.0574457,0.253535,'W0_Defense state'),('AT1G01010',0.0136855,0.12117,'W0_Guard'),('AT1G01010',0.0272221,0.169833,'W0_Phloem companion'),('AT1G01010',0.0715313,0.271057,'W0_Stress responsive'),('AT1G01010',0.00820466,0.0673068,'W0_Myrosin'),('AT1G01010',0.0393423,0.199799,'W0_Sugar metabolic state'),('AT1G01010',0.0339956,0.217678,'W0_Metabolic stress state'),('AT1G01010',0.0214199,0.133693,'W0_Trichome'),('AT1G01010',0.0571251,0.236552,'W0_Hydathode'),('AT1G01010',0.0422542,0.250415,'W15_Dividing'),('AT1G01010',0.0446,0.254963,'W15_Mesophyll'),('AT1G01010',0.037824,0.236808,'W15_Phloem Parenchyma'),('AT1G01010',0.0420744,0.251218,'W15_Immune active'),('AT1G01010',0.0387755,0.241218,'W15_Epidermal'),('AT1G01010',0.104241,0.43291,'W15_Hydathode'),('AT1G01010',0.104467,0.324257,'W15_Stress responsive'),('AT1G01010',0.03282,0.228891,'W15_Guard'),('AT1G01010',0.0783275,0.325689,'W15_Defense state'),('AT1G01010',0.0205945,0.15169,'W15_Myrosin'),('AT1G01010',0.0290853,0.203305,'W15_Phloem companion'),('AT1G01010',0.0409241,0.246531,'W15_Vascular'),('AT1G01010',0,0,'W15_Trichome'),('AT1G01010',0.0304135,0.206024,'W15_Sieve element_responsive'),('AT1G01010',0,0,'W15_Sugar metabolic state'),('AT1G01010',0.0573618,0.262866,'W15_Metabolic stress state'),('AT1G01010',0.0187317,0.156752,'R15_Sieve element_responsive'),('AT1G01010',0.0287312,0.191696,'R15_Immune active'),('AT1G01010',0.0329913,0.213788,'R15_Mesophyll'),('AT1G01010',0.0130775,0.14382,'R15_Guard'),('AT1G01010',0.0547408,0.257222,'R15_Defense state'),('AT1G01010',0.0541722,0.243631,'R15_Stress responsive'),('AT1G01010',0.02531,0.180358,'R15_Phloem Parenchyma'),('AT1G01010',0.0400045,0.239473,'R15_Epidermal'),('AT1G01010',0.0300884,0.196556,'R15_Vascular'),('AT1G01010',0.0282214,0.199286,'R15_Dividing'),('AT1G01010',0.034919,0.214859,'R15_Phloem companion'),('AT1G01010',0.018792,0.157063,'R15_Metabolic stress state'),('AT1G01010',0.0528136,0.261553,'R15_Trichome'),('AT1G01010',0,0,'R15_Myrosin'),('AT1G01010',0.0564275,0.281679,'R15_Hydathode'),('AT1G01010',0.0462055,0.222434,'R15_Sugar metabolic state');
INSERT INTO `sample_data` VALUES ('AT1G01010',0.0242853,0.114475,'W0_Phloem average');
INSERT INTO `sample_data` VALUES ('AT1G01010',0.0228029,0.123446,'D0_Phloem average');
INSERT INTO `sample_data` VALUES ('AT1G01010',0.0301145,0.140262,'R15_Phloem average');
INSERT INTO `sample_data` VALUES ('AT1G01010',0.0334546,0.156053,'W15_Phloem average');
INSERT INTO `sample_data` VALUES ('AT1G01010',0.036322,0.224118,'Mean_CTRL');
/*!40000 ALTER TABLE `sample_data` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2026-07-17 01:32:31
83 changes: 83 additions & 0 deletions config/databases/arabidopsis_NIE_umap.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
-- MySQL dump 10.13 Distrib 9.4.0, for Linux (x86_64)
--
-- Host: localhost Database: arabidopsis_NIE_umap
-- ------------------------------------------------------
-- Server version 9.4.0

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Current Database: `arabidopsis_NIE_umap`
--

CREATE DATABASE /*!32312 IF NOT EXISTS*/ `arabidopsis_NIE_umap` /*!40100 DEFAULT CHARACTER SET latin1 */ /*!80016 DEFAULT ENCRYPTION='N' */;

USE `arabidopsis_NIE_umap`;

--
-- Table structure for table `umap_coords`
--

DROP TABLE IF EXISTS `umap_coords`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `umap_coords` (
`cell_id` INT NOT NULL,
`umap_1` FLOAT NOT NULL,
`umap_2` FLOAT NOT NULL,
`cell_type` VARCHAR(128) NOT NULL,
PRIMARY KEY (`cell_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `umap_coords`
--

LOCK TABLES `umap_coords` WRITE;
/*!40000 ALTER TABLE `umap_coords` DISABLE KEYS */;
INSERT INTO `umap_coords` VALUES (43,-6.89231,7.56863,'Metabolic stress state'),(44,2.87577,-4.82349,'Dividing'),(45,-0.262505,-8.55344,'Guard'),(46,-4.5876,6.05261,'Defense state');
/*!40000 ALTER TABLE `umap_coords` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `umap_expression`
--

DROP TABLE IF EXISTS `umap_expression`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `umap_expression` (
`gene_id` VARCHAR(32) NOT NULL,
`expression` JSON NOT NULL,
PRIMARY KEY (`gene_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `umap_expression`
--

LOCK TABLES `umap_expression` WRITE;
/*!40000 ALTER TABLE `umap_expression` DISABLE KEYS */;
INSERT INTO `umap_expression` VALUES ('AT1G01010','{\"43\": 1.152292, \"44\": 1.546603, \"46\": 1.392931}'),('AT3G18780','{\"43\": 1.673516, \"44\": 3.142986, \"45\": 1.490115, \"46\": 1.953491}');
/*!40000 ALTER TABLE `umap_expression` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2026-07-17 01:12:53
2 changes: 2 additions & 0 deletions config/init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ echo "Welcome to the BAR API. Running init!"

mysql -u $DB_USER -p$DB_PASS < ./config/databases/annotations_lookup.sql
mysql -u $DB_USER -p$DB_PASS < ./config/databases/arabidopsis_ecotypes.sql
mysql -u $DB_USER -p$DB_PASS < ./config/databases/arabidopsis_NIE_pseudobulk_dump.sql
mysql -u $DB_USER -p$DB_PASS < ./config/databases/arabidopsis_NIE_umap.sql
mysql -u $DB_USER -p$DB_PASS < ./config/databases/arachis.sql
mysql -u $DB_USER -p$DB_PASS < ./config/databases/cannabis.sql
mysql -u $DB_USER -p$DB_PASS < ./config/databases/canola_nssnp.sql
Expand Down
Loading
Loading