From cbf3ba240a30792e0054de0d121eb9ec9759e7ca Mon Sep 17 00:00:00 2001 From: Emanuele Bezzi Date: Thu, 9 Dec 2021 14:12:16 -0500 Subject: [PATCH] Put scaling back in the backend --- client/src/components/graph/graph.js | 36 +++++---------- server/common/rest.py | 2 +- server/data_common/data_adaptor.py | 66 ++++++++++++++++++---------- 3 files changed, 57 insertions(+), 47 deletions(-) diff --git a/client/src/components/graph/graph.js b/client/src/components/graph/graph.js index 75199f0f..009b0bfe 100644 --- a/client/src/components/graph/graph.js +++ b/client/src/components/graph/graph.js @@ -112,24 +112,20 @@ class Graph extends React.Component { return !shallowEqual(props.watchProps, prevProps.watchProps); } - computePointPositions = memoize( - (X, Y, modelTF, spatialMetadata, imageUnderlay) => { - /* + computePointPositions = memoize((X, Y, modelTF) => { + /* compute the model coordinate for each point */ - console.log({ X }, { Y }); - const positions = new Float32Array(2 * X.length); - for (let i = 0, len = X.length; i < len; i += 1) { - const p = imageUnderlay?.isActive - ? this.rescalePointForSpatial(X[i], Y[i], spatialMetadata) - : vec2.fromValues(X[i], Y[i]); - vec2.transformMat3(p, p, modelTF); - positions[2 * i] = p[0]; - positions[2 * i + 1] = p[1]; - } - return positions; + console.log({ X }, { Y }); + const positions = new Float32Array(2 * X.length); + for (let i = 0, len = X.length; i < len; i += 1) { + const p = vec2.fromValues(X[i], Y[i]); + vec2.transformMat3(p, p, modelTF); + positions[2 * i] = p[0]; + positions[2 * i + 1] = p[1]; } - ); + return positions; + }); computePointColors = memoize((rgb) => { /* @@ -585,13 +581,7 @@ class Graph extends React.Component { const { currentDimNames } = layoutChoice; const X = layoutDf.col(currentDimNames[0]).asArray(); const Y = layoutDf.col(currentDimNames[1]).asArray(); - const positions = this.computePointPositions( - X, - Y, - modelTF, - spatial.data, - imageUnderlay - ); + const positions = this.computePointPositions(X, Y, modelTF); const colorTable = this.updateColorTable(colorsProp, colorDf); const colors = this.computePointColors(colorTable.rgb); @@ -809,8 +799,6 @@ class Graph extends React.Component { const { pointBuffer, colorBuffer, flagBuffer } = this.state; let needToRenderCanvas = false; - console.log("updateReglAndRender"); - if (height !== prevAsyncProps?.height || width !== prevAsyncProps?.width) { needToRenderCanvas = true; } diff --git a/server/common/rest.py b/server/common/rest.py index 76b8d769..c78c038d 100644 --- a/server/common/rest.py +++ b/server/common/rest.py @@ -294,7 +294,7 @@ def layout_obs_get(request, data_adaptor): try: return make_response( - data_adaptor.layout_to_fbs_matrix(fields), HTTPStatus.OK, {"Content-Type": "application/octet-stream"} + data_adaptor.layout_to_fbs_matrix(fields, data_adaptor.get_spatial()), HTTPStatus.OK, {"Content-Type": "application/octet-stream"} ) except (KeyError, DatasetAccessError) as e: return abort_and_log(HTTPStatus.BAD_REQUEST, str(e), include_exc_info=True) diff --git a/server/data_common/data_adaptor.py b/server/data_common/data_adaptor.py index fa3113dd..8cca3b81 100644 --- a/server/data_common/data_adaptor.py +++ b/server/data_common/data_adaptor.py @@ -340,41 +340,65 @@ class DataAdaptor(metaclass=ABCMeta): pass @staticmethod - def normalize_embedding(embedding): + def normalize_embedding(embedding, spatial = None): """Normalize embedding layout to meet client assumptions. - Embedding is an ndarray, shape (n_obs, n)., where n is normally 2 + Embedding is an ndarray, shape (n_obs, n)., where n is normally 2. + Note: if spatial data is available, the normalization will be done + according to the size of the underlying image """ + if spatial is not None: + + # TODO: sync with the code in spatial_data_get + resolution = "hires" + + if len(list(spatial)) == 0: + raise Exception("uns does not have spatial information") + + library_id = list(spatial)[0] + + if "images" not in spatial[library_id]: + raise Exception("spatial information does not contain images") + + if resolution not in spatial[library_id]["images"]: + raise Exception(f"spatial information does not contain requested resolution '{resolution}'") + + scaleref = spatial[library_id]["scalefactors"][f"tissue_{resolution}_scalef"] + (h, w, _) = spatial[library_id]["images"][resolution].shape + + A = embedding * scaleref + A = np.column_stack([A[:, 0] / w, A[:, 1] / h]) + normalized_layout = A.astype(dtype=np.float32) + + else: + # scale isotropically - try: - min = np.nanmin(embedding, axis=0) - max = np.nanmax(embedding, axis=0) - except RuntimeError: - # indicates entire array was NaN, which should propagate - min = np.NaN - max = np.NaN + try: + min = np.nanmin(embedding, axis=0) + max = np.nanmax(embedding, axis=0) + except RuntimeError: + # indicates entire array was NaN, which should propagate + min = np.NaN + max = np.NaN - scale = np.amax(max - min) - normalized_layout = (embedding - min) / scale + scale = np.amax(max - min) + normalized_layout = (embedding - min) / scale - # translate to center on both axis - translate = 0.5 - ((max - min) / scale / 2) - normalized_layout = normalized_layout + translate + # translate to center on both axis + translate = 0.5 - ((max - min) / scale / 2) + normalized_layout = normalized_layout + translate - print(f"scale {scale}, translate {translate}") + # print(f"scale {scale}, translate {translate}") # if True: # if visium # self.data.uns["spatial"] # adata.uns["spatial"]['V1_Adult_Mouse_Brain']["scalefactors"]["tissue_hires_scalef"] - # A = embedding * 0.17011142 - # A = np.column_stack([A[:, 0] / 1921, A[:, 1] / 2000]) - # normalized_layout = A.astype(dtype=np.float32) return normalized_layout - def layout_to_fbs_matrix(self, fields): + def layout_to_fbs_matrix(self, fields, spatial = None): """ return specified embeddings as a flatbuffer, using the cellxgene matrix fbs encoding. @@ -390,7 +414,7 @@ class DataAdaptor(metaclass=ABCMeta): with ServerTiming.time("layout.query"): for ename in embeddings: embedding = self.get_embedding_array(ename, 2) - normalized_layout = DataAdaptor.normalize_embedding(embedding) + normalized_layout = DataAdaptor.normalize_embedding(embedding, ename == "spatial" and spatial) layout_data.append(pd.DataFrame(normalized_layout, columns=[f"{ename}_0", f"{ename}_1"])) with ServerTiming.time("layout.encode"): @@ -398,8 +422,6 @@ class DataAdaptor(metaclass=ABCMeta): df = pd.concat(layout_data, axis=1, copy=False) else: df = pd.DataFrame() - # print("##########DF") - # print(df) fbs = encode_matrix_fbs(df, col_idx=df.columns, row_idx=None) return fbs