diff --git a/src/nomad_ikz_omega_theta_xrd/schema_packages/omegascan.py b/src/nomad_ikz_omega_theta_xrd/schema_packages/omegascan.py index d4e9def..edf8f41 100644 --- a/src/nomad_ikz_omega_theta_xrd/schema_packages/omegascan.py +++ b/src/nomad_ikz_omega_theta_xrd/schema_packages/omegascan.py @@ -21,11 +21,17 @@ import matplotlib.pyplot as plt import numpy as np +import plotly.colors as pc import plotly.graph_objects as go from nomad.datamodel.data import ArchiveSection, EntryData -from nomad.datamodel.metainfo.basesections import Measurement, MeasurementResult +from nomad.datamodel.metainfo.basesections import ( + Instrument, + InstrumentReference, + Measurement, + MeasurementResult, +) from nomad.datamodel.metainfo.plot import PlotlyFigure, PlotSection -from nomad.metainfo import Datetime, MEnum, Package, Quantity, Section, SubSection +from nomad.metainfo import MEnum, Package, Quantity, Section, SubSection from nomad_ikz_omega_theta_xrd.schema_packages.omegathetaxrdreader import ( extract_data_and_metadata, @@ -33,6 +39,7 @@ extract_parameter_list, extract_scan_data, ) +from nomad_ikz_omega_theta_xrd.schema_packages.utils import create_archive if TYPE_CHECKING: from nomad.datamodel.datamodel import EntryArchive @@ -41,6 +48,40 @@ m_package = Package(name='Omega Theta XRD') +class OmegaThetaXRDInstrument(Instrument, EntryData, ArchiveSection): + """ + Class autogenerated from yaml schema. + """ + + m_def = Section() + instrument_name = Quantity( + type=str, + description='Name of the instrument used.', + default='Freiberger Omega Theta XRD', + ) + lab_id = Quantity( + type=str, + description='Identifier for the instrument.', + default='26-0019', + label='serial_number', + ) + + +class OmegaThetaXRDInstrumentReference(InstrumentReference): + """ + Class autogenerated from yaml schema. + """ + + m_def = Section() + reference = Quantity( + type=OmegaThetaXRDInstrument, + a_eln={ + 'component': 'ReferenceEditQuantity', + 'label': 'omega_theta_xrd_instrument', + }, + ) + + class ScanCurve(PlotSection, ArchiveSection): """ Class autogenerated from yaml schema. @@ -182,7 +223,7 @@ class ParameterList(MeasurementResult, PlotSection, ArchiveSection): class SampleSpecifications(ArchiveSection): - m_def = Section() + m_def = Section(a_eln=dict(overview=True)) sample_preparation_status = Quantity( type=str, description='Status of the sample preparation', @@ -211,21 +252,25 @@ class OmegaThetaXRD(Measurement, PlotSection, EntryData, ArchiveSection): description='Data file *.xrd containing the XRD data.', a_eln={'component': 'FileEditQuantity'}, ) - time_stamp = Quantity( - type=Datetime, - a_eln={'component': 'DateTimeEditQuantity'}, - ) + # time_stamp = Quantity( + # type=Datetime, + # a_eln={'component': 'DateTimeEditQuantity'}, + # ) measurement_type = Quantity( type=MEnum(['single measurement', 'mapping']), description='Type of the measurement', a_eln={'component': 'EnumEditQuantity'}, ) + + sample_specifications = SubSection( + section_def=SampleSpecifications, + ) results = SubSection( section_def=ParameterList, repeats=True, ) - sample_specifications = SubSection( - section_def=SampleSpecifications, + instruments = SubSection( + section_def=OmegaThetaXRDInstrumentReference, ) def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: @@ -261,8 +306,11 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: ) scan_dict = extract_scan_data(xrd_dict.get('Measurement', {})) - self.name = info_dict.get('name') - self.time_stamp = datetime.strptime( + self.name = info_dict.get('name').split('_')[ + 0 + ] # should be original_name + self.lab_id = self.name + self.datetime = datetime.strptime( info_dict.get('time_stamp'), '%m/%d/%Y %H:%M:%S' ) self.scan_recipe_name = info_dict.get('scan_recipe_name') @@ -291,6 +339,22 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: # results.normalize(archive, logger) self.results = [results] + xrdinstrumentref = OmegaThetaXRDInstrumentReference() + xrdinstrumentref.lab_id = info_dict.get('device_serial_no') + # xrdinstrumentref.normalize(archive, logger) + if xrdinstrumentref.reference is None: + xrdinstrument = OmegaThetaXRDInstrument( + lab_id=xrdinstrumentref.lab_id + ) + # self.instruments = [ramanspectrometer] + + xrdinstrumentref.reference = create_archive( + xrdinstrument, + archive, + f'Freiberger_Omega_Theta_XRD_{xrdinstrumentref.lab_id}.archive.json', + ) + self.instruments = [xrdinstrumentref] + fig = go.Figure() fig.add_trace( go.Scatter( @@ -329,8 +393,9 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: xrd_dict.get('MultiMeasurement', {}) ) - self.name = info_dict.get('name') - self.time_stamp = datetime.strptime( + self.name = info_dict.get('name').split('_')[0] + self.lab_id = self.name + self.datetime = datetime.strptime( info_dict.get('time_stamp'), '%m/%d/%Y %H:%M:%S' ) self.scan_recipe_name = info_dict.get('scan_recipe_name') @@ -369,6 +434,22 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: # results.normalize(archive, logger) self.results.append(results) + xrdinstrumentref = OmegaThetaXRDInstrumentReference() + xrdinstrumentref.lab_id = info_dict.get('device_serial_no') + # xrdinstrumentref.normalize(archive, logger) + if xrdinstrumentref.reference is None: + xrdinstrument = OmegaThetaXRDInstrument( + lab_id=xrdinstrumentref.lab_id + ) + # self.instruments = [ramanspectrometer] + + xrdinstrumentref.reference = create_archive( + xrdinstrument, + archive, + f'Freiberger_Omega_Theta_XRD_{xrdinstrumentref.lab_id}.archive.json', + ) + self.instruments = [xrdinstrumentref] + if self.results != None: # Extracting data for the plots x_coords = [int(point['x_pos']) for point in self.results] @@ -399,29 +480,96 @@ def get_colors(values): cmap = plt.cm.viridis return [cmap(norm(value)) for value in values] + # def get_colors_pl(values): + # norm = plt.Normalize(min(values), max(values)) + # colorscale = pc.get_colorscale('balance') + # return [ + # pc.find_intermediate_color( + # colorscale, intermed=norm(value), colortype='rgb' + # ) + # for value in values + # ] + def get_colors_pl(values): + norm = plt.Normalize(min(values), max(values)) + colorscale = pc.get_colorscale('Picnic') + + def get_interpolated_color(value): + scaled_value = norm(value) + + for i in range(1, len(colorscale)): + low_pos, low_color = colorscale[i - 1] + high_pos, high_color = colorscale[i] + + # Ensure positions are treated as floats + low_pos = float(low_pos) + high_pos = float(high_pos) + + if low_pos <= scaled_value <= high_pos: + return pc.find_intermediate_color( + lowcolor=low_color, + highcolor=high_color, + intermed=(scaled_value - low_pos) + / (high_pos - low_pos), + colortype='rgb', + ) + + # Return the last color if value exceeds the colorscale range + return colorscale[-1][1] + + return [get_interpolated_color(value) for value in values] + # Function to convert RGBA to hex def rgba_to_hex(rgba): return f'#{int(rgba[0]*255):02x}{int(rgba[1]*255):02x}{int(rgba[2]*255):02x}' # Function to create a scatter plot with text annotations and color gradient boxes def create_plot(x_coords, y_coords, values, title): - colors = get_colors(values) - hex_colors = [rgba_to_hex(color) for color in colors] + colors = get_colors_pl(values) + # hex_colors = [rgba_to_hex(color) for color in colors] fig = go.Figure() + + # Define the circle's center and radius + circle_center_x = sum(x_coords) / len( + x_coords + ) # Center of x_coords + circle_center_y = sum(y_coords) / len( + y_coords + ) # Center of y_coords + circle_radius = ( + 3 + + max( + max(x_coords) - min(x_coords), + max(y_coords) - min(y_coords), + ) + / 2 + ) + + # Add the circle to the plot + fig.add_shape( + type='circle', + xref='x', + yref='y', + x0=circle_center_x - circle_radius, + y0=circle_center_y - circle_radius, + x1=circle_center_x + circle_radius, + y1=circle_center_y + circle_radius, + line=dict(color='darkgrey', width=2), + fillcolor='grey', + opacity=0.3, + ) + for x, y, value, color in zip( - x_coords, y_coords, values, hex_colors + x_coords, + y_coords, + values, + colors, # hex_colors ): - fig.add_trace( - go.Scatter( - x=[x], - y=[y], - mode='text', - text=[f'{float(value):.3f}'], - textposition='middle center', - showlegend=False, - ) - ) + if title == 'Tilt Direction': + text_format = f'{float(value):.1f}' + else: + text_format = f'{float(value):.3f}' + # Adding box around the text with color gradient fig.add_shape( type='rect', @@ -431,15 +579,74 @@ def create_plot(x_coords, y_coords, values, title): y1=y + 1.5, line=dict(color=color, width=2), fillcolor=color, - opacity=0.5, + opacity=1, + ) + # fig.add_trace( + # go.Scatter( + # x=[x], + # y=[y], + # mode='text', + # # marker=dict( + # # size=0, + # # ), + # text=text_format, # [f'{float(value):.3f}'], + # textfont=dict( + # size=12, # Font size + # color='black', # Font color + # family='Arial', # Font family + # weight='bold', # Font weight for bold text + # ), + # textposition='middle center', + # showlegend=False, + # ) + # ) + # Add text annotation on top of the colored box + fig.add_annotation( + x=x, + y=y, + text=text_format, + showarrow=False, + font=dict(color='black', size=12), + xanchor='center', + yanchor='middle', ) + + # Add color bar + fig.add_trace( + go.Scatter( + x=x_coords, + y=y_coords, + mode='markers', + opacity=0, + marker=dict( + size=0, # Hide the markers + color=values, + colorscale='Picnic', # Choose a colorscale + colorbar=dict( + title='', + titleside='right', + ), + ), + showlegend=False, + ) + ) fig.update_layout( title=title, xaxis_title='X Position', yaxis_title='Y Position', plot_bgcolor='white', - xaxis=dict(showgrid=True, zeroline=False), - yaxis=dict(showgrid=True, zeroline=False), + xaxis=dict( + showgrid=True, + zeroline=False, + # scaleanchor='y', + # scaleratio=1, + ), + yaxis=dict( + showgrid=True, + zeroline=False, + scaleanchor='x', + scaleratio=1, + ), ) return fig diff --git a/src/nomad_ikz_omega_theta_xrd/schema_packages/omegathetaxrdreader.py b/src/nomad_ikz_omega_theta_xrd/schema_packages/omegathetaxrdreader.py index b49eca7..fc2bc8c 100644 --- a/src/nomad_ikz_omega_theta_xrd/schema_packages/omegathetaxrdreader.py +++ b/src/nomad_ikz_omega_theta_xrd/schema_packages/omegathetaxrdreader.py @@ -34,6 +34,7 @@ def extract_general_info( """Extract general information from the XRD data.""" general_info = { 'name': xrd_dict.get('Info', {}).get('Name'), + 'original_name': xrd_dict.get('Info', {}).get('OriginalName'), 'time_stamp': xrd_dict.get('Info', {}).get('TimeStamp'), 'user': xrd_dict.get('Info', {}).get('User'), 'description': xrd_dict.get('Info', {}).get('Comment'), diff --git a/src/nomad_ikz_omega_theta_xrd/schema_packages/utils.py b/src/nomad_ikz_omega_theta_xrd/schema_packages/utils.py new file mode 100644 index 0000000..d4f940e --- /dev/null +++ b/src/nomad_ikz_omega_theta_xrd/schema_packages/utils.py @@ -0,0 +1,25 @@ +def get_reference(upload_id, entry_id): + return f'../uploads/{upload_id}/archive/{entry_id}#/data' + + +def get_entry_id_from_file_name(file_name, archive): + from nomad.utils import hash + + return hash(archive.metadata.upload_id, file_name) + + +def create_archive(entity, archive, file_name) -> str: + import json + + from nomad.datamodel.context import ClientContext + + if isinstance(archive.m_context, ClientContext): + return None + if not archive.m_context.raw_path_exists(file_name): + entity_entry = entity.m_to_dict(with_root_def=True) + with archive.m_context.raw_file(file_name, 'w') as outfile: + json.dump({'data': entity_entry}, outfile) + archive.m_context.process_updated_raw_file(file_name) + return get_reference( + archive.metadata.upload_id, get_entry_id_from_file_name(file_name, archive) + )