From 1f1af1b2f72b3cad4d94380468cedb91564ac502 Mon Sep 17 00:00:00 2001 From: Viktoriia Fedotkina <94109373+ViktoriiaFedotkina@users.noreply.github.com> Date: Wed, 19 Jan 2022 12:58:13 +0300 Subject: [PATCH] #1172 extend ketcher api add possibility to run server functions (#1173) * Add get server to ketcher * Add server method in ketcher * Refactor server method in ketcher * Fix ts errors * Fix prettier * Refactored indigo functions * Refactor indigo methods --- .../ketcher-core/src/application/indigo.ts | 212 ++++++++++++++++++ .../src/application/indigo.types.ts | 19 ++ .../ketcher-core/src/application/ketcher.ts | 11 +- .../services/struct/structService.types.ts | 32 ++- .../services/struct/indigoWorker.types.ts | 8 + .../struct/standaloneStructService.ts | 13 +- 6 files changed, 282 insertions(+), 13 deletions(-) create mode 100644 packages/ketcher-core/src/application/indigo.ts create mode 100644 packages/ketcher-core/src/application/indigo.types.ts diff --git a/packages/ketcher-core/src/application/indigo.ts b/packages/ketcher-core/src/application/indigo.ts new file mode 100644 index 0000000000..95de949a50 --- /dev/null +++ b/packages/ketcher-core/src/application/indigo.ts @@ -0,0 +1,212 @@ +/**************************************************************************** + * Copyright 2021 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +import { + AutomapMode, + CalculateProps, + CalculateResult, + CheckResult, + CheckTypes, + ChemicalMimeType, + ConvertResult, + InfoResult, + OutputFormatType, + StructService +} from 'domain/services' +import { StructOrString } from 'application/indigo.types' +import { KetSerializer } from 'domain/serializers' +import { Struct } from 'domain/entities' + +const defaultTypes: Array = [ + 'radicals', + 'pseudoatoms', + 'stereo', + 'query', + 'overlapping_atoms', + 'overlapping_bonds', + 'rgroups', + 'chiral', + '3d' +] +const defaultCalcProps: Array = [ + 'molecular-weight', + 'most-abundant-mass', + 'monoisotopic-mass', + 'gross', + 'mass-composition' +] + +type ConvertOptions = { + outputFormat?: ChemicalMimeType +} +type AutomapOptions = { + mode?: AutomapMode +} +type CheckOptions = { + types?: Array +} +type CalculateOptions = { + properties?: Array +} +type RecognizeOptions = { + version?: string +} +type GenerateImageOptions = { + outputFormat?: OutputFormatType + backgroundColor?: string +} + +function convertStructToString( + struct: StructOrString, + serializer: KetSerializer +): string { + if (typeof struct !== 'string') { + const aidMap = new Map() + const result = struct.clone(null, null, false, aidMap) + + return serializer.serialize(result) + } + + return struct +} + +export class Indigo { + #structService: StructService + #ketSerializer: KetSerializer + + constructor(structService) { + this.#structService = structService + this.#ketSerializer = new KetSerializer() + } + + info(): Promise { + return this.#structService.info() + } + + convert( + struct: StructOrString, + options?: ConvertOptions + ): Promise { + const outputFormat = options?.outputFormat || ChemicalMimeType.KET + + return this.#structService.convert({ + struct: convertStructToString(struct, this.#ketSerializer), + output_format: outputFormat + }) + } + + layout(struct: StructOrString): Promise { + return this.#structService + .layout({ + struct: convertStructToString(struct, this.#ketSerializer), + output_format: ChemicalMimeType.KET + }) + .then((data) => this.#ketSerializer.deserialize(data.struct)) + } + + clean(struct: StructOrString): Promise { + return this.#structService + .clean({ + struct: convertStructToString(struct, this.#ketSerializer), + output_format: ChemicalMimeType.KET + }) + .then((data) => this.#ketSerializer.deserialize(data.struct)) + } + + aromatize(struct: StructOrString): Promise { + return this.#structService + .aromatize({ + struct: convertStructToString(struct, this.#ketSerializer), + output_format: ChemicalMimeType.KET + }) + .then((data) => this.#ketSerializer.deserialize(data.struct)) + } + + dearomatize(struct: StructOrString): Promise { + return this.#structService + .dearomatize({ + struct: convertStructToString(struct, this.#ketSerializer), + output_format: ChemicalMimeType.KET + }) + .then((data) => this.#ketSerializer.deserialize(data.struct)) + } + + calculateCip(struct: StructOrString): Promise { + return this.#structService + .calculateCip({ + struct: convertStructToString(struct, this.#ketSerializer), + output_format: ChemicalMimeType.KET + }) + .then((data) => this.#ketSerializer.deserialize(data.struct)) + } + + automap(struct: StructOrString, options?: AutomapOptions): Promise { + const mode = options?.mode || 'discard' + + return this.#structService + .automap({ + struct: convertStructToString(struct, this.#ketSerializer), + output_format: ChemicalMimeType.KET, + mode + }) + .then((data) => this.#ketSerializer.deserialize(data.struct)) + } + + check(struct: StructOrString, options?: CheckOptions): Promise { + const types = options?.types || defaultTypes + + return this.#structService.check({ + struct: convertStructToString(struct, this.#ketSerializer), + types + }) + } + + calculate( + struct: StructOrString, + options?: CalculateOptions + ): Promise { + const properties = options?.properties || defaultCalcProps + + return this.#structService.calculate({ + struct: convertStructToString(struct, this.#ketSerializer), + properties + }) + } + + recognize(image: Blob, options?: RecognizeOptions): Promise { + const version = options?.version || '' + + return this.#structService + .recognize(image, version) + .then((data) => this.#ketSerializer.deserialize(data.struct)) + } + + generateImageAsBase64( + struct: StructOrString, + options?: GenerateImageOptions + ): Promise { + const outputFormat = options?.outputFormat || 'png' + const backgroundColor = options?.backgroundColor || '' + + return this.#structService.generateImageAsBase64( + convertStructToString(struct, this.#ketSerializer), + { + outputFormat, + backgroundColor + } + ) + } +} diff --git a/packages/ketcher-core/src/application/indigo.types.ts b/packages/ketcher-core/src/application/indigo.types.ts new file mode 100644 index 0000000000..345cc0f20a --- /dev/null +++ b/packages/ketcher-core/src/application/indigo.types.ts @@ -0,0 +1,19 @@ +/**************************************************************************** + * Copyright 2021 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +import { Struct } from 'domain/entities' + +export type StructOrString = Struct | string diff --git a/packages/ketcher-core/src/application/ketcher.ts b/packages/ketcher-core/src/application/ketcher.ts index e6dce07737..c385237d51 100644 --- a/packages/ketcher-core/src/application/ketcher.ts +++ b/packages/ketcher-core/src/application/ketcher.ts @@ -16,8 +16,8 @@ import { FormatterFactory, - SupportedFormat, - identifyStructFormat + identifyStructFormat, + SupportedFormat } from './formatters' import { GenerateImageOptions, StructService } from 'domain/services' @@ -25,6 +25,7 @@ import { Editor } from './editor' import { MolfileFormat } from 'domain/serializers' import { Struct } from 'domain/entities' import assert from 'assert' +import { Indigo } from 'application/indigo' function parseStruct(structStr: string, structService: StructService) { const format = identifyStructFormat(structStr) @@ -47,6 +48,7 @@ export class Ketcher { #structService: StructService #formatterFactory: FormatterFactory #editor: Editor + #indigo: Indigo get editor(): Editor { return this.#editor @@ -64,6 +66,11 @@ export class Ketcher { this.#editor = editor this.#structService = structService this.#formatterFactory = formatterFactory + this.#indigo = new Indigo(this.#structService) + } + + get indigo() { + return this.#indigo } getSmiles(isExtended = false): Promise { diff --git a/packages/ketcher-core/src/domain/services/struct/structService.types.ts b/packages/ketcher-core/src/domain/services/struct/structService.types.ts index 57b21ef5a2..fd081782c7 100644 --- a/packages/ketcher-core/src/domain/services/struct/structService.types.ts +++ b/packages/ketcher-core/src/domain/services/struct/structService.types.ts @@ -43,8 +43,21 @@ export interface WithSelection { selected?: Array } +export type CheckTypes = + | 'radicals' + | 'pseudoatoms' + | 'stereo' + | 'query' + | 'overlapping_atoms' + | 'overlapping_bonds' + | 'rgroups' + | 'chiral' + | '3d' + | 'chiral_flag' + | 'valence' + export interface CheckData extends WithStruct { - types: Array + types: Array } export interface CheckResult { @@ -78,16 +91,23 @@ export interface CalculateCipData extends WithStruct, WithOutputFormat {} export interface CalculateCipResult extends WithStruct, WithFormat {} +export type CalculateProps = + | 'molecular-weight' + | 'most-abundant-mass' + | 'monoisotopic-mass' + | 'gross' + | 'mass-composition' + export interface CalculateData extends WithStruct, WithSelection { - properties: Array + properties: Array } -export interface CalculateResult { - [key: string]: string | number | boolean -} +export type CalculateResult = Record + +export type AutomapMode = 'discard' | 'keep' | 'alter' | 'clear' export interface AutomapData extends WithStruct, WithOutputFormat { - mode: string + mode: AutomapMode } export interface AutomapResult extends WithStruct, WithFormat {} diff --git a/packages/ketcher-standalone/src/infrastructure/services/struct/indigoWorker.types.ts b/packages/ketcher-standalone/src/infrastructure/services/struct/indigoWorker.types.ts index 586f773548..7c473d79f3 100644 --- a/packages/ketcher-standalone/src/infrastructure/services/struct/indigoWorker.types.ts +++ b/packages/ketcher-standalone/src/infrastructure/services/struct/indigoWorker.types.ts @@ -100,6 +100,14 @@ export interface CalculateCipCommandData WithStruct, WithFormat {} +export type CalculateProps = + | 'molecular-weight' + | 'most-abundant-mass' + | 'monoisotopic-mass' + | 'gross' + | 'gross-formula' + | 'mass-composition' + export interface CalculateCommandData extends CommandData, WithStruct, diff --git a/packages/ketcher-standalone/src/infrastructure/services/struct/standaloneStructService.ts b/packages/ketcher-standalone/src/infrastructure/services/struct/standaloneStructService.ts index 304e597378..2a3fdf9e4f 100644 --- a/packages/ketcher-standalone/src/infrastructure/services/struct/standaloneStructService.ts +++ b/packages/ketcher-standalone/src/infrastructure/services/struct/standaloneStructService.ts @@ -19,6 +19,7 @@ import { AutomapCommandData, CalculateCipCommandData, CalculateCommandData, + CalculateProps, CheckCommandData, CleanCommandData, Command, @@ -116,8 +117,8 @@ function convertMimeTypeToOutputFormat( return format } -function mapCalculatedPropertyName(property: string) { - let mappedProperty: string | undefined +function mapCalculatedPropertyName(property: CalculateProps) { + let mappedProperty: CalculateProps | undefined switch (property) { case 'gross-formula': { mappedProperty = 'gross' @@ -544,18 +545,20 @@ class IndigoService implements StructService { worker.terminate() const msg: OutputMessage = e.data if (!msg.hasError) { - const calculatedProperties = JSON.parse(msg.payload!) as KeyValuePair + const calculatedProperties: CalculateResult = JSON.parse(msg.payload!) const result: CalculateResult = Object.entries( calculatedProperties ).reduce((acc, curr) => { const [key, value] = curr - const mappedPropertyName = mapCalculatedPropertyName(key) + const mappedPropertyName = mapCalculatedPropertyName( + key as CalculateProps + ) if (properties.includes(mappedPropertyName)) { acc[mappedPropertyName] = value } return acc - }, {}) + }, {} as CalculateResult) resolve(result) } else { reject(msg.error)