From c7fd998dfffa5e9f57b7f7b624c7e39eba1cb598 Mon Sep 17 00:00:00 2001 From: Anton Rau Date: Mon, 7 Jun 2021 18:22:19 +0200 Subject: [PATCH] - FIX: bug introduced in Vue 2.6.13 (https://github.com/vuejs/vue/issues/12102). - WIP: `steinbock` data import. --- CHANGELOG.md | 9 + backend/histocat/worker/io/dataset_v2.py | 9 - backend/histocat/worker/io/steinbock.py | 206 ++++++++++++++++++ backend/histocat/worker/io/zip.py | 3 + frontend/package.json | 16 +- .../src/components/BrushableHistogram.vue | 2 +- frontend/src/components/ImageViewer.vue | 19 +- frontend/yarn.lock | 174 +++++++-------- 8 files changed, 326 insertions(+), 112 deletions(-) create mode 100644 backend/histocat/worker/io/steinbock.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 52398bf0..a66de5ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.1.2] - 2021-06-07 + +- FIX: bug introduced in Vue 2.6.13 (https://github.com/vuejs/vue/issues/12102). +- WIP: `steinbock` data import. + +## [2.1.1] - 2021-06-02 + +- Per-channel Min-Max / z-Score normalization as a segmentation pre-processing step. + ## [2.1.0] - 2021-05-27 Initial open-source release diff --git a/backend/histocat/worker/io/dataset_v2.py b/backend/histocat/worker/io/dataset_v2.py index f68c1fef..2e3f9130 100644 --- a/backend/histocat/worker/io/dataset_v2.py +++ b/backend/histocat/worker/io/dataset_v2.py @@ -123,15 +123,6 @@ def _import_cell_csv( obs["CentroidX"] = df["Location_Center_X"] obs["CentroidY"] = df["Location_Center_Y"] - # TODO: skip neighbors columns to keep things simple - # neighbors_cols = [col for col in df.columns if "Neighbors_" in col] - # for col in neighbors_cols: - # col_name = col.split("_")[1] - # if col_name: - # obs[col_name] = df[col] - # if col_name == "NumberOfNeighbors": - # obs[col_name] = obs[col_name].astype("int64") - var_names = [] x_df = pd.DataFrame() for key, value in channel_order.items(): diff --git a/backend/histocat/worker/io/steinbock.py b/backend/histocat/worker/io/steinbock.py new file mode 100644 index 00000000..c1dcae0a --- /dev/null +++ b/backend/histocat/worker/io/steinbock.py @@ -0,0 +1,206 @@ +import logging +import os +import re +from pathlib import Path +from typing import Dict, Union, List + +import anndata as ad +import pandas as pd +from sqlalchemy.orm import Session + +from histocat.core.acquisition import service as acquisition_service +from histocat.core.dataset import service as dataset_service +from histocat.core.dataset.dto import DatasetCreateDto, DatasetUpdateDto +from histocat.core.dataset.models import CELL_FILENAME, DatasetModel +from histocat.core.notifier import Message +from histocat.core.project import service as project_service +from histocat.core.redis_manager import UPDATES_CHANNEL_NAME, redis_manager +from histocat.core.slide import service as slide_service +from histocat.worker.io.utils import CELL_CSV_FILENAME, IMAGE_CSV_FILENAME, copy_file + +logger = logging.getLogger(__name__) + + +PANEL_CSV_FILE = "panel.csv" + + +def import_dataset(db: Session, input_folder: Path, project_id: int): + """Import dataset from the folder compatible with 'steinbock' format.""" + + project = project_service.get(db, id=project_id) + if not project: + logger.warning(f"Cannot import dataset: project [id: {project_id}] does not exist.") + return + + src_folder = None + for path in input_folder.rglob(PANEL_CSV_FILE): + src_folder = path.parent + break + + if src_folder is None: + return + + create_params = DatasetCreateDto(project_id=project_id, origin="steinbock", status="pending") + dataset = dataset_service.create(db, params=create_params) + + # Metadata dictionary + meta = {} + + dst_folder = Path(dataset.location) + os.makedirs(dst_folder, exist_ok=True) + + # Import panel data: { Metal Tag : channel number } + channel_order = _import_panel(os.path.join(src_folder, PANEL_CSV_FILE)) + + masks = {} + acquisition_id_mapping = {} + for mask_file in sorted(Path(src_folder / "cell_masks").rglob("*.tiff")): + result = _import_mask(db, mask_file, dataset) + if result is not None: + mask_meta, slide_name, acquisition_origin_id = result + acquisition_id = mask_meta.get("acquisition").get("id") + image_number = mask_meta.get("acquisition").get("origin_id") + masks[acquisition_id] = mask_meta + acquisition_id_mapping[f"{slide_name}_{image_number}"] = acquisition_id + meta["masks"] = masks + + regionprops_df = pd.DataFrame() + for regionprops_file in sorted(Path(src_folder / "cell_regionprops").rglob("*.csv")): + slide_name, acquisition_origin_id, df = _import_regionprops(regionprops_file, acquisition_id_mapping) + regionprops_df = regionprops_df.append(df) + regionprops_df.reset_index(inplace=True, drop=True) + regionprops_df["CellId"] = regionprops_df.index + + obs = pd.DataFrame(index=regionprops_df.index) + obs["CellId"] = regionprops_df.index + obs["AcquisitionId"] = regionprops_df["AcquisitionId"] + obs["ImageNumber"] = regionprops_df["ImageNumber"] + obs["ObjectNumber"] = regionprops_df["ObjectNumber"] + obs["CentroidX"] = regionprops_df["CentroidX"] + obs["CentroidY"] = regionprops_df["CentroidY"] + + print(obs) + + var_names = [] + x_df = pd.DataFrame() + for key, value in channel_order.items(): + # TODO: check intensity multiplier + x_df[key] = range(348) # df[f"Intensity_MeanIntensity_FullStackFiltered_c{value}"] * 2 ** 16 + var_names.append(key) + var = pd.DataFrame(index=var_names) + var["Channel"] = var.index + X_counts = x_df.to_numpy() + + adata = ad.AnnData(X_counts, obs=obs, var=var, dtype="float32") + dst_uri = dst_folder / CELL_FILENAME + adata.write_h5ad(dst_uri) + + # # Convert cell.csv to AnnData file format + # cell_df = _import_cell_csv(src_folder, dst_folder, image_number_to_acquisition_id, channel_order) + + acquisition_ids = sorted(list(masks.keys())) + channels = [c[0] for c in channel_order] + + update_params = DatasetUpdateDto( + name=f"Dataset {dataset.id}", status="ready", acquisition_ids=acquisition_ids, channels=channels, meta=meta + ) + dataset = dataset_service.update(db, item=dataset, params=update_params) + redis_manager.publish(UPDATES_CHANNEL_NAME, Message(project_id, "dataset_imported")) + + +def _import_panel(path: str): + panel_df = pd.read_csv(path) + # Map Metal Tag to its order number + channel_order = dict( + [(metal_name, int(index)) for metal_name, index in zip(panel_df.channel, panel_df.index)] + ) + return channel_order + + +def _import_mask(db: Session, filepath: Path, dataset: DatasetModel): + p = re.compile("(?P.*)_(?P[0-9]+).tiff") + slide_name, acquisition_origin_id = p.findall(filepath.name)[0] + + slide = slide_service.get_by_name(db, project_id=dataset.project_id, name=slide_name) + if slide is None: + return None + acquisition = acquisition_service.get_by_origin_id(db, slide_id=slide.id, origin_id=acquisition_origin_id) + + location = copy_file(str(filepath), dataset.location) + + meta = { + "location": location, + "slide": {"id": slide.id, "origin_id": slide.origin_id}, + "acquisition": {"id": acquisition.id, "origin_id": acquisition.origin_id}, + } + return meta, slide_name, acquisition_origin_id + + +def _import_regionprops(filepath: Path, acquisition_id_mapping: Dict[str, int]): + p = re.compile("(?P.*)_(?P[0-9]+).csv") + slide_name, acquisition_origin_id = p.findall(filepath.name)[0] + + df = pd.read_csv(filepath) + df.rename(columns={"Object": "ObjectNumber", "centroid-0": "CentroidY", "centroid-1": "CentroidX"}, inplace=True) + df["ImageNumber"] = acquisition_origin_id + df["AcquisitionId"] = acquisition_id_mapping.get(f"{slide_name}_{acquisition_origin_id}") + return slide_name, acquisition_origin_id, df[["ObjectNumber", "ImageNumber", "AcquisitionId", "CentroidX", "CentroidY"]] + + +def _import_image_csv(src_folder: Path): + src_uri = src_folder / IMAGE_CSV_FILENAME + + if not src_uri.exists(): + return None + + df = pd.read_csv(src_uri) + return df + + +def _import_cell_csv( + src_folder: Path, + dst_folder: Path, + image_number_to_acquisition_id: Dict[int, int], + channel_order: Dict[str, int], +): + src_uri = src_folder / CELL_CSV_FILENAME + + if not src_uri.exists(): + return None + + df = pd.read_csv(src_uri) + df.index = df.index.astype(str, copy=False) + + obs = pd.DataFrame(index=df.index) + obs["CellId"] = df.index + obs["AcquisitionId"] = df["ImageNumber"] + obs["AcquisitionId"].replace(image_number_to_acquisition_id, inplace=True) + obs["ImageNumber"] = df["ImageNumber"] + obs["ObjectNumber"] = df["ObjectNumber"] + obs["CentroidX"] = df["Location_Center_X"] + obs["CentroidY"] = df["Location_Center_Y"] + + # TODO: skip neighbors columns to keep things simple + # neighbors_cols = [col for col in df.columns if "Neighbors_" in col] + # for col in neighbors_cols: + # col_name = col.split("_")[1] + # if col_name: + # obs[col_name] = df[col] + # if col_name == "NumberOfNeighbors": + # obs[col_name] = obs[col_name].astype("int64") + + var_names = [] + x_df = pd.DataFrame() + for key, value in channel_order.items(): + # TODO: check intensity multiplier + x_df[key] = df[f"Intensity_MeanIntensity_FullStackFiltered_c{value}"] * 2 ** 16 + var_names.append(key) + var = pd.DataFrame(index=var_names) + var["Channel"] = var.index + X_counts = x_df.to_numpy() + + adata = ad.AnnData(X_counts, obs=obs, var=var, dtype="float32") + dst_uri = dst_folder / CELL_FILENAME + adata.write_h5ad(dst_uri) + + return df diff --git a/backend/histocat/worker/io/zip.py b/backend/histocat/worker/io/zip.py index 7f61f9e0..4ac4b7d4 100644 --- a/backend/histocat/worker/io/zip.py +++ b/backend/histocat/worker/io/zip.py @@ -12,6 +12,7 @@ dataset_masks, dataset_v1, dataset_v2, + steinbock, imcfolder, imcfolder_v1, mcd, @@ -56,6 +57,8 @@ def import_dataset_zip(db: Session, uri: str, project_id: int): with zipfile.ZipFile(path, "r") as zip: zip.extractall(output_dir) + # steinbock.import_dataset(db, output_dir, project_id) + for cell_csv_filename in utils.locate(output_dir, CELL_CSV_FILENAME): src_folder = Path(cell_csv_filename).parent is_v2 = os.path.exists(os.path.join(src_folder, "var_cell.csv")) diff --git a/frontend/package.json b/frontend/package.json index c4f1c50e..3e2dd709 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "2021.06.02", + "version": "2021.06.07", "private": true, "description": "histoCAT for Web", "author": { @@ -27,7 +27,7 @@ "test:unit": "vue-cli-service test:unit" }, "dependencies": { - "core-js": "3.13.1", + "core-js": "3.14.0", "d3": "6.7.0", "file-saver": "2.0.5", "golden-layout": "2.2.1", @@ -38,14 +38,14 @@ "register-service-worker": "1.7.2", "regl-scatterplot": "0.19.0", "uuid": "8.3.2", - "vue": "2.6.13", + "vue": "2.6.14", "vue-class-component": "7.2.6", "vue-property-decorator": "9.1.2", "vue-router": "3.5.1", "vuetify": "2.5.3", "vuex": "3.6.2", "vuex-persist": "3.1.3", - "vuex-smart-module": "0.5.0" + "vuex-smart-module": "0.6.0" }, "devDependencies": { "@mdi/font": "5.9.55", @@ -67,19 +67,19 @@ "@vue/eslint-config-typescript": "7.0.0", "@vue/test-utils": "1.2.0", "deepmerge": "4.2.2", - "eslint": "7.27.0", + "eslint": "7.28.0", "eslint-plugin-prettier": "3.4.0", "eslint-plugin-vue": "7.10.0", "fibers": "5.0.0", "jest-serializer-vue": "2.0.2", - "prettier": "2.3.0", + "prettier": "2.3.1", "sass": "1.32.13", - "sass-loader": "10.1.1", + "sass-loader": "10.2.0", "typescript": "4.3.2", "vue-cli-plugin-vuetify": "2.4.1", "vue-cli-plugin-webpack-bundle-analyzer": "4.0.0", "vue-masonry-css": "1.0.3", - "vue-template-compiler": "2.6.13", + "vue-template-compiler": "2.6.14", "vuetify-loader": "1.7.2" } } diff --git a/frontend/src/components/BrushableHistogram.vue b/frontend/src/components/BrushableHistogram.vue index b6ce7d2e..41448624 100644 --- a/frontend/src/components/BrushableHistogram.vue +++ b/frontend/src/components/BrushableHistogram.vue @@ -228,7 +228,7 @@ export default class BrushableHistogram extends Vue { const stats = await this.projectsContext.actions.getChannelStats({ acquisitionId: this.activeAcquisitionId, channelName: this.channel.name, - }); + }) as IChannelStats; const histogram = this.calcHistogram(stats); this.renderHistogram(histogram); diff --git a/frontend/src/components/ImageViewer.vue b/frontend/src/components/ImageViewer.vue index c2956c78..fc647a5e 100644 --- a/frontend/src/components/ImageViewer.vue +++ b/frontend/src/components/ImageViewer.vue @@ -114,25 +114,29 @@ export default class ImageViewer extends Vue { @Watch("channelStackImage") async onChannelStackImageChanged(value) { if (value && this.activeAcquisitionId) { + const prevBackgroundImage = this.scatterplot.get("backgroundImage"); + if (prevBackgroundImage) { + this.scatterplot.set({ + backgroundImage: null, + }); + prevBackgroundImage.destroy(); + } + + const regl = this.scatterplot.get("regl"); const img = new Image(); img.crossOrigin = ""; img.onload = () => { - const prevBackgroundImage = this.scatterplot.get("backgroundImage"); - const regl = this.scatterplot.get("regl"); this.scatterplot.set({ backgroundImage: regl.texture(img), aspectRatio: this.activeAcquisition!.max_x / this.activeAcquisition!.max_y, }); - if (prevBackgroundImage) { - prevBackgroundImage.destroy(); - } if (this.applyMask && this.cellsByAcquisition && this.cellsByAcquisition.has(this.activeAcquisitionId!)) { this.points = this.cellsByAcquisition.get(this.activeAcquisitionId!)!; const x = transformToWebGl(this.points, this.activeAcquisition!.max_x, this.activeAcquisition!.max_y); this.scatterplot.draw(x); } else { - this.scatterplot.draw([]); + this.scatterplot.clear(); } // this.scatterplot.deselect({ preventEvent: true }); }; @@ -237,7 +241,8 @@ export default class ImageViewer extends Vue { const canvas = this.$refs.canvasWebGl as Element; this.scatterplot = createScatterplot({ - syncEvents: true, + syncEvents: false, + backgroundImage: null, canvas: canvas, opacity: 0.5, pointSize: 2, diff --git a/frontend/yarn.lock b/frontend/yarn.lock index c523398b..1abb0ba8 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -918,15 +918,15 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@eslint/eslintrc@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.1.tgz#442763b88cecbe3ee0ec7ca6d6dd6168550cbf14" - integrity sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ== +"@eslint/eslintrc@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.2.tgz#f63d0ef06f5c0c57d76c4ab5f63d3835c51b0179" + integrity sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg== dependencies: ajv "^6.12.4" debug "^4.1.1" espree "^7.3.0" - globals "^12.1.0" + globals "^13.9.0" ignore "^4.0.6" import-fresh "^3.2.1" js-yaml "^3.13.1" @@ -1140,18 +1140,18 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@nodelib/fs.scandir@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" - integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: - "@nodelib/fs.stat" "2.0.4" + "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" - integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.stat@^1.1.2": version "1.1.3" @@ -1159,11 +1159,11 @@ integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== "@nodelib/fs.walk@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" - integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== + version "1.2.7" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz#94c23db18ee4653e129abd26fb06f870ac9e1ee2" + integrity sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA== dependencies: - "@nodelib/fs.scandir" "2.1.4" + "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" "@polka/url@^1.0.0-next.15": @@ -1561,9 +1561,9 @@ integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg== "@types/node@*": - version "15.9.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.9.0.tgz#0b7f6c33ca5618fe329a9d832b478b4964d325a8" - integrity sha512-AR1Vq1Ei1GaA5FjKL5PBqblTZsL5M+monvGSZwe6sSIdGiuu7Xr/pNwWJY+0ZQuN8AapD/XMB5IzBAyYRFbocA== + version "15.12.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.1.tgz#9b60797dee1895383a725f828a869c86c6caa5c2" + integrity sha512-zyxJM8I1c9q5sRMtVF+zdd13Jt6RU4r4qfhTd7lQubyThvLfx6yYekWSQjGCGV2Tkecgxnlpl/DNlb6Hg+dmEw== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -2343,9 +2343,9 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv uri-js "^4.2.2" ajv@^8.0.1: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.5.0.tgz#695528274bcb5afc865446aa275484049a18ae4b" - integrity sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ== + version "8.6.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.0.tgz#60cc45d9c46a477d80d92c48076d972c342e5720" + integrity sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -3239,9 +3239,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219: - version "1.0.30001233" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001233.tgz#b7cb4a377a4b12ed240d2fa5c792951a06e5f2c4" - integrity sha512-BmkbxLfStqiPA7IEzQpIk0UFZFf3A4E6fzjPJ6OR+bFC2L8ES9J8zGA/asoi47p8XDVkev+WJo2I2Nc8c/34Yg== + version "1.0.30001235" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001235.tgz#ad5ca75bc5a1f7b12df79ad806d715a43a5ac4ed" + integrity sha512-zWEwIVqnzPkSAXOUlQnPW2oKoYb2aLQ4Q5ejdjBcnH63rfypaW34CxaeBn1VMya2XaEU3P/R2qHpWyj+l0BT1A== capture-exit@^2.0.0: version "2.0.0" @@ -3613,9 +3613,9 @@ condense-newlines@^0.2.1: kind-of "^3.0.2" config-chain@^1.1.12: - version "1.1.12" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" - integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== + version "1.1.13" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== dependencies: ini "^1.3.4" proto-list "~1.2.1" @@ -3707,17 +3707,17 @@ copy-webpack-plugin@^5.1.1: webpack-log "^2.0.0" core-js-compat@^3.6.5, core-js-compat@^3.9.0, core-js-compat@^3.9.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.13.1.tgz#05444caa8f153be0c67db03cf8adb8ec0964e58e" - integrity sha512-mdrcxc0WznfRd8ZicEZh1qVeJ2mu6bwQFh8YVUK48friy/FOwFV5EJj9/dlh+nMQ74YusdVfBFDuomKgUspxWQ== + version "3.14.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.14.0.tgz#b574dabf29184681d5b16357bd33d104df3d29a5" + integrity sha512-R4NS2eupxtiJU+VwgkF9WTpnSfZW4pogwKHd8bclWU2sp93Pr5S1uYJI84cMOubJRou7bcfL0vmwtLslWN5p3A== dependencies: browserslist "^4.16.6" semver "7.0.0" -core-js@3.13.1, core-js@^3.6.5: - version "3.13.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.13.1.tgz#30303fabd53638892062d8b4e802cac7599e9fb7" - integrity sha512-JqveUc4igkqwStL2RTRn/EPFGBOfEZHxJl/8ej1mXJR75V3go2mFF4bmUYkEIT1rveHKnkUlcJX/c+f1TyIovQ== +core-js@3.14.0, core-js@^3.6.5: + version "3.14.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.14.0.tgz#62322b98c71cc2018b027971a69419e2425c2a6c" + integrity sha512-3s+ed8er9ahK+zJpp9ZtuVcDoFzHNiZsPbNAAE4KXgrRHbjSqqNN6xGSXq6bq7TZIbKj4NLrLb6bJ5i+vSVjHA== core-js@^2.4.0: version "2.6.12" @@ -4021,13 +4021,20 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -d3-array@2, d3-array@>=2.5, d3-array@^2.3.0: +d3-array@2, d3-array@^2.3.0: version "2.12.1" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81" integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ== dependencies: internmap "^1.0.0" +d3-array@>=2.5: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.0.1.tgz#ca45c263f5bb780ab5a34a6e1d3d5883fe4a8d14" + integrity sha512-l3Bh5o8RSoC3SBm5ix6ogaFW+J6rOUm42yOtZ2sQPCEvCqUMepeX7zgrlLLGIemxgOyo9s2CsWEidnLv5PwwRw== + dependencies: + internmap "1 - 2" + d3-axis@2: version "2.1.0" resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-2.1.0.tgz#978db534092711117d032fad5d733d206307f6a0" @@ -4657,9 +4664,9 @@ ejs@^2.6.1: integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== electron-to-chromium@^1.3.723: - version "1.3.744" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.744.tgz#34e0da7babb325e18b50d3a0214504b12045ca85" - integrity sha512-o/vep/PvSXg+7buwCbVJXHY3zbjYVmFPwnMMnchESXgAzrfcasvbX/hQZHCFGG7YdZgdtwt1KTMyK9CyBxPbLA== + version "1.3.749" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.749.tgz#0ecebc529ceb49dd2a7c838ae425236644c3439a" + integrity sha512-F+v2zxZgw/fMwPz/VUGIggG4ZndDsYy0vlpthi3tjmDZlcfbhN5mYW0evXUsBr2sUtuDANFtle410A9u/sd/4A== elliptic@^6.5.3: version "6.5.4" @@ -4896,13 +4903,13 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint@7.27.0: - version "7.27.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.27.0.tgz#665a1506d8f95655c9274d84bd78f7166b07e9c7" - integrity sha512-JZuR6La2ZF0UD384lcbnd0Cgg6QJjiCwhMD6eU4h/VGPcVGwawNNzKU41tgokGXnfjOOyI6QIffthhJTPzzuRA== +eslint@7.28.0: + version "7.28.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.28.0.tgz#435aa17a0b82c13bb2be9d51408b617e49c1e820" + integrity sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g== dependencies: "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.1" + "@eslint/eslintrc" "^0.4.2" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -4919,7 +4926,7 @@ eslint@7.27.0: fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" + glob-parent "^5.1.2" globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" @@ -5668,7 +5675,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: +glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -5697,14 +5704,7 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== - dependencies: - type-fest "^0.8.1" - -globals@^13.6.0: +globals@^13.6.0, globals@^13.9.0: version "13.9.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb" integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA== @@ -5916,9 +5916,9 @@ hex-color-regex@^1.1.0: integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== highlight.js@^10.7.1: - version "10.7.2" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360" - integrity sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg== + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== hmac-drbg@^1.0.1: version "1.0.1" @@ -6269,6 +6269,11 @@ internal-ip@^4.3.0: default-gateway "^4.2.0" ipaddr.js "^1.9.0" +"internmap@1 - 2": + version "2.0.1" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.1.tgz#33d0fa016185397549fb1a14ea3dbe5a2949d1cd" + integrity sha512-Ujwccrj9FkGqjbY3iVoxD1VV+KdZZeENx0rphrtzmRXbFvkFO88L80BL/zeSIguX/7T+y8k04xqtgWgS5vxwxw== + internmap@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" @@ -8062,9 +8067,9 @@ node-notifier@^5.4.2: which "^1.3.0" node-releases@^1.1.71: - version "1.1.72" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" - integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== + version "1.1.73" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" + integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg== nopt@^5.0.0: version "5.0.0" @@ -9038,10 +9043,10 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18" - integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w== +prettier@2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.1.tgz#76903c3f8c4449bc9ac597acefa24dc5ad4cbea6" + integrity sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA== prettier@^1.18.2: version "1.19.1" @@ -9727,10 +9732,10 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" -sass-loader@10.1.1: - version "10.1.1" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.1.1.tgz#4ddd5a3d7638e7949065dd6e9c7c04037f7e663d" - integrity sha512-W6gVDXAd5hR/WHsPicvZdjAWHBcEJ44UahgxcIE196fW2ong0ZHMPO1kZuI5q0VlvMQZh32gpv69PLWQm70qrw== +sass-loader@10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.2.0.tgz#3d64c1590f911013b3fa48a0b22a83d5e1494716" + integrity sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw== dependencies: klona "^2.0.4" loader-utils "^2.0.0" @@ -10830,11 +10835,6 @@ type-fest@^0.6.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -11211,10 +11211,10 @@ vue-style-loader@^4.1.0, vue-style-loader@^4.1.2: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue-template-compiler@2.6.13: - version "2.6.13" - resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.13.tgz#a735b8974e013ce829e7f77e08e4ee5aecbd3005" - integrity sha512-latKAqpUjCkovB8XppW5gnZbSdYQzkf8pavsMBZYZrQcG6lAnj0EH4Ty7jMwAwFw5Cf4mybKBHlp1UTjnLPOWw== +vue-template-compiler@2.6.14: + version "2.6.14" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz#a2f0e7d985670d42c9c9ee0d044fed7690f4f763" + integrity sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g== dependencies: de-indent "^1.0.2" he "^1.1.0" @@ -11224,10 +11224,10 @@ vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0: resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825" integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== -vue@2.6.13: - version "2.6.13" - resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.13.tgz#94b2c1b31fddf1dfcc34f28ec848ba8f01ea4c5b" - integrity sha512-O+pAdJkce1ooYS1XyoQtpBQr9An+Oys3w39rkqxukVO3ZD1ilYJkWBGoRuadiQEm2LLJnCL2utV4TMSf52ubjw== +vue@2.6.14: + version "2.6.14" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235" + integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ== vuetify-loader@1.7.2: version "1.7.2" @@ -11251,10 +11251,10 @@ vuex-persist@3.1.3: deepmerge "^4.2.2" flatted "^3.0.5" -vuex-smart-module@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/vuex-smart-module/-/vuex-smart-module-0.5.0.tgz#b7973071a517a075a1bf8f7bb1b508eb5ae3b18b" - integrity sha512-g/dXwtL4oG0fr4ekxVPmx2VLeI2M+5om6BwReS4TMkawa0qROI4DZMuqvmkTg8SCAR9zbLoTYV0GgzVSfyfERw== +vuex-smart-module@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/vuex-smart-module/-/vuex-smart-module-0.6.0.tgz#e6d4f0b35e4869215504df9d1702af6387f6d931" + integrity sha512-hL63i4O2m3viMsV0vY7wugE+mfy6EPz0HK+wzGqBV2rRcaqEROas3mlBH5XBiuAclNtNEWsr6VPWEfujIzvVMg== vuex@3.6.2: version "3.6.2"