diff --git a/api/package.json b/api/package.json index 50b06683499367df8fe7320b083a7c8120de2702..481ac4aeefe16a08e350d7df20fbce7e2996b9f9 100644 --- a/api/package.json +++ b/api/package.json @@ -91,6 +91,7 @@ "json", "ts" ], + "slowTestThreshold": 30, "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..9aa0115d8daa56a9ab36233472c86aa1c48025b9 --- /dev/null +++ b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts @@ -0,0 +1,98 @@ +import { Experiment } from '../../../../models/experiment/experiment.model'; +import { HeatMapResult } from '../../../../models/result/heat-map-result.model'; +import handlers from '..'; +import { BarChartResult } from 'src/engine/models/result/bar-chart-result.model'; + +const createExperiment = (): Experiment => ({ + id: 'dummy-id', + name: 'Testing purpose', + algorithm: { + id: 'PCA', + }, + datasets: ['desd-synthdata'], + domain: 'dementia', + variables: [ + 'rightcerebralwhitematter', + 'rightmtgmiddletemporalgyrus', + 'rightlateralventricle', + 'lefttmptemporalpole', + 'leftcerebellumwhitematter', + 'rightporgposteriororbitalgyrus', + 'leftangangulargyrus', + 'rightcuncuneus', + ], + results: [], +}); + +describe('PCA result handler', () => { + const data = { + n_obs: 920, + eigen_vals: [ + 4.355128575368841, 1.0450017532423244, 0.7421875389691931, + 0.5621334410413102, 0.4449774285333259, 0.3926006044635884, + 0.24709792171717856, 0.2195778509188677, + ], + eigen_vecs: [ + [ + -0.39801260415007417, -0.408618968170694, -0.005388160172392957, + -0.39520225162483835, -0.34101372698341037, -0.3857891852309699, + -0.37808437879347245, -0.3321614049065189, + ], + [ + -0.08974916969984865, -0.06306725142041861, 0.957701626296451, + -0.05813339720242487, 0.03639315522751595, -0.042472298797464114, + -0.0024375598833834417, 0.25349834682806394, + ], + [ + -0.4461915539200752, 0.31570679861452433, -0.025606596993284957, + 0.30818994376586817, -0.7193269233477485, 0.18210682315923998, + 0.07424770446526184, 0.22248311387276953, + ], + [ + -0.1073921285806547, -0.057551147661307055, -0.24144428440227128, + 0.035245201087085794, 0.19168469965719226, -0.2871851841802395, + -0.43040426139061094, 0.7881313641348668, + ], + [ + -0.08495390059838565, 0.24279781377786122, 0.13080782479311695, + 0.5200478463181943, 0.21537192596569144, 0.08500148719542867, + -0.6763140751914142, -0.36777786677407387, + ], + [ + -0.0932054360289125, 0.2816208747297754, 0.02363518180881532, + 0.29984356493304964, 0.09621700712393312, -0.8213627734608029, + 0.3468941309570408, -0.1315583071518168, + ], + [ + -0.5592437704142699, 0.5214515551418919, -0.04356242791170582, + -0.4758673723220084, 0.40140905254944736, 0.1444142675145091, + 0.05395624557894896, -0.0457302433772836, + ], + [ + -0.5424509822332161, -0.5620474388715386, -0.06527272376104105, + 0.3967451838014927, 0.3338246891755648, 0.17608817173645286, + 0.29263999750747904, -0.009909769193655195, + ], + ], + }; + + it('Test PCA handler with regular data (no edge cases)', () => { + const exp = createExperiment(); + handlers(exp, data); + expect(exp.results.length).toBeGreaterThanOrEqual(2); + + exp.results.forEach((it) => { + if (it['matrix']) { + const heatmap = it as HeatMapResult; + expect(heatmap.matrix).toEqual(data.eigen_vecs); + expect(heatmap.yAxis.categories).toEqual(exp.variables); + } + }); + exp.results.forEach((it) => { + if (it['barValues']) { + const barchart = it as BarChartResult; + expect(barchart.barValues).toEqual(data.eigen_vals); + } + }); + }); +}); diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..3278962199e512fa04c3a2b7c55bd1339447b6c5 --- /dev/null +++ b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts @@ -0,0 +1,49 @@ +import { Experiment } from '../../../../models/experiment/experiment.model'; +import { BarChartResult } from '../../../../models/result/bar-chart-result.model'; +import { HeatMapResult } from '../../../../models/result/heat-map-result.model'; +import BaseHandler from '../base.handler'; + +export default class PCAHandler extends BaseHandler { + canHandle(algorithm: string): boolean { + return algorithm.toLocaleLowerCase() === 'pca'; + } + + handle(exp: Experiment, data: unknown): void { + if (!this.canHandle(exp.algorithm.id)) return this.next?.handle(exp, data); + + const barChar: BarChartResult = { + name: 'Eigen values', + barValues: data['eigen_vals'], + xAxis: { + label: 'Dimensions', + categories: exp.variables.map((_, i) => i + 1).map(String), + }, + hasConnectedBars: true, + yAxis: { + label: 'Eigen values', + }, + }; + + if (barChar.barValues && barChar.barValues.length > 0) + exp.results.push(barChar); + + const matrix = data['eigen_vecs'] as number[][]; + + const heatMapChart: HeatMapResult = { + name: 'Eigen vectors', + matrix, + yAxis: { + categories: exp.variables, + }, + xAxis: { + categories: [...Array(matrix.length).keys()] + .map((i) => i + 1) + .map(String), + }, + }; + + if (matrix) exp.results.push(heatMapChart); + + this.next?.handle(exp, data); + } +} diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/area.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/area.handler.ts index 198175a884dce0f40293a5cfec038f3de2920973..6e096e31c85d37193089acc4039f2a949c2dfd2e 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/area.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/area.handler.ts @@ -1,10 +1,10 @@ import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata' -import { AlgoResults } from 'src/common/interfaces/utilities.interface'; +import { Experiment } from '../../../../../engine/models/experiment/experiment.model'; import { ResultChartExperiment } from '../../interfaces/experiment/result-chart-experiment.interface'; import BaseHandler from '../base.handler'; export default class AreaHandler extends BaseHandler { - static readonly transform = jsonata(` + private static readonly transform = jsonata(` ({ "name": data.title.text, "xAxis": { @@ -25,25 +25,31 @@ export default class AreaHandler extends BaseHandler { `); canHandle(input: ResultChartExperiment): boolean { - return ( - input.type === 'application/vnd.highcharts+json' && - input.data.chart.type === 'area' - ); + try { + return ( + input.type === 'application/vnd.highcharts+json' && + input.data?.chart?.type === 'area' + ); + } catch (e) { + AreaHandler.logger.log('Error when parsing input from experiment'); + AreaHandler.logger.debug(e); + return false; + } } - handle(algorithm: string, data: unknown, res: AlgoResults): void { + handle(exp: Experiment, data: unknown): void { let req = data; const inputs = data as ResultChartExperiment[]; - if (inputs) { + if (inputs && Array.isArray(inputs)) { inputs .filter(this.canHandle) .map((input) => AreaHandler.transform.evaluate(input)) - .forEach((input) => res.push(input)); + .forEach((input) => exp.results.push(input)); req = JSON.stringify(inputs.filter((input) => !this.canHandle(input))); } - this.next?.handle(algorithm, req, res); + this.next?.handle(exp, req); } } diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/descriptive.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/descriptive.handler.ts index a314a6bf6840e6b447a2ee5ddd881244b5a10793..d9fbc65f432ac29931d69428078d55e72eecd2a7 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/descriptive.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/descriptive.handler.ts @@ -1,11 +1,11 @@ -import { AlgoResults } from 'src/common/interfaces/utilities.interface'; +import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata' +import { Experiment } from '../../../../models/experiment/experiment.model'; import { GroupResult, GroupsResult, -} from 'src/engine/models/result/groups-result.model'; +} from '../../../../models/result/groups-result.model'; import { ResultExperiment } from '../../interfaces/experiment/result-experiment.interface'; import BaseHandler from '../base.handler'; -import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata' export default class DescriptiveHandler extends BaseHandler { private static readonly headerDescriptive = ` @@ -110,22 +110,24 @@ $fn := function($o, $prefix) { return result; } - handle(algorithm: string, data: unknown, res: AlgoResults): void { + handle(exp: Experiment, data: unknown): void { let req = data; - if (algorithm.toLowerCase() === 'descriptive_stats') { + if (exp.algorithm.id.toLowerCase() === 'descriptive_stats') { const inputs = data as ResultExperiment[]; - inputs - .filter((input) => input.type === 'application/json') - .map((input) => this.descriptiveDataToTableResult(input)) - .forEach((input) => res.push(input)); + if (inputs && Array.isArray(inputs)) { + inputs + .filter((input) => input.type === 'application/json') + .map((input) => this.descriptiveDataToTableResult(input)) + .forEach((input) => exp.results.push(input)); - req = JSON.stringify( - inputs.filter((input) => input.type !== 'application/json'), - ); + req = JSON.stringify( + inputs.filter((input) => input.type !== 'application/json'), + ); + } } - this.next?.handle(algorithm, req, res); + this.next?.handle(exp, req); } } diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/heat-map.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/heat-map.handler.ts index 20591ad7589b9e075d2266a22ae5ccd7f0acd7a8..47d937a04a5e0223ba6b636cd5cfe7094076dc8a 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/heat-map.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/heat-map.handler.ts @@ -1,10 +1,10 @@ -import { AlgoResults } from 'src/common/interfaces/utilities.interface'; +import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata' +import { Experiment } from '../../../../models/experiment/experiment.model'; import { ResultChartExperiment } from '../../interfaces/experiment/result-chart-experiment.interface'; import BaseHandler from '../base.handler'; -import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata' export default class HeatMapHandler extends BaseHandler { - static readonly transform = jsonata(` + private static readonly transform = jsonata(` ( { "name": data.title.text, @@ -28,19 +28,19 @@ export default class HeatMapHandler extends BaseHandler { ); } - handle(algorithm: string, data: unknown, res: AlgoResults): void { + handle(exp: Experiment, data: unknown): void { let req = data; const inputs = data as ResultChartExperiment[]; - if (inputs) { + if (inputs && Array.isArray(inputs)) { inputs .filter(this.canHandle) .map((input) => HeatMapHandler.transform.evaluate(input)) - .forEach((input) => res.push(input)); + .forEach((input) => exp.results.push(input)); req = JSON.stringify(inputs.filter((input) => !this.canHandle(input))); } - this.next?.handle(algorithm, req, res); + this.next?.handle(exp, req); } } diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3ca9a5a31aae0db39f78813c63013071b840ce0 --- /dev/null +++ b/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.spec.ts @@ -0,0 +1,435 @@ +import { HeatMapResult } from 'src/engine/models/result/heat-map-result.model'; +import handlers from '..'; +import { Experiment } from '../../../../models/experiment/experiment.model'; + +const createExperiment = (): Experiment => ({ + id: 'dummy-id', + name: 'Testing purpose', + algorithm: { + id: 'pearson', + }, + datasets: ['desd-synthdata'], + domain: 'dementia', + variables: [ + 'leftputamen', + 'leftttgtransversetemporalgyrus', + 'rightmfgmiddlefrontalgyrus', + 'leftpoparietaloperculum', + 'rightfrpfrontalpole', + 'rightppplanumpolare', + 'leftsmgsupramarginalgyrus', + 'rightcocentraloperculum', + ], + coVariables: [ + 'rightamygdala', + 'rightfugfusiformgyrus', + 'leftmcggmiddlecingulategyrus', + 'rightpoparietaloperculum', + 'rightacgganteriorcingulategyrus', + 'leftinflatvent', + 'leftlorglateralorbitalgyrus', + 'leftcerebellumwhitematter', + 'rightofugoccipitalfusiformgyrus', + 'rightpinsposteriorinsula', + 'leftliglingualgyrus', + 'rightaorganteriororbitalgyrus', + 'rightocpoccipitalpole', + 'rightlateralventricle', + 'leftcuncuneus', + 'rightioginferioroccipitalgyrus', + ], + results: [], +}); + +describe('Pearson result handler', () => { + const data = { + n_obs: 1840, + correlations: { + variables: [ + 'leftputamen', + 'leftttgtransversetemporalgyrus', + 'rightmfgmiddlefrontalgyrus', + 'leftpoparietaloperculum', + 'rightfrpfrontalpole', + 'rightppplanumpolare', + 'leftsmgsupramarginalgyrus', + 'rightcocentraloperculum', + ], + rightamygdala: [ + 0.5597275855729502, 0.521003857984853, 0.5493459247574832, + 0.4990055858928424, 0.5436898736026363, 0.5102812983261346, + 0.5143748265730225, 0.4722467679593771, + ], + rightfugfusiformgyrus: [ + 0.6236369193900397, 0.5554591047869487, 0.6325745785731374, + 0.5564649706753391, 0.5996324143525289, 0.5683013952276237, + 0.5513102320688864, 0.5743257964630883, + ], + leftmcggmiddlecingulategyrus: [ + 0.7376856963056209, 0.5331355949305163, 0.8325818878316201, + 0.6391784359303597, 0.7919997793931225, 0.5363656083162878, + 0.6238856655418632, 0.8104485180352985, + ], + rightpoparietaloperculum: [ + 0.6808861136901048, 0.659805294413907, 0.7125128077326375, + 0.833812063258428, 0.7210727503874539, 0.5882070783888582, + 0.6634669282893578, 0.8232885033629704, + ], + rightacgganteriorcingulategyrus: [ + 0.636479829062369, 0.5429447749241025, 0.7190201398356373, + 0.6204758861708148, 0.7260623375900093, 0.6053466056876111, + 0.655322719516193, 0.7186457783767399, + ], + leftinflatvent: [ + -0.14362476816074463, -0.054034584106918304, -0.11821687590010249, + -0.06544444615182315, -0.10633265521323353, -0.027040115545313083, + -0.17046474035585527, -0.022632778720409058, + ], + leftlorglateralorbitalgyrus: [ + 0.7597563664274374, 0.5659114420942543, 0.8452472555121631, + 0.6744203157440845, 0.8308566043773566, 0.5317586587141924, + 0.6991548143130566, 0.7767501778942923, + ], + leftcerebellumwhitematter: [ + 0.4672426439635395, 0.4675387937928246, 0.44045603915219855, + 0.4673988028139205, 0.46640877400281155, 0.47065769959466736, + 0.3997068887722457, 0.451019143091475, + ], + rightofugoccipitalfusiformgyrus: [ + 0.5680149671034159, 0.5262409699017856, 0.5839753893338389, + 0.5398424958483423, 0.556340087055964, 0.518748375513551, + 0.47428963144084374, 0.5616852958699678, + ], + rightpinsposteriorinsula: [ + 0.5572160702601328, 0.664894938465614, 0.5310698103788525, + 0.5955293874528285, 0.5631233256189295, 0.8552811696157994, + 0.49600493455959327, 0.6246177346436557, + ], + leftliglingualgyrus: [ + 0.4808478556018692, 0.5093081761811737, 0.4921805943517303, + 0.4825625261475997, 0.46947030793190964, 0.5067796702191458, + 0.4298526420673836, 0.4780868298557831, + ], + rightaorganteriororbitalgyrus: [ + 0.7607482217534212, 0.5763655233036808, 0.8646224385388447, + 0.6614380967851735, 0.8906692101232966, 0.5469536547168057, + 0.6611706227831682, 0.7932242851880642, + ], + rightocpoccipitalpole: [ + 0.44284674120236533, 0.4355081626563567, 0.4445242260919515, + 0.40613009484979723, 0.4132956707143595, 0.39301351966173986, + 0.3773895646446932, 0.4162450709224627, + ], + rightlateralventricle: [ + 0.0505077516744776, 0.13726405409235048, 0.06637815205132092, + 0.10405223013323087, 0.07226972265613646, 0.20162228961724815, + 0.0074181027599261335, 0.17544330355914306, + ], + leftcuncuneus: [ + 0.41048650901353273, 0.4343603456814044, 0.4078152624875396, + 0.3949323301818183, 0.3959200673317691, 0.41092528317389276, + 0.37647046461031386, 0.408870588377115, + ], + rightioginferioroccipitalgyrus: [ + 0.5044398174949668, 0.520079539158586, 0.5463949474024208, + 0.5046699205941667, 0.5432256637171967, 0.5225157940406768, + 0.47221482855748664, 0.5181316725208311, + ], + }, + 'p-values': { + variables: [ + 'leftputamen', + 'leftttgtransversetemporalgyrus', + 'rightmfgmiddlefrontalgyrus', + 'leftpoparietaloperculum', + 'rightfrpfrontalpole', + 'rightppplanumpolare', + 'leftsmgsupramarginalgyrus', + 'rightcocentraloperculum', + ], + rightamygdala: [ + 3.25878497534328e-152, 1.4292835080466475e-128, 1.4377632353608821e-145, + 1.9076637338233883e-116, 4.79209249174369e-142, 1.5010580913457995e-122, + 7.999217474230516e-125, 7.63659251960516e-103, + ], + rightfugfusiformgyrus: [ + 7.880767591256638e-199, 1.8769392348616705e-149, 3.083566666605416e-206, + 4.2322615339039046e-150, 4.422840355459025e-180, + 7.0046414182375614e-158, 8.286393012479547e-147, 5.782133121863308e-162, + ], + leftmcggmiddlecingulategyrus: [ + 6.80125926e-316, 1.1943475659485691e-135, 0.0, 7.195249745311181e-212, + 0.0, 1.3918667024509139e-137, 4.9392995773919805e-199, 0.0, + ], + rightpoparietaloperculum: [ + 6.794384226988926e-251, 2.188378648473448e-230, 3.949825103998713e-285, + 0.0, 3.2898829773162474e-295, 1.0550451663913097e-171, + 7.90886734535506e-234, 0.0, + ], + rightacgganteriorcingulategyrus: [ + 1.496840126287998e-209, 1.3791329370084863e-141, 9.299404204475335e-293, + 2.8781738258377686e-196, 2.919972809311805e-301, + 2.1251830789923035e-184, 3.0785875285875392e-226, + 2.589163623684831e-292, + ], + leftinflatvent: [ + 6.064677654872629e-10, 0.020452375563536022, 3.6697971151155235e-7, + 0.004979495668624701, 4.853388819714173e-6, 0.2463276104291352, + 1.8241228643539274e-13, 0.3318956876819963, + ], + leftlorglateralorbitalgyrus: [ + 0.0, 2.766991227573042e-156, 0.0, 2.0137850080792056e-244, 0.0, + 7.852865129161054e-135, 4.038754496383528e-270, 0.0, + ], + leftcerebellumwhitematter: [ + 1.9732288353813585e-100, 1.4240672172310133e-100, 3.515443053501476e-88, + 1.6615172288288418e-100, 4.934745063188765e-100, + 4.5006434906390576e-102, 1.5515164216307766e-71, 7.002915575530633e-93, + ], + rightofugoccipitalfusiformgyrus: [ + 1.0900010229936088e-157, 1.364233027309996e-131, + 1.1016599072076947e-168, 1.0931287224563064e-139, 5.09338172609745e-150, + 2.7519899124789254e-127, 7.700720307500303e-104, + 1.7120697796993193e-153, + ], + rightpinsposteriorinsula: [ + 1.3870444679224216e-150, 3.4872651813597626e-235, + 2.0081044688634357e-134, 4.931997404788463e-177, 1.94168660797323e-154, + 0.0, 7.357124541039134e-115, 1.2457938980398953e-199, + ], + leftliglingualgyrus: [ + 4.383285494317619e-107, 5.155563249457493e-122, 7.337430271855075e-113, + 6.049708225881905e-108, 1.6837129104819712e-101, + 1.2490548740603875e-120, 1.2595096430033426e-83, + 1.0386218167081171e-105, + ], + rightaorganteriororbitalgyrus: [ + 0.0, 2.2922172824148564e-163, 0.0, 6.475386505057067e-232, 0.0, + 4.52820402710442e-144, 1.1544409732861286e-231, 0.0, + ], + rightocpoccipitalpole: [ + 3.1375123209482334e-89, 4.9126071325068964e-86, 5.691489321496656e-90, + 5.219329092759199e-74, 7.830554375370253e-77, 5.134346992198931e-69, + 2.3418792852217334e-63, 5.144289096106288e-78, + ], + rightlateralventricle: [ + 0.030276690847126156, 3.380317160224418e-9, 0.004392413325060018, + 7.73266816312055e-6, 0.0019223367765046472, 2.4892019680584758e-18, + 0.7504930559128203, 3.470908728297453e-14, + ], + leftcuncuneus: [ + 1.0210799641591016e-75, 1.5272837780095206e-85, 1.1474018974579004e-74, + 9.864647241816193e-70, 4.2019208571769036e-70, 6.848004077142739e-76, + 4.931153238684641e-63, 4.423622763102229e-75, + ], + rightioginferioroccipitalgyrus: [ + 2.3295090635604702e-119, 4.81616669890162e-128, 1.0094654570929263e-143, + 1.748809779511419e-119, 9.26139603486897e-142, 1.94363033821541e-129, + 7.914506269450685e-103, 6.154408322606963e-127, + ], + }, + low_confidence_intervals: { + variables: [ + 'leftputamen', + 'leftttgtransversetemporalgyrus', + 'rightmfgmiddlefrontalgyrus', + 'leftpoparietaloperculum', + 'rightfrpfrontalpole', + 'rightppplanumpolare', + 'leftsmgsupramarginalgyrus', + 'rightcocentraloperculum', + ], + rightamygdala: [ + 0.5249098258491429, 0.4841365802420275, 0.5139629983199647, + 0.4610451060660259, 0.5080038833597089, 0.47287474525502154, + 0.47717270971386866, 0.4330256427218252, + ], + rightfugfusiformgyrus: [ + 0.592552977469149, 0.5204075786941283, 0.6020478772444463, + 0.5214683560620843, 0.5670945315892311, 0.5339590772902444, + 0.5160333568669146, 0.5403222582888416, + ], + leftmcggmiddlecingulategyrus: [ + 0.7143639967539037, 0.4968932100843397, 0.8168077151171453, + 0.609069019131725, 0.7728758424239124, 0.5002922402855292, + 0.5928171155208684, 0.7928246920919761, + ], + rightpoparietaloperculum: [ + 0.6535214123861091, 0.6310296803365533, 0.6873557216616649, + 0.8181422957903792, 0.6965319806241004, 0.5549989676962152, + 0.6349329141319887, 0.8067310515593928, + ], + rightacgganteriorcingulategyrus: [ + 0.606199322371302, 0.5072191152027564, 0.6943308500994183, + 0.589196928124056, 0.7018845242490084, 0.5731491954798821, + 0.6262533213829016, 0.6939294509097995, + ], + leftinflatvent: [ + -0.19157164006169264, -0.10306344153089386, -0.16654956992836623, + -0.11437881513386053, -0.1548244060266091, -0.07624189983236083, + -0.21793652146135434, -0.07185602421196502, + ], + leftlorglateralorbitalgyrus: [ + 0.738101170098229, 0.5314358034122589, 0.8305560787693618, + 0.646617724836663, 0.8149362908785072, 0.49544456094879824, + 0.673051978854646, 0.7564147468681321, + ], + leftcerebellumwhitematter: [ + 0.4277941666823468, 0.4281036979068707, 0.3998354347218368, + 0.42795738034621744, 0.42692266838716936, 0.4313640919576996, + 0.357447653243165, 0.41085173909249084, + ], + rightofugoccipitalfusiformgyrus: [ + 0.5336566388411408, 0.48964152843148717, 0.5505226019762696, + 0.5039523057664171, 0.5213366490075679, 0.4817666467297378, + 0.4351620792763062, 0.5269754034645308, + ], + rightpinsposteriorinsula: [ + 0.5222605296683821, 0.6364555414239462, 0.4947199121490947, + 0.5627491991846226, 0.5284929280790968, 0.8414607036679829, + 0.4578993171542838, 0.5935945223379712, + ], + leftliglingualgyrus: [ + 0.4420236810840954, 0.47185328590704667, 0.4538913812845799, + 0.4438184203420561, 0.4301227136135017, 0.469199653222301, + 0.3887888985950984, 0.43913437885781215, + ], + rightaorganteriororbitalgyrus: [ + 0.7391691782605253, 0.542477569973228, 0.8516227073838096, + 0.6327700374070443, 0.8800096874264628, 0.5114421283072862, + 0.6324849247259476, 0.7741987455256272, + ], + rightocpoccipitalpole: [ + 0.4023276806514739, 0.39467930685195485, 0.4040767744897971, + 0.36411761469429316, 0.3715635538502939, 0.35050173376600824, + 0.3343064428829883, 0.3746299139622512, + ], + rightlateralventricle: [ + 0.0012073304453156647, 0.08855996374328491, 0.017130787743867445, + 0.055031119287923994, 0.02304841978915614, 0.15384821623276107, + -0.04190064962521336, 0.12724050059110326, + ], + leftcuncuneus: [ + 0.36864383979654575, 0.39348354671131447, 0.36586823403787583, + 0.35249246570152476, 0.35351737527789645, 0.3690998271705332, + 0.33335452500661206, 0.36696469983336766, + ], + rightioginferioroccipitalgyrus: [ + 0.46674461400320916, 0.48316529283757464, 0.5108534748941154, + 0.46698601888599106, 0.5075149517179784, 0.4857255400931659, + 0.4329922438384865, 0.48111874424540896, + ], + }, + high_confidence_intervals: { + variables: [ + 'leftputamen', + 'leftttgtransversetemporalgyrus', + 'rightmfgmiddlefrontalgyrus', + 'leftpoparietaloperculum', + 'rightfrpfrontalpole', + 'rightppplanumpolare', + 'leftsmgsupramarginalgyrus', + 'rightcocentraloperculum', + ], + rightamygdala: [ + 0.592675257341564, 0.5560245301139938, 0.5828627270461675, + 0.5351430571087057, 0.5775126281154671, 0.5458518484011133, + 0.5497366888418282, 0.5096830493321188, + ], + rightfugfusiformgyrus: [ + 0.6528663787572359, 0.5886419614911998, 0.6612547309235441, + 0.5895925549423322, 0.630301646189045, 0.6007716771157602, + 0.5847200996580633, 0.6064566577450156, + ], + leftmcggmiddlecingulategyrus: [ + 0.7593704901847629, 0.5675214864150775, 0.8471120919120667, + 0.6674481122142404, 0.8096863292852055, 0.5705802340388453, + 0.6530999340401716, 0.8267180374165225, + ], + rightpoparietaloperculum: [ + 0.7064732217324503, 0.6867677133428213, 0.7359623749453352, + 0.8482443418500734, 0.7439285091847726, 0.6195433613147318, + 0.6901933089646523, 0.8385542225749278, + ], + rightacgganteriorcingulategyrus: [ + 0.6649177151689354, 0.5768076173541662, 0.7420188813530194, + 0.6498979049428133, 0.7485689708951747, 0.6356778080210272, + 0.6825724654589472, 0.7416705572758208, + ], + leftinflatvent: [ + -0.09499401139529606, -0.00474379427299377, -0.0693174648197171, + -0.016193268196372335, -0.05732978255946915, 0.022293032595298155, + -0.12218824322830613, 0.0267004433284245, + ], + leftlorglateralorbitalgyrus: [ + 0.7798477929960332, 0.598515466029371, 0.858762950723273, + 0.7004334668584968, 0.8455239236833967, 0.5662172712073998, + 0.723518047599173, 0.7955855079728452, + ], + leftcerebellumwhitematter: [ + 0.5049145254575481, 0.505196796229598, 0.4793499078802479, + 0.5050633669299481, 0.504119691514346, 0.5081690453783337, + 0.44033271504378585, 0.48943901828784, + ], + rightofugoccipitalfusiformgyrus: [ + 0.6005013048211433, 0.5609892599715158, 0.6155557432422029, + 0.5738717052177915, 0.5894745386805359, 0.5538855850628387, + 0.5116290714641561, 0.5945245490838112, + ], + rightpinsposteriorinsula: [ + 0.5903023216810375, 0.6915289413647547, 0.5655647386991592, + 0.6264395233442352, 0.5958827190545568, 0.8679832260594337, + 0.5322913147493766, 0.6537872636083, + ], + leftliglingualgyrus: [ + 0.5178738170046137, 0.5449281109150198, 0.5286555654925087, + 0.5195058769961408, 0.5070375923632664, 0.5425275190060376, + 0.4692119627587552, 0.5152452472599367, + ], + rightaorganteriororbitalgyrus: [ + 0.7807670338856209, 0.6083807202616289, 0.8765591575043511, + 0.6882954090835207, 0.9004319299639187, 0.580600216404521, + 0.6880451692119877, 0.8108177057900682, + ], + rightocpoccipitalpole: [ + 0.4816342334010153, 0.47462051717744624, 0.4832367624311347, + 0.44649312035401567, 0.45336100955683456, 0.43390912768657663, + 0.4188987119663672, 0.45618649847294657, + ], + rightlateralventricle: [ + 0.09956324648176589, 0.18531335730033968, 0.11530422704768956, + 0.15257293860809273, 0.1211415056300567, 0.2484558992468594, + 0.05670079286121268, 0.22281935363113622, + ], + leftcuncuneus: [ + 0.45066911849201374, 0.4735230620985795, 0.44810870721124, + 0.43575102417549455, 0.43669903537777843, 0.4510896238467394, + 0.41801499711842355, 0.44912032456189893, + ], + rightioginferioroccipitalgyrus: [ + 0.5403055163205017, 0.5551480255560861, 0.5800717391366282, + 0.5405240521245662, 0.5770733996480748, 0.5574580870477, + 0.5096526209667468, 0.5533006654400224, + ], + }, + }; + + it('Test pearson handler with regular data', () => { + const exp = createExperiment(); + const names = [ + 'correlations', + 'p-values', + 'low_confidence_intervals', + 'high_confidence_intervals', + ]; + + handlers(exp, data); + const results = exp.results as HeatMapResult[]; + + const heatmaps = names.map((name) => + results.find((it) => it.name === name), + ); + + expect(heatmaps.length).toBeGreaterThanOrEqual(4); + }); +}); diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.ts index 16702657df654d7c282efe54094ce46e4fb5e497..0832d960e4ceb8bcf29551e6aaf98ebf9ffff2a5 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.ts @@ -1,11 +1,11 @@ -import { Expression } from 'jsonata'; import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata' -import { AlgoResults } from 'src/common/interfaces/utilities.interface'; -import { HeatMapResult } from 'src/engine/models/result/heat-map-result.model'; +import { Expression } from 'jsonata'; +import { Experiment } from '../../../../models/experiment/experiment.model'; +import { HeatMapResult } from '../../../../models/result/heat-map-result.model'; import BaseHandler from '../base.handler'; export default class PearsonHandler extends BaseHandler { - readonly transform: Expression = jsonata(` + private static readonly transform: Expression = jsonata(` ( $params := ['correlations', 'p-values', 'low_confidence_intervals', 'high_confidence_intervals']; @@ -23,17 +23,32 @@ export default class PearsonHandler extends BaseHandler { }) )`); + /** + * This function returns true if the algorithm is Pearson. + * @param {string} algorithm - The name of the algorithm to use. + * @returns a boolean value. + */ canHandle(algorithm: string): boolean { return algorithm.toLocaleLowerCase() === 'pearson'; } - handle(algorithm: string, data: unknown, res: AlgoResults): void { - if (this.canHandle(algorithm)) { + /** + * If the algorithm is Pearson, then transform the data into a HeatMapResult and push it into the + * results array + * @param {string} algorithm - The name of the algorithm. + * @param {unknown} data - The data that is passed to the algorithm. + * @param {AlgoResults} res - list of possible results + * @returns + */ + handle(exp: Experiment, data: unknown): void { + if (this.canHandle(exp.algorithm.id)) { try { - const results = this.transform.evaluate(data) as HeatMapResult[]; + const results = PearsonHandler.transform.evaluate( + data, + ) as HeatMapResult[]; results .filter((heatMap) => heatMap.matrix.length > 0 && heatMap.name) - .forEach((heatMap) => res.push(heatMap)); + .forEach((heatMap) => exp.results.push(heatMap)); } catch (e) { PearsonHandler.logger.warn( 'An error occur when converting result from Pearson', @@ -42,6 +57,6 @@ export default class PearsonHandler extends BaseHandler { } } - this.next?.handle(algorithm, data, res); + this.next?.handle(exp, data); } } diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/raw.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/raw.handler.ts index 810be82cecd06bb193adaefb88fc50ccbdaa2fad..2e0dd77d9f86138dca531e1a0680286ba03fb40c 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/raw.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/raw.handler.ts @@ -1,8 +1,6 @@ -import { - AlgoResults, - MIME_TYPES, -} from 'src/common/interfaces/utilities.interface'; -import { RawResult } from 'src/engine/models/result/raw-result.model'; +import { MIME_TYPES } from '../../../../../common/interfaces/utilities.interface'; +import { Experiment } from '../../../../models/experiment/experiment.model'; +import { RawResult } from '../../../../models/result/raw-result.model'; import { ResultExperiment } from '../../interfaces/experiment/result-experiment.interface'; import BaseHandler from '../base.handler'; @@ -17,13 +15,15 @@ export default class RawHandler extends BaseHandler { return { rawdata: data }; }; - handle(algorithm: string, data: unknown, res: AlgoResults): void { + handle(exp: Experiment, data: unknown): void { const inputs = data as ResultExperiment[]; - inputs - .map((input) => this.dataToRaw(algorithm, input)) - .forEach((input) => res.push(input)); + if (inputs && Array.isArray(inputs)) + inputs + .filter((input) => !!input.data && !!input.type) + .map((input) => this.dataToRaw(exp.algorithm.id, input)) + .forEach((input) => exp.results.push(input)); - this.next?.handle(algorithm, data, res); + this.next?.handle(exp, data); } } diff --git a/api/src/engine/connectors/exareme/handlers/base.handler.ts b/api/src/engine/connectors/exareme/handlers/base.handler.ts index 2f72f190258e37f1bd689cdd6163b32699586b52..6f84123e0514cfe937c48366bccbacd7763d99cb 100644 --- a/api/src/engine/connectors/exareme/handlers/base.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/base.handler.ts @@ -1,5 +1,5 @@ import { Logger } from '@nestjs/common'; -import { AlgoResults } from 'src/common/interfaces/utilities.interface'; +import { Experiment } from '../../../models/experiment/experiment.model'; import ResultHandler from './result-handler.interface'; export default abstract class BaseHandler implements ResultHandler { @@ -12,7 +12,7 @@ export default abstract class BaseHandler implements ResultHandler { return h; } - handle(algorithm: string, data: unknown, res: AlgoResults): void { - this.next?.handle(algorithm, data, res); + handle(partialExperiment: Experiment, data: unknown): void { + this.next?.handle(partialExperiment, data); } } diff --git a/api/src/engine/connectors/exareme/handlers/index.ts b/api/src/engine/connectors/exareme/handlers/index.ts index 7e153ece232bc26a5a2de18d65265bdc1b34f0eb..f9c6c47a8a5dd4dd9b12a817fe77054baf9e85d5 100644 --- a/api/src/engine/connectors/exareme/handlers/index.ts +++ b/api/src/engine/connectors/exareme/handlers/index.ts @@ -1,19 +1,21 @@ -import { AlgoResults } from 'src/common/interfaces/utilities.interface'; +import { Experiment } from '../../../../engine/models/experiment/experiment.model'; import AreaHandler from './algorithms/area.handler'; import DescriptiveHandler from './algorithms/descriptive.handler'; import HeatMapHandler from './algorithms/heat-map.handler'; -import { - default as PearsonHandler, - default as RawHandler, -} from './algorithms/raw.handler'; +import PCAHandler from './algorithms/PCA.handler'; +import PearsonHandler from './algorithms/pearson.handler'; +import RawHandler from './algorithms/raw.handler'; -const last = new RawHandler(); // should be last handler as it works as a fallback (if other handlers could not process the results) -const start = new PearsonHandler() +const start = new PearsonHandler(); + +start .setNext(new AreaHandler()) .setNext(new DescriptiveHandler()) .setNext(new HeatMapHandler()) - .setNext(last); + .setNext(new PCAHandler()) + .setNext(new RawHandler()); // should be last handler as it works as a fallback (if other handlers could not process the results) -export default (algo: string, data: unknown, res: AlgoResults) => { - start.handle(algo, data, res); +export default (exp: Experiment, data: unknown): Experiment => { + start.handle(exp, data); + return exp; }; diff --git a/api/src/engine/connectors/exareme/handlers/result-handler.interface.ts b/api/src/engine/connectors/exareme/handlers/result-handler.interface.ts index 93f98a9e3043a793a08b233ae66965ebc1e93495..661c2a437015b8a75950394a2f27380602cbb25b 100644 --- a/api/src/engine/connectors/exareme/handlers/result-handler.interface.ts +++ b/api/src/engine/connectors/exareme/handlers/result-handler.interface.ts @@ -1,7 +1,7 @@ -import { AlgoResults } from 'src/common/interfaces/utilities.interface'; +import { Experiment } from '../../../models/experiment/experiment.model'; // produce algo handler export default interface ResultHandler { setNext(h: ResultHandler): ResultHandler; - handle(algorithm: string, data: unknown, res: AlgoResults): void; + handle(partialExperiment: Experiment, data: unknown): void; } diff --git a/api/src/engine/models/result/bar-chart-result.model.ts b/api/src/engine/models/result/bar-chart-result.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ba9c0498197f3e5b9afa097cfd030b51e0f559f --- /dev/null +++ b/api/src/engine/models/result/bar-chart-result.model.ts @@ -0,0 +1,21 @@ +import { Field, ObjectType } from '@nestjs/graphql'; +import { ChartAxis } from './common/chart-axis.model'; +import { Result } from './common/result.model'; + +@ObjectType() +export class BarChartResult extends Result { + @Field() + name: string; + + @Field(() => ChartAxis, { nullable: true }) + xAxis?: ChartAxis; + + @Field(() => ChartAxis, { nullable: true }) + yAxis?: ChartAxis; + + @Field(() => [Number], { description: "List of bar's value" }) + barValues: number[]; + + @Field({ defaultValue: false, nullable: true }) + hasConnectedBars?: boolean; +} diff --git a/api/src/engine/models/result/common/result-union.model.ts b/api/src/engine/models/result/common/result-union.model.ts index bc4bb4a1472ea131fd8696da73d5c3d227c44a8f..5c2ffc69597c6b1afc39ea624972586285d6d3e4 100644 --- a/api/src/engine/models/result/common/result-union.model.ts +++ b/api/src/engine/models/result/common/result-union.model.ts @@ -1,4 +1,5 @@ import { createUnionType } from '@nestjs/graphql'; +import { BarChartResult } from '../bar-chart-result.model'; import { GroupsResult } from '../groups-result.model'; import { HeatMapResult } from '../heat-map-result.model'; import { LineChartResult } from '../line-chart-result.model'; @@ -13,6 +14,7 @@ export const ResultUnion = createUnionType({ GroupsResult, HeatMapResult, LineChartResult, + BarChartResult, ], resolveType(value) { if (value.headers) { @@ -31,6 +33,10 @@ export const ResultUnion = createUnionType({ return LineChartResult; } + if (value.barValues) { + return BarChartResult; + } + return RawResult; }, }); diff --git a/api/src/engine/models/result/heat-map-result.model.ts b/api/src/engine/models/result/heat-map-result.model.ts index 8dd65ada27662536bffa7aeea6c8b78b0759c2fa..9187424d856047565fe7e7ce1c503e9bc17ac4e8 100644 --- a/api/src/engine/models/result/heat-map-result.model.ts +++ b/api/src/engine/models/result/heat-map-result.model.ts @@ -1,7 +1,17 @@ -import { Field, ObjectType } from '@nestjs/graphql'; +import { Field, ObjectType, registerEnumType } from '@nestjs/graphql'; import { ChartAxis } from './common/chart-axis.model'; import { Result } from './common/result.model'; +export enum HeatMapStyle { + NORMAL, + BUBBLE, +} + +registerEnumType(HeatMapStyle, { + name: 'HeatMapStyle', + description: 'Type of display.', +}); + @ObjectType() export class HeatMapResult extends Result { @Field() @@ -15,4 +25,10 @@ export class HeatMapResult extends Result { @Field(() => [[Number]]) matrix: number[][]; + + @Field(() => HeatMapStyle, { + defaultValue: HeatMapStyle.NORMAL, + nullable: true, + }) + heatMapStyle?: HeatMapStyle; } diff --git a/api/src/jest.config.ts b/api/src/jest.config.ts index 09e402b93abdd342aa1ba638532a1805527a247d..6eb49ecdaa3b6dce51035f457e61df552891d379 100644 --- a/api/src/jest.config.ts +++ b/api/src/jest.config.ts @@ -14,6 +14,7 @@ export default async (): Promise<Config.InitialOptions> => { return { moduleFileExtensions: ['js', 'json', 'ts'], + slowTestThreshold: 30, testPathIgnorePatterns: dirs, rootDir: 'src', testRegex: '.*\\.spec\\.ts$', diff --git a/api/src/schema.gql b/api/src/schema.gql index b940e0ec06d23a0e87f41a791a669163fb88fe47..8a0e2400bf297c9d066ef68dc77ec2cee3b7672f 100644 --- a/api/src/schema.gql +++ b/api/src/schema.gql @@ -85,13 +85,21 @@ type Algorithm { description: String } +type ChartAxis { + """label of the Axis""" + label: String + + """label of each element on this Axis""" + categories: [String!] +} + type GroupResult { name: String! description: String results: [ResultUnion!]! } -union ResultUnion = TableResult | RawResult | GroupsResult | HeatMapResult | LineChartResult +union ResultUnion = TableResult | RawResult | GroupsResult | HeatMapResult | LineChartResult | BarChartResult type TableResult { name: String! @@ -123,6 +131,13 @@ type HeatMapResult { xAxis: ChartAxis yAxis: ChartAxis matrix: [[Float!]!]! + heatMapStyle: HeatMapStyle +} + +"""Type of display.""" +enum HeatMapStyle { + NORMAL + BUBBLE } type LineChartResult { @@ -132,12 +147,14 @@ type LineChartResult { lines: [LineResult!]! } -type ChartAxis { - """label of the Axis""" - label: String +type BarChartResult { + name: String! + xAxis: ChartAxis + yAxis: ChartAxis - """label of each element on this Axis""" - categories: [String!] + """List of bar's value""" + barValues: [Float!]! + hasConnectedBars: Boolean } type ExtraLineInfo {