@abi-software/map-utilities 1.6.0 → 1.6.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abi-software/map-utilities",
3
- "version": "1.6.0",
3
+ "version": "1.6.1-beta.1",
4
4
  "files": [
5
5
  "dist/*",
6
6
  "src/*",
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Competency Queries
3
+ *
4
+ * competencyQuery: base function
5
+ * query[functionName]: specific queries
6
+ *
7
+ * Note: use named-export for better tree-shaking.
8
+ */
9
+
10
+ async function postRequest(API_URL, payload) {
11
+ try {
12
+ const response = await fetch(API_URL, {
13
+ method: "POST",
14
+ headers: {
15
+ "Content-Type": "application/json",
16
+ },
17
+ body: JSON.stringify(payload),
18
+ });
19
+
20
+ if (!response.ok) {
21
+ throw new Error(`API Error: ${response.status} ${response.statusText}`);
22
+ }
23
+
24
+ return await response.json();
25
+ } catch (error) {
26
+ console.error("Request failed:", error);
27
+ throw error;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Competency Query
33
+ *
34
+ * @param {Object} options - Query options.
35
+ * @param {string} options.flatmapAPI - Base URL of the flatmap server.
36
+ * @param {string} options.knowledgeSource - SCKAN source ID.
37
+ * @param {string} options.queryId - Competency query ID.
38
+ * @param {Array} options.parameters - Parameters specific to the query.
39
+ * @param {string} [options.orderId] - Optional order ID for sorting.
40
+ * @returns {Promise<any>} - JSON response.
41
+ */
42
+ async function competencyQuery(options) {
43
+ const { flatmapAPI, knowledgeSource, queryId, parameters, orderId } = options;
44
+ const API_URL = `${flatmapAPI}competency/query`;
45
+
46
+ const params = Array.isArray(parameters) ? [...parameters] : [];
47
+
48
+ params.push({
49
+ "column": "source_id",
50
+ "value": knowledgeSource,
51
+ });
52
+
53
+ let queryIdStr;
54
+ if (typeof queryId === 'number') {
55
+ queryIdStr = queryId.toString();
56
+ } else if (typeof queryId === 'string') {
57
+ queryIdStr = queryId;
58
+ } else {
59
+ throw new TypeError('queryId must be a string or a number convertible to string.');
60
+ }
61
+
62
+ const payload = {
63
+ "query_id": queryIdStr,
64
+ "parameters": params,
65
+ };
66
+
67
+ // Currently only query 12 has an order parameter
68
+ if (orderId) {
69
+ payload.order = [orderId];
70
+ }
71
+
72
+ return postRequest(API_URL, payload);
73
+ }
74
+
75
+ // Neuron populations associated with a location [query id => 1] (or)
76
+ // Neuron populations that share at least one edge with another neuron population [query id => 23]
77
+ async function queryAllConnectedPaths(flatmapAPI, knowledgeSource, featureId) {
78
+ const featureIds = Array.isArray(featureId) ? featureId : [featureId];
79
+ const queryId = featureIds[0].startsWith('ilxtr:') ? 23 : 1;
80
+ const data = await competencyQuery({
81
+ flatmapAPI: flatmapAPI,
82
+ knowledgeSource: knowledgeSource,
83
+ queryId: queryId,
84
+ parameters: [
85
+ {
86
+ column: 'feature_id',
87
+ value: featureIds
88
+ },
89
+ ]
90
+ });
91
+ if (data?.results?.values) {
92
+ const paths = data.results.values.map((value) => {
93
+ // value => [ 'source_id', 'path_id', 'axon_terminal']
94
+ return value[1];
95
+ });
96
+ // remove duplicates
97
+ return [...new Set(paths)];
98
+ }
99
+ return [];
100
+ }
101
+
102
+ // Neuron populations beginning at a location
103
+ async function queryPathsByOrigin(flatmapAPI, knowledgeSource, featureId) {
104
+ const data = await competencyQuery({
105
+ flatmapAPI: flatmapAPI,
106
+ knowledgeSource: knowledgeSource,
107
+ queryId: 1, // TODO: to update to a specific query ID for origins
108
+ parameters: [
109
+ {
110
+ column: 'feature_id',
111
+ value: featureId
112
+ },
113
+ ]
114
+ });
115
+ if (data?.results?.values) {
116
+ const paths = data.results.values.map((value) => {
117
+ // value => [ 'source_id', 'path_id', 'axon_terminal']
118
+ return value[1];
119
+ });
120
+ // remove duplicates
121
+ return [...new Set(paths)];
122
+ }
123
+ return [];
124
+ }
125
+
126
+ // Neuron populations via a location
127
+ async function queryPathsByViaLocation(flatmapAPI, knowledgeSource, featureId) {
128
+ const data = await competencyQuery({
129
+ flatmapAPI: flatmapAPI,
130
+ knowledgeSource: knowledgeSource,
131
+ queryId: 1, // TODO: to update to a specific query ID for via
132
+ parameters: [
133
+ {
134
+ column: 'feature_id',
135
+ value: featureId
136
+ },
137
+ ]
138
+ });
139
+ if (data?.results?.values) {
140
+ const paths = data.results.values.map((value) => {
141
+ // value => [ 'source_id', 'path_id', 'axon_terminal']
142
+ return value[1];
143
+ });
144
+ // remove duplicates
145
+ return [...new Set(paths)];
146
+ }
147
+ return [];
148
+ }
149
+
150
+ // Neuron populations terminating at a location
151
+ async function queryPathsByDestination(flatmapAPI, knowledgeSource, featureId) {
152
+ const data = await competencyQuery({
153
+ flatmapAPI: flatmapAPI,
154
+ knowledgeSource: knowledgeSource,
155
+ queryId: 2,
156
+ parameters: [
157
+ {
158
+ column: 'feature_id',
159
+ value: featureId
160
+ },
161
+ ]
162
+ });
163
+ if (data?.results?.values) {
164
+ const paths = data.results.values.map((value) => {
165
+ // value => [ 'source_id', 'path_id', 'axon_terminal']
166
+ return value[1];
167
+ });
168
+ // remove duplicates
169
+ return [...new Set(paths)];
170
+ }
171
+ return [];
172
+ }
173
+
174
+ // Neuron populations from origin node(s) to destination node(s), via node(s)
175
+ // API Label: Neuron populations that have source, via, and destination nodes
176
+ async function queryPathsByRoute({ flatmapAPI, knowledgeSource, origins, destinations, vias }) {
177
+ const originParam = {
178
+ column: 'source_node_id',
179
+ value: origins
180
+ };
181
+ const viaParam = {
182
+ column: 'via_node_id',
183
+ value: vias
184
+ };
185
+ const destinationParam = {
186
+ column: 'dest_node_id',
187
+ value: destinations
188
+ };
189
+ if (!origins.length) {
190
+ originParam['negate'] = true;
191
+ }
192
+ if (!vias.length) {
193
+ viaParam['negate'] = true;
194
+ }
195
+ if (!destinations.length) {
196
+ destinationParam['negate'] = true;
197
+ }
198
+ const data = await competencyQuery({
199
+ flatmapAPI: flatmapAPI,
200
+ knowledgeSource: knowledgeSource,
201
+ queryId: 24,
202
+ parameters: [
203
+ originParam,
204
+ viaParam,
205
+ destinationParam,
206
+ ]
207
+ });
208
+ if (data?.results?.values) {
209
+ const paths = data.results.values.map((value) => {
210
+ // value => [ 'source_id', 'path_id', 'axon_terminal']
211
+ return value[1];
212
+ });
213
+ // remove duplicates
214
+ return [...new Set(paths)];
215
+ }
216
+ return [];
217
+ }
218
+
219
+ export {
220
+ competencyQuery,
221
+ queryAllConnectedPaths,
222
+ queryPathsByOrigin,
223
+ queryPathsByViaLocation,
224
+ queryPathsByDestination,
225
+ queryPathsByRoute,
226
+ };
@@ -0,0 +1,237 @@
1
+ // origins = ilxtr:hasSomaLocatedIn
2
+ // destinations = ilxtr:hasAxonPresynapticElementIn, ilxtr:hasAxonSensorySubcellularElementIn
3
+ // via = ilxtr:hasAxonLeadingToSensorySubcellularElementIn, ilxtr:hasAxonLocatedIn
4
+
5
+ async function query(flatmapAPI, sql, params) {
6
+ const url = `${flatmapAPI}knowledge/query/`;
7
+ const query = { sql, params };
8
+
9
+ try {
10
+ const response = await fetch(url, {
11
+ method: 'POST',
12
+ headers: {
13
+ "Accept": "application/json; charset=utf-8",
14
+ "Content-Type": "application/json"
15
+ },
16
+ body: JSON.stringify(query)
17
+ });
18
+
19
+ if (!response.ok) {
20
+ throw new Error(`Cannot access ${url}`);
21
+ }
22
+
23
+ return await response.json();
24
+ } catch {
25
+ return {
26
+ values: []
27
+ };
28
+ }
29
+ }
30
+
31
+ async function fetchLabels(flatmapAPI, labelledTerms) {
32
+ if (!labelledTerms.length) return [];
33
+
34
+ const data = await query(
35
+ flatmapAPI,
36
+ `select entity, knowledge from knowledge
37
+ where entity in (?${', ?'.repeat(labelledTerms.length - 1)})
38
+ order by source desc`,
39
+ [...labelledTerms]
40
+ );
41
+
42
+ return await data.values;
43
+ }
44
+
45
+ function filterOrigins(item) {
46
+ const soma = item["node-phenotypes"]?.["ilxtr:hasSomaLocatedIn"];
47
+ return Array.isArray(item.connectivity) &&
48
+ item.connectivity.length > 0 &&
49
+ Array.isArray(soma) &&
50
+ soma.length > 0;
51
+ }
52
+
53
+ function filterDestinations(item) {
54
+ const axonPresyn = item["node-phenotypes"]?.["ilxtr:hasAxonPresynapticElementIn"];
55
+ const axonSensory = item["node-phenotypes"]?.["ilxtr:hasAxonSensorySubcellularElementIn"];
56
+ const hasDest =
57
+ (Array.isArray(axonPresyn) && axonPresyn.length > 0) ||
58
+ (Array.isArray(axonSensory) && axonSensory.length > 0);
59
+ return Array.isArray(item.connectivity) &&
60
+ item.connectivity.length > 0 &&
61
+ hasDest;
62
+ }
63
+
64
+ function filterViaLocations(item) {
65
+ if (!Array.isArray(item.connectivity) || item.connectivity.length === 0) return false;
66
+
67
+ const origins = new Set(
68
+ (item["node-phenotypes"]?.["ilxtr:hasSomaLocatedIn"] || []).map(arr => arr[0])
69
+ );
70
+ const destinations = new Set([
71
+ ...((item["node-phenotypes"]?.["ilxtr:hasAxonPresynapticElementIn"] || []).map(arr => arr[0])),
72
+ ...((item["node-phenotypes"]?.["ilxtr:hasAxonSensorySubcellularElementIn"] || []).map(arr => arr[0]))
73
+ ]);
74
+
75
+ return item.connectivity.some(pair => {
76
+ const [from, to] = pair;
77
+ const fromId = from[0];
78
+ const toId = to[0];
79
+ return (
80
+ !origins.has(fromId) &&
81
+ !destinations.has(toId)
82
+ );
83
+ });
84
+ }
85
+
86
+ function getConnectivityItems(obj) {
87
+ if (!Array.isArray(obj.connectivity)) return [];
88
+ const items = new Set();
89
+
90
+ obj.connectivity.forEach((pair) => {
91
+ if (Array.isArray(pair) && pair.length) {
92
+ pair.forEach((endpoint) => {
93
+ if (Array.isArray(endpoint) && typeof endpoint[0] === 'string') {
94
+ const stringifyEndpoint = JSON.stringify(endpoint);
95
+ items.add(stringifyEndpoint);
96
+ }
97
+ });
98
+ }
99
+ });
100
+ return Array.from(items);
101
+ }
102
+
103
+ function getPhenotypeItems(obj, prop) {
104
+ const arr = obj["node-phenotypes"]?.[prop];
105
+ if (!Array.isArray(arr)) return [];
106
+ return arr;
107
+ }
108
+
109
+ async function transformResults(flatmapAPI, results) {
110
+ const baseResults = Array.from(
111
+ new Map(results.map(item => [JSON.stringify(item), item])).values()
112
+ );
113
+ const terms = baseResults.flat(Infinity);
114
+ const uniqueTerms = [...new Set(terms)];
115
+ const fetchResults = await fetchLabels(flatmapAPI, uniqueTerms);
116
+ const objectResults = fetchResults.map((item) => JSON.parse(item[1]));
117
+ const formattedResults = baseResults.map((item) => {
118
+ const itemPair = item.flat();
119
+ const labels = [];
120
+ for (let i = 0; i < itemPair.length; i++) {
121
+ const foundObj = objectResults.find((obj) => obj.id === itemPair[i])
122
+ if (foundObj) {
123
+ labels.push(foundObj.label);
124
+ }
125
+ }
126
+ return {
127
+ key: item,
128
+ label: labels.join(', '),
129
+ };
130
+ });
131
+ return formattedResults;
132
+ }
133
+
134
+ async function extractOriginItems(flatmapAPI, knowledge) {
135
+ const results = [];
136
+ knowledge.forEach(obj => {
137
+ if (!Array.isArray(obj.connectivity) || obj.connectivity.length === 0) return;
138
+ const connectivityItems = new Set(getConnectivityItems(obj));
139
+ getPhenotypeItems(obj, "ilxtr:hasSomaLocatedIn").forEach((item) => {
140
+ const stringifyItem = JSON.stringify(item);
141
+ if (connectivityItems.has(stringifyItem)) results.push(item);
142
+ });
143
+ });
144
+ return await transformResults(flatmapAPI, results);
145
+ }
146
+
147
+ async function extractDestinationItems(flatmapAPI, knowledge) {
148
+ const results = [];
149
+ knowledge.forEach(obj => {
150
+ if (!Array.isArray(obj.connectivity) || obj.connectivity.length === 0) return;
151
+ const connectivityItems = new Set(getConnectivityItems(obj));
152
+ [
153
+ ...getPhenotypeItems(obj, "ilxtr:hasAxonPresynapticElementIn"),
154
+ ...getPhenotypeItems(obj, "ilxtr:hasAxonSensorySubcellularElementIn")
155
+ ].forEach(item => {
156
+ const stringifyItem = JSON.stringify(item);
157
+ if (connectivityItems.has(stringifyItem)) results.push(item);
158
+ });
159
+ });
160
+ return await transformResults(flatmapAPI, results);
161
+ }
162
+
163
+ async function extractViaItems(flatmapAPI, knowledge) {
164
+ const results = [];
165
+ knowledge.forEach(obj => {
166
+ if (!Array.isArray(obj.connectivity) || obj.connectivity.length === 0) return;
167
+ const connectivityItems = new Set(getConnectivityItems(obj));
168
+ [
169
+ ...getPhenotypeItems(obj, "ilxtr:hasAxonLeadingToSensorySubcellularElementIn"),
170
+ ...getPhenotypeItems(obj, "ilxtr:hasAxonLocatedIn")
171
+ ].forEach(item => {
172
+ const stringifyItem = JSON.stringify(item);
173
+ if (connectivityItems.has(stringifyItem)) results.push(item);
174
+ });
175
+ });
176
+ return await transformResults(flatmapAPI, results);
177
+ }
178
+
179
+ function findPathsByOriginItem(knowledge, originItems) {
180
+ return knowledge.filter(obj => {
181
+ if (!Array.isArray(obj.connectivity) || obj.connectivity.length === 0) return false;
182
+ const origins = getPhenotypeItems(obj, "ilxtr:hasSomaLocatedIn");
183
+ return origins.some(item => originItems.map(i => JSON.stringify(i)).includes(JSON.stringify(item)));
184
+ });
185
+ }
186
+
187
+ function findPathsByDestinationItem(knowledge, destinationItems) {
188
+ return knowledge.filter(obj => {
189
+ if (!Array.isArray(obj.connectivity) || obj.connectivity.length === 0) return false;
190
+ const destinations = [
191
+ ...getPhenotypeItems(obj, "ilxtr:hasAxonPresynapticElementIn"),
192
+ ...getPhenotypeItems(obj, "ilxtr:hasAxonSensorySubcellularElementIn")
193
+ ];
194
+ return destinations.some(item => destinationItems.map(i => JSON.stringify(i)).includes(JSON.stringify(item)));
195
+ });
196
+ }
197
+
198
+ function findPathsByViaItem(knowledge, viaItems) {
199
+ return knowledge.filter(obj => {
200
+ if (!Array.isArray(obj.connectivity) || obj.connectivity.length === 0) return false;
201
+ const vias = [
202
+ ...getPhenotypeItems(obj, "ilxtr:hasAxonLeadingToSensorySubcellularElementIn"),
203
+ ...getPhenotypeItems(obj, "ilxtr:hasAxonLocatedIn")
204
+ ];
205
+ return vias.some(item => viaItems.map(i => JSON.stringify(i)).includes(JSON.stringify(item)));
206
+ });
207
+ }
208
+
209
+ async function queryPathsByRouteFromKnowledge({ knowledge, origins, destinations, vias }) {
210
+ let results = knowledge;
211
+
212
+ if (origins.length) {
213
+ results = findPathsByOriginItem(results, origins);
214
+ }
215
+ if (destinations.length) {
216
+ results = findPathsByDestinationItem(results, destinations);
217
+ }
218
+ if (vias.length) {
219
+ results = findPathsByViaItem(results, vias);
220
+ }
221
+
222
+ return results;
223
+ }
224
+
225
+ export {
226
+ filterOrigins,
227
+ filterDestinations,
228
+ filterViaLocations,
229
+ extractOriginItems,
230
+ extractDestinationItems,
231
+ extractViaItems,
232
+ findPathsByOriginItem,
233
+ findPathsByDestinationItem,
234
+ findPathsByViaItem,
235
+ queryPathsByRouteFromKnowledge,
236
+ fetchLabels,
237
+ }
@@ -8,6 +8,27 @@ import HelpModeDialog from "./HelpModeDialog/HelpModeDialog.vue";
8
8
  import Tooltip from "./Tooltip/Tooltip.vue";
9
9
  import TreeControls from "./TreeControls/TreeControls.vue";
10
10
  import ExternalResourceCard from "./Tooltip/ExternalResourceCard.vue";
11
+ import {
12
+ competencyQuery,
13
+ queryAllConnectedPaths,
14
+ queryPathsByOrigin,
15
+ queryPathsByViaLocation,
16
+ queryPathsByDestination,
17
+ queryPathsByRoute,
18
+ } from "./CompetencyQueries/CompetencyQueries.js";
19
+ import {
20
+ filterOrigins,
21
+ filterDestinations,
22
+ filterViaLocations,
23
+ extractOriginItems,
24
+ extractDestinationItems,
25
+ extractViaItems,
26
+ findPathsByOriginItem,
27
+ findPathsByDestinationItem,
28
+ findPathsByViaItem,
29
+ queryPathsByRouteFromKnowledge,
30
+ fetchLabels,
31
+ } from "./CompetencyQueries/knowledgeQueries.js";
11
32
 
12
33
  export {
13
34
  AnnotationPopup,
@@ -20,4 +41,21 @@ export {
20
41
  Tooltip,
21
42
  TreeControls,
22
43
  ExternalResourceCard,
44
+ competencyQuery,
45
+ queryAllConnectedPaths,
46
+ queryPathsByOrigin,
47
+ queryPathsByViaLocation,
48
+ queryPathsByDestination,
49
+ queryPathsByRoute,
50
+ filterOrigins,
51
+ filterDestinations,
52
+ filterViaLocations,
53
+ extractOriginItems,
54
+ extractDestinationItems,
55
+ extractViaItems,
56
+ findPathsByOriginItem,
57
+ findPathsByDestinationItem,
58
+ findPathsByViaItem,
59
+ queryPathsByRouteFromKnowledge,
60
+ fetchLabels,
23
61
  };