import { Component, OnInit } from '@angular/core'; import { ChartOptions } from 'chart.js'; import { ApiService } from '../services/api.service'; import { animate, style, transition, trigger } from '@angular/animations'; import cloneDeep from 'lodash/cloneDeep'; import omit from 'lodash/omit'; import { DataService } from '../services/data.service'; import { LoginService } from '../services/login.service'; import { ModelItemModel } from '../models/model-item.model'; import * as FileSaver from 'file-saver'; import * as pdfMake from "pdfmake/build/pdfmake"; import * as pdfFonts from 'pdfmake/build/vfs_fonts'; (pdfMake).vfs = pdfFonts.pdfMake.vfs; @Component({ selector: 'app-prediction', templateUrl: './prediction.component.html', styleUrls: ['./prediction.component.scss'], animations: [ trigger( 'inOut', [ transition(':enter', [ style({height: 0, opacity: 0}), animate('0.5s ease-out', style({height: '*', opacity: 1})) ]), transition(':leave', [ style({height: '*', opacity: 1}), animate('0.5s ease-in', style({height: 0, opacity: 0})) ]) ] ) ] }) export class PredictionComponent implements OnInit { result: { predictions: any[], mean: any[] }; // Prediction result from python container loading = false; activeGroup: ModelItemModel = new ModelItemModel(); activeModelIndex = 0; // If true, spectra belong to different samples, otherwise multiple spectra from the same sample are given multipleSamples = false; spectrumNames: string[] = []; spectrum: string[][] = [[]]; flattenedSpectra = []; chart = []; readonly chartInit = { data: [], label: 'Spectrum', showLine: true, fill: false, pointRadius: 0, borderColor: '#00a8b0', borderWidth: 2 }; readonly chartOptions: ChartOptions = { scales: { xAxes: [{ticks: {min: 400, max: 4000, stepSize: 400, reverse: true}}], yAxes: [{ticks: {}}] }, responsive: true, tooltips: {enabled: false}, hover: {mode: null}, maintainAspectRatio: true, plugins: {datalabels: {display: false}} }; constructor( private api: ApiService, public d: DataService, public login: LoginService ) { this.chart[0] = cloneDeep(this.chartInit); } ngOnInit(): void { this.d.load('modelGroups', () => { this.activeGroup = this.d.arr.modelGroups[0]; }); } fileToArray(files) { this.loading = true; this.flattenedSpectra = []; this.chart = []; let load = files.length; this.spectrumNames = files.map(e => e.name); for (const i in files) { if (files.hasOwnProperty(i)) { const fileReader = new FileReader(); fileReader.onload = () => { // Parse to database spectrum representation this.spectrum = fileReader.result.toString().split('\r\n').map(e => e.split(',').map(el => parseFloat(el))) .filter(el => el.length === 2) as any; // Flatten to format needed for prediction this.flattenedSpectra[i] = {labels: this.spectrum.map(e => e[0]), values: this.spectrum.map(e => e[1])}; // Add to chart this.chart[i] = cloneDeep(this.chartInit); this.chart[i].data = this.spectrum.map(e => ({x: parseFloat(e[0]), y: parseFloat(e[1])})); load --; if (load <= 0) { // All loaded this.loadPrediction(); } }; fileReader.readAsText(files[i]); } } } loadPrediction() { this.loading = true; this.api.post(this.activeGroup.models[this.activeModelIndex].url, this.flattenedSpectra, data => { let tmp = Object.entries(omit(data, ['mean', 'std', 'label'])) // Form: [[label, [{value, color}]]] .map((entry: any) => entry[1].map(e => ({category: entry[0], label: data.label[entry[0]], value: e.value, color: e.color}))); // Form: [[{category, label, value, color}]] this.result = { predictions: tmp[0].map((ignore, columnIndex) => tmp.map(row => row[columnIndex])), // Transpose tmp mean: Object.entries(data.mean) .map((entry:any) => ({category: entry[0], label: data.label[entry[0]], value: entry[1].value, color: entry[1].color, std: data.std[entry[0]]})) // Form: [{category, label, value, color}] }; this.loading = false; }); } groupChange(index) { // Group was changed this.activeGroup = this.d.arr.modelGroups[index]; this.activeModelIndex = 0; this.result = undefined; } // Aggregates spectrum names and prediction values into an associative array prepareExport() { const zip = (a, b) => a.map((k, i) => [k, b[i]]); const values = this.result.predictions .map(prediction => prediction .filter(field => field.category === 'Prediction') .map(field => field.value)); return zip(this.spectrumNames, values); } // Converts the prediction results to a CSV file exportCSV() { const predictions = this.prepareExport(); const csv = predictions.map(line => line.join(";")).join("\n"); FileSaver.saveAs(new Blob([csv], { type: 'text/csv;charset=utf-8' }), "predictions.csv"); } // Converts the prediction results to a PDF file exportPDF() { const dd = { content: [ { text: 'DeFinMa - Decoding the Fingerprint of Materials by AI', style: 'header' }, { table: { body: [ [new Date().toLocaleDateString(), 'Security class', 'Person in charge', 'Phone'], [this.activeGroup.group, 'Intern', { stack: [ 'CR/APS1-Lotter', 'CR/APS1-Lingenfelser' ] }, { stack: [ '0711/811-49017', '0711/811-6897' ] } ] ] } }, { bold: true, text: 'Customer' }, this.login.username, { text: 'Prediction of ' + this.activeGroup.group + ' (' + this.activeGroup.models[this.activeModelIndex].name + ')*', style: 'subheader' }, { text: this.result.mean.map(e => e.category + ' ' + e.value + ' ' + e.label + ' ' + (e.std !== '' ? (' (standard deviation: ' + e.std + ')') : '')) }, { table: { widths: ['*', 'auto'], body: [ ['Input Data / Sample Name', 'Prediction*'] ].concat(this.prepareExport()) } }, { text: 'Reference Data', style: 'subheader' }, { image: document.getElementsByTagName('canvas')[0].toDataURL('image/png'), width: 500 }, { text: '*Disclaimer: This tool is still under development and Testing', style: 'subsubheader' }, { text: [ 'The prediction and classification of material parameters are validated only for certain conditions.', 'These results may therefore under no circumstances be used to evaluate quality-relevant issues.', 'For more details please contact ', { text: 'CR/APS1-Lingenfelder', link: 'mailto:dominic.lingenfelser@bosch.com' }, '.' ] }, { table: { body: [ ['Pr\u00fcfung', 'Freigabe', 'Datum'], ['CR/APS1-Lotter', 'CR/APS1-Lingenfelser', new Date().toLocaleDateString()] ] } }, '\u00a9 Alle Rechte bei Robert Bosch GmbH, auch f\u00fcr den Fall von Schutzreichtsanmeldungen. Jede Verf\u00fcgungsbefugnis, wie Kopier- und Weitergaberecht, bei uns.' ], styles: { header: { fontSize: 18, bold: true }, subheader: { fontSize: 15, bold: true }, subsubheader: { bold: true } } }; pdfMake.createPdf(dd).download(); } }