Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raman re-up #776

Closed
wants to merge 11 commits into from
10 changes: 5 additions & 5 deletions pydatalab/example_data/raman/labspec_raman_example.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Acq. time (s)= 300
#Accumulations= 12
#Range (cm-�)= 200...800
#Range (cm-�)= 200...800
#Windows= 1
#Auto scanning= Off
#Autofocus= Off
Expand All @@ -15,7 +15,7 @@
#Inst. Process= Off
#Detector Gain= High Sensitivity
#Detector ADC= 45 kHz
#Detector temperature (�C)= -60.1
#Detector temperature (�C)= -60.1
#Instrument= LabRAM HR Evol
#Detector= Syncerity OE
#Objective= x10_VIS
Expand All @@ -27,9 +27,9 @@
#Collection= Microscope
#StageXY= Marzhauser
#StageZ= Marzhauser
#X (�m)= 0
#Y (�m)= 0
#Z (�m)= 7.9
#X (�m)= 0
#Y (�m)= 0
#Z (�m)= 7.9
#Full time(hh:mm)= 1:00
#Project= A
#Sample= B
Expand Down
2 changes: 1 addition & 1 deletion pydatalab/pydatalab/apps/raman/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def generate_raman_plot(self):
self.accepted_file_extensions,
ext,
)
pattern_dfs, y_options, _ = self.load(file_info["location"])
pattern_dfs, metadata, y_options = self.load(file_info["location"])
pattern_dfs = [pattern_dfs]

if pattern_dfs:
Expand Down
3 changes: 3 additions & 0 deletions pydatalab/pydatalab/apps/raman_map/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .blocks import RamanMapBlock

__all__ = ("RamanMapBlock",)
189 changes: 189 additions & 0 deletions pydatalab/pydatalab/apps/raman_map/blocks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import os
from pathlib import Path

import bokeh
import numpy as np
import PIL
from bokeh.layouts import column
from bokeh.models import ColorBar, ColumnDataSource, LinearColorMapper
from pybaselines import Baseline
from rsciio.renishaw import file_reader

from pydatalab.blocks.base import DataBlock
from pydatalab.file_utils import get_file_info_by_id


class RamanMapBlock(DataBlock):
blocktype = "raman_map"
description = "Raman spectroscopy map"
accepted_file_extensions = ".wdf"

@property
def plot_functions(self):
return (self.generate_raman_map_plot,)

@classmethod
def get_map_data(self, location: Path | str):
"""Read the .wdf file with RosettaSciIO and extract relevant
data.

Parameters:
location: The location of the file to read.

Returns:
A dataframe with the appropriate columns,


"""

raman_data = file_reader(location)

if len(raman_data[0]["axes"]) == 3:
pass
elif len(raman_data[0]["axes"]) == 1:
raise RuntimeError("This block is for 2D Raman data, not 1D")
else:
raise RuntimeError("Data is not compatible 1D or 2D Raman data.")

for dictionary in raman_data[0]["axes"]:
if dictionary["name"] == "Raman Shift":
raman_shift = []
for i in range(int(dictionary["size"])):
raman_shift.append(float(dictionary["offset"]) + float(dictionary["scale"]) * i)
return np.array(raman_shift), raman_data[0]["data"], raman_data[0]["metadata"]

def plot_raman_map(self, location: str | Path):
data = file_reader(location)
raman_shift, intensity_data, metadata = self.get_map_data(location)
x_coordinates = []
# gets the size, point spacing and original offset of x-axis
# check for origin
size_x = data[0]["original_metadata"]["WMAP_0"]["size_xyz"][0]
scale_x = data[0]["original_metadata"]["WMAP_0"]["scale_xyz"][0]
offset_x = data[0]["original_metadata"]["WMAP_0"]["offset_xyz"][0]
# generates x-coordinates
for i in range(size_x):
x_coordinates.append(i * scale_x + offset_x)
y_coordinates = []
# gets the size, point spacing and original offset of x-axis
size_y = data[0]["original_metadata"]["WMAP_0"]["size_xyz"][1]
scale_y = data[0]["original_metadata"]["WMAP_0"]["scale_xyz"][1]
offset_y = data[0]["original_metadata"]["WMAP_0"]["offset_xyz"][1]
# generates y-coordinates
for i in range(size_y):
y_coordinates.append(i * scale_y + offset_y)

coordinate_pairs = []
for y in y_coordinates:
for x in x_coordinates:
coordinate_pairs.append((x, y))

# extracts image and gets relevant data
image_data = data[0]["original_metadata"]["WHTL_0"]["image"]
image = PIL.Image.open(image_data)
origin = data[0]["original_metadata"]["WHTL_0"]["FocalPlaneXYOrigins"]
origin = [float(origin[0]), float(origin[1])]
x_span = float(data[0]["original_metadata"]["WHTL_0"]["FocalPlaneXResolution"])
y_span = float(data[0]["original_metadata"]["WHTL_0"]["FocalPlaneYResolution"])
print(x_span, y_span, origin)
# converts image to vector compatible with bokeh
image_array = np.array(image, dtype=np.uint8)
image_array = np.dstack((image_array, 255 * np.ones_like(image_array[:, :, 0])))
img_vector = image_array.view(dtype=np.uint32).reshape(
(image_array.shape[0], image_array.array[1])
)
# generates numbers for colours for points in linear gradient
col = [
i / (len(x_coordinates) * len(y_coordinates))
for i in range(len(x_coordinates) * len(y_coordinates))
]

# links x- and y-coordinates with colour numbers
source = ColumnDataSource(
data={
"x": [pair[0] for pair in coordinate_pairs],
"y": [pair[1] for pair in coordinate_pairs],
"col": col,
}
)
# gemerates colormap for coloured scatter poitns
exp_cmap = LinearColorMapper(palette="Turbo256", low=min(col), high=max(col))

# generates image figure and plots image
from pathlib import Path

p = bokeh.plotting.figure(
width=image_array.shape[1],
height=image_array.shape[0],
x_range=(origin[0], origin[0] + x_span),
y_range=(origin[1] + y_span, origin[1]),
)
p.image_rgba(image=[img_vector], x=origin[0], y=origin[1], dw=x_span, dh=y_span)
# plot scatter points and colorbar
p.circle("x", "y", size=10, source=source, color={"field": "col", "transform": exp_cmap})
color_bar = ColorBar(
color_mapper=exp_cmap, label_standoff=12, border_line_color=None, location=(0, 0)
)
p.add_layout(color_bar, "right")
bokeh.plotting.save(p)
# return color numbers to use in spectra plotting and returns figure object

bokeh.plotting.output_file(Path(__file__).parent / "plot.html")
return col, p, metadata

def plot_raman_spectra(self, location: str | Path, col):
# generates plot and extracts raman spectra from .wdf file
p = bokeh.plotting.figure(
width=800,
height=400,
x_axis_label="Raman Shift (cm-1)",
y_axis_label="Intensity (a.u.)",
)
raman_shift, intensity_data, metadata = self.get_map_data(location)
intensity_list = []

# generates baseline to be subtracted from spectra
def generate_baseline(x_data, y_data):
baseline_fitter = Baseline(x_data=x_data)
baseline = baseline_fitter.mor(y_data, half_window=30)[0]
return baseline

# the bokeh ColumnDataSource works was easiest for me to work with this as a list so making list of spectra intensities
for i in range(intensity_data.shape[0]):
for j in range(intensity_data.shape[1]):
intensity_spectrum = intensity_data[i, j, :]
# want to make optional but will leave for now
baseline = generate_baseline(raman_shift, intensity_spectrum)
intensity_spectrum = intensity_spectrum - baseline
intensity_list.append(intensity_spectrum)

# generates colorbar
source = ColumnDataSource(
data={"x": [raman_shift] * len(intensity_list), "y": intensity_list, "col": col}
)
exp_cmap = LinearColorMapper(palette="Turbo256", low=min(col), high=max(col))
# plots spectra
p.multi_line(
"x", "y", line_width=0.5, source=source, color={"field": "col", "transform": exp_cmap}
)
return p

def generate_raman_map_plot(self):
file_info = None
pattern_dfs = None

if "file_id" not in self.data:
return None

else:
file_info = get_file_info_by_id(self.data["file_id"], update_if_live=True)
ext = os.path.splitext(file_info["location"].split("/")[-1])[-1].lower()
if ext not in self.accepted_file_extensions:
raise RuntimeError(
"RamanBlock.generate_raman_plot(): Unsupported file extension (must be one of %s), not %s",
self.accepted_file_extensions,
ext,
)
col, p1, metadata = self.plot_raman_map(file_info["location"])
p2 = self.plot_raman_spectra(file_info["location"], col=col)
self.data["bokeh_plot_data"] = bokeh.embed.json_item(column(p1, p2))
4 changes: 3 additions & 1 deletion pydatalab/pydatalab/blocks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pydatalab.apps.eis import EISBlock
from pydatalab.apps.nmr import NMRBlock
from pydatalab.apps.raman import RamanBlock
from pydatalab.apps.raman_map import RamanMapBlock
from pydatalab.apps.tga import MassSpecBlock
from pydatalab.apps.xrd import XRDBlock
from pydatalab.blocks.base import DataBlock
Expand All @@ -17,6 +18,7 @@
XRDBlock,
CycleBlock,
RamanBlock,
RamanMapBlock,
NMRBlock,
MassSpecBlock,
NotSupportedBlock,
Expand All @@ -35,7 +37,7 @@
"CycleBlock",
"NMRBlock",
"RamanBlock",
"MassSpecBlock",
"RamanMapBlock" "MassSpecBlock",
"BLOCK_TYPES",
"BLOCKS",
)
58 changes: 58 additions & 0 deletions webapp/src/components/datablocks/RamanMapBlock.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<template>
<!-- think about elegant two-way binding to DataBlockBase... or, just pass all the block data into
DataBlockBase as a prop, and save from within DataBlockBase -->
<DataBlockBase :item_id="item_id" :block_id="block_id">
<FileSelectDropdown
v-model="file_id"
:item_id="item_id"
:block_id="block_id"
:extensions="['.wdf']"
updateBlockOnChange
/>

<div class="row">
<div id="bokehPlotContainer" class="col-xl-9 col-lg-10 col-md-11 mx-auto">
<BokehPlot :bokehPlotData="bokehPlotData" />
</div>
</div>
</DataBlockBase>
</template>

<script>
import DataBlockBase from "@/components/datablocks/DataBlockBase";
import FileSelectDropdown from "@/components/FileSelectDropdown";
import BokehPlot from "@/components/BokehPlot";

import { createComputedSetterForBlockField } from "@/field_utils.js";
import { updateBlockFromServer } from "@/server_fetch_utils.js";

export default {
props: {
item_id: String,
block_id: String,
},
computed: {
bokehPlotData() {
return this.$store.state.all_item_data[this.item_id]["blocks_obj"][this.block_id]
.bokeh_plot_data;
},
file_id: createComputedSetterForBlockField("file_id"),
},
components: {
DataBlockBase,
FileSelectDropdown,
BokehPlot,
},
methods: {
updateBlock() {
updateBlockFromServer(
this.item_id,
this.block_id,
this.$store.state.all_item_data[this.item_id]["blocks_obj"][this.block_id],
);
},
},
};
</script>

<style scoped></style>
2 changes: 2 additions & 0 deletions webapp/src/resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import MediaBlock from "@/components/datablocks/MediaBlock";
import XRDBlock from "@/components/datablocks/XRDBlock";
import ChatBlock from "@/components/datablocks/ChatBlock";
import RamanBlock from "@/components/datablocks/RamanBlock";
import RamanMapBlock from "@/components/datablocks/RamanMapBlock";
import CycleBlock from "@/components/datablocks/CycleBlock";
import NMRBlock from "@/components/datablocks/NMRBlock";
import EISBlock from "@/components/datablocks/EISBlock";
Expand Down Expand Up @@ -42,6 +43,7 @@ export const blockTypes = {
media: { description: "Media", component: MediaBlock },
xrd: { description: "Powder XRD", component: XRDBlock },
raman: { description: "Raman", component: RamanBlock },
raman_map: { description: "Raman Map", component: RamanMapBlock },
cycle: { description: "Electrochemistry", component: CycleBlock },
eis: { description: "EIS", component: EISBlock },
nmr: { description: "NMR", component: NMRBlock },
Expand Down